Compare commits
58 Commits
git-graph
...
revert-pre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee53b805f0 | ||
|
|
9e628505f3 | ||
|
|
3a84ec38ac | ||
|
|
a61bf33fb0 | ||
|
|
d83201256d | ||
|
|
8ee85eab3c | ||
|
|
5b309ef986 | ||
|
|
326ebb5230 | ||
|
|
f5babf96e1 | ||
|
|
f48aa252f8 | ||
|
|
4106c8a188 | ||
|
|
21f7e6a9e6 | ||
|
|
dd431631b4 | ||
|
|
511e51c80e | ||
|
|
0a816cbc87 | ||
|
|
b1333b53ad | ||
|
|
30597a0cba | ||
|
|
a8e2dc2f25 | ||
|
|
fd2094fa19 | ||
|
|
22f1655f8f | ||
|
|
7cbe25fda5 | ||
|
|
728f09f3f4 | ||
|
|
4353b8ecd5 | ||
|
|
736a712387 | ||
|
|
3180f44477 | ||
|
|
5dd8561b06 | ||
|
|
bfab0b71e0 | ||
|
|
04d920016f | ||
|
|
20fa9983ad | ||
|
|
dd57d97bb6 | ||
|
|
d5a437d22f | ||
|
|
a524071dd9 | ||
|
|
1471105643 | ||
|
|
f05ee8a24d | ||
|
|
4d0cada8f4 | ||
|
|
abf90cc274 | ||
|
|
b79d92d1c6 | ||
|
|
660234fed2 | ||
|
|
2b02b60317 | ||
|
|
9d49c1ffda | ||
|
|
6253b1d220 | ||
|
|
4e75f0f3ab | ||
|
|
0b4f72e549 | ||
|
|
dc5f54eaf9 | ||
|
|
ba807a3c46 | ||
|
|
45829b3380 | ||
|
|
631e3dd272 | ||
|
|
8d44bcd4f9 | ||
|
|
1888106664 | ||
|
|
c005adb09c | ||
|
|
6b2d1f153d | ||
|
|
22e1bcccad | ||
|
|
bb591f1e65 | ||
|
|
3d6cc3dc79 | ||
|
|
464d4f72eb | ||
|
|
f4892559f0 | ||
|
|
387059c6b2 | ||
|
|
4a382b2797 |
33
Cargo.lock
generated
33
Cargo.lock
generated
@@ -401,6 +401,7 @@ dependencies = [
|
|||||||
"unindent",
|
"unindent",
|
||||||
"url",
|
"url",
|
||||||
"util",
|
"util",
|
||||||
|
"uuid",
|
||||||
"watch",
|
"watch",
|
||||||
"workspace",
|
"workspace",
|
||||||
"zed_actions",
|
"zed_actions",
|
||||||
@@ -3594,6 +3595,7 @@ dependencies = [
|
|||||||
"settings",
|
"settings",
|
||||||
"smol",
|
"smol",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"terminal",
|
||||||
"url",
|
"url",
|
||||||
"util",
|
"util",
|
||||||
]
|
]
|
||||||
@@ -5247,6 +5249,7 @@ dependencies = [
|
|||||||
"client",
|
"client",
|
||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
|
"text",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5799,6 +5802,7 @@ dependencies = [
|
|||||||
"gpui",
|
"gpui",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"http_client",
|
"http_client",
|
||||||
|
"indoc",
|
||||||
"language",
|
"language",
|
||||||
"log",
|
"log",
|
||||||
"lsp",
|
"lsp",
|
||||||
@@ -6997,28 +7001,6 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "git_graph"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"collections",
|
|
||||||
"db",
|
|
||||||
"git",
|
|
||||||
"git_ui",
|
|
||||||
"gpui",
|
|
||||||
"menu",
|
|
||||||
"project",
|
|
||||||
"settings",
|
|
||||||
"smallvec",
|
|
||||||
"theme",
|
|
||||||
"time",
|
|
||||||
"ui",
|
|
||||||
"ui_input",
|
|
||||||
"util",
|
|
||||||
"workspace",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "git_hosting_providers"
|
name = "git_hosting_providers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -7776,7 +7758,6 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
"url",
|
"url",
|
||||||
"util",
|
"util",
|
||||||
"zed-reqwest",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -13176,6 +13157,7 @@ dependencies = [
|
|||||||
"askpass",
|
"askpass",
|
||||||
"auto_update",
|
"auto_update",
|
||||||
"dap",
|
"dap",
|
||||||
|
"db",
|
||||||
"editor",
|
"editor",
|
||||||
"extension_host",
|
"extension_host",
|
||||||
"file_finder",
|
"file_finder",
|
||||||
@@ -13187,6 +13169,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"markdown",
|
"markdown",
|
||||||
"menu",
|
"menu",
|
||||||
|
"node_runtime",
|
||||||
"ordered-float 2.10.1",
|
"ordered-float 2.10.1",
|
||||||
"paths",
|
"paths",
|
||||||
"picker",
|
"picker",
|
||||||
@@ -13205,6 +13188,7 @@ dependencies = [
|
|||||||
"util",
|
"util",
|
||||||
"windows-registry 0.6.1",
|
"windows-registry 0.6.1",
|
||||||
"workspace",
|
"workspace",
|
||||||
|
"worktree",
|
||||||
"zed_actions",
|
"zed_actions",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -20489,7 +20473,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.217.0"
|
version = "0.218.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"acp_tools",
|
"acp_tools",
|
||||||
"activity_indicator",
|
"activity_indicator",
|
||||||
@@ -20537,7 +20521,6 @@ dependencies = [
|
|||||||
"fs",
|
"fs",
|
||||||
"futures 0.3.31",
|
"futures 0.3.31",
|
||||||
"git",
|
"git",
|
||||||
"git_graph",
|
|
||||||
"git_hosting_providers",
|
"git_hosting_providers",
|
||||||
"git_ui",
|
"git_ui",
|
||||||
"go_to_line",
|
"go_to_line",
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ members = [
|
|||||||
"crates/fsevent",
|
"crates/fsevent",
|
||||||
"crates/fuzzy",
|
"crates/fuzzy",
|
||||||
"crates/git",
|
"crates/git",
|
||||||
"crates/git_graph",
|
|
||||||
"crates/git_hosting_providers",
|
"crates/git_hosting_providers",
|
||||||
"crates/git_ui",
|
"crates/git_ui",
|
||||||
"crates/go_to_line",
|
"crates/go_to_line",
|
||||||
@@ -300,7 +299,6 @@ fs = { path = "crates/fs" }
|
|||||||
fsevent = { path = "crates/fsevent" }
|
fsevent = { path = "crates/fsevent" }
|
||||||
fuzzy = { path = "crates/fuzzy" }
|
fuzzy = { path = "crates/fuzzy" }
|
||||||
git = { path = "crates/git" }
|
git = { path = "crates/git" }
|
||||||
git_graph = { path = "crates/git_graph" }
|
|
||||||
git_hosting_providers = { path = "crates/git_hosting_providers" }
|
git_hosting_providers = { path = "crates/git_hosting_providers" }
|
||||||
git_ui = { path = "crates/git_ui" }
|
git_ui = { path = "crates/git_ui" }
|
||||||
go_to_line = { path = "crates/go_to_line" }
|
go_to_line = { path = "crates/go_to_line" }
|
||||||
|
|||||||
@@ -34,8 +34,4 @@ RUN apt-get update; \
|
|||||||
linux-perf binutils
|
linux-perf binutils
|
||||||
WORKDIR app
|
WORKDIR app
|
||||||
COPY --from=builder /app/collab /app/collab
|
COPY --from=builder /app/collab /app/collab
|
||||||
COPY --from=builder /app/crates/collab/migrations /app/migrations
|
|
||||||
COPY --from=builder /app/crates/collab/migrations_llm /app/migrations_llm
|
|
||||||
ENV MIGRATIONS_PATH=/app/migrations
|
|
||||||
ENV LLM_DATABASE_MIGRATIONS_PATH=/app/migrations_llm
|
|
||||||
ENTRYPOINT ["/app/collab"]
|
ENTRYPOINT ["/app/collab"]
|
||||||
|
|||||||
5
assets/icons/box.svg
Normal file
5
assets/icons/box.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.3996 5.59852C13.3994 5.3881 13.3439 5.18144 13.2386 4.99926C13.1333 4.81709 12.9819 4.66581 12.7997 4.56059L8.59996 2.16076C8.41755 2.05544 8.21063 2 8 2C7.78937 2 7.58246 2.05544 7.40004 2.16076L3.20033 4.56059C3.0181 4.66581 2.86674 4.81709 2.76144 4.99926C2.65613 5.18144 2.60059 5.3881 2.60037 5.59852V10.3982C2.60059 10.6086 2.65613 10.8153 2.76144 10.9975C2.86674 11.1796 3.0181 11.3309 3.20033 11.4361L7.40004 13.836C7.58246 13.9413 7.78937 13.9967 8 13.9967C8.21063 13.9967 8.41755 13.9413 8.59996 13.836L12.7997 11.4361C12.9819 11.3309 13.1333 11.1796 13.2386 10.9975C13.3439 10.8153 13.3994 10.6086 13.3996 10.3982V5.59852Z" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M2.78033 4.99857L7.99998 7.99836L13.2196 4.99857" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M8 13.9979V7.99829" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -811,7 +811,10 @@
|
|||||||
"context": "PromptEditor",
|
"context": "PromptEditor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-[": "agent::CyclePreviousInlineAssist",
|
"ctrl-[": "agent::CyclePreviousInlineAssist",
|
||||||
"ctrl-]": "agent::CycleNextInlineAssist"
|
"ctrl-]": "agent::CycleNextInlineAssist",
|
||||||
|
"ctrl-shift-enter": "inline_assistant::ThumbsUpResult",
|
||||||
|
"ctrl-shift-backspace": "inline_assistant::ThumbsDownResult"
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -816,7 +816,9 @@
|
|||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-[": "agent::CyclePreviousInlineAssist",
|
"ctrl-[": "agent::CyclePreviousInlineAssist",
|
||||||
"ctrl-]": "agent::CycleNextInlineAssist"
|
"ctrl-]": "agent::CycleNextInlineAssist",
|
||||||
|
"ctrl-shift-enter": "inline_assistant::ThumbsUpResult",
|
||||||
|
"ctrl-shift-delete": "inline_assistant::ThumbsDownResult"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -180,7 +180,6 @@
|
|||||||
"ctrl-w g shift-d": "editor::GoToTypeDefinitionSplit",
|
"ctrl-w g shift-d": "editor::GoToTypeDefinitionSplit",
|
||||||
"ctrl-w space": "editor::OpenExcerptsSplit",
|
"ctrl-w space": "editor::OpenExcerptsSplit",
|
||||||
"ctrl-w g space": "editor::OpenExcerptsSplit",
|
"ctrl-w g space": "editor::OpenExcerptsSplit",
|
||||||
"ctrl-6": "pane::AlternateFile",
|
|
||||||
"ctrl-^": "pane::AlternateFile",
|
"ctrl-^": "pane::AlternateFile",
|
||||||
".": "vim::Repeat"
|
".": "vim::Repeat"
|
||||||
}
|
}
|
||||||
@@ -902,7 +901,11 @@
|
|||||||
"context": "!Editor && !Terminal",
|
"context": "!Editor && !Terminal",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
":": "command_palette::Toggle",
|
":": "command_palette::Toggle",
|
||||||
"g /": "pane::DeploySearch"
|
"g /": "pane::DeploySearch",
|
||||||
|
"] b": "pane::ActivateNextItem",
|
||||||
|
"[ b": "pane::ActivatePreviousItem",
|
||||||
|
"] shift-b": "pane::ActivateLastItem",
|
||||||
|
"[ shift-b": ["pane::ActivateItem", 0]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -870,6 +870,10 @@
|
|||||||
//
|
//
|
||||||
// Default: false
|
// Default: false
|
||||||
"collapse_untracked_diff": false,
|
"collapse_untracked_diff": false,
|
||||||
|
/// Whether to show entries with tree or flat view in the panel
|
||||||
|
///
|
||||||
|
/// Default: false
|
||||||
|
"tree_view": false,
|
||||||
"scrollbar": {
|
"scrollbar": {
|
||||||
// When to show the scrollbar in the git panel.
|
// When to show the scrollbar in the git panel.
|
||||||
//
|
//
|
||||||
@@ -1810,6 +1814,9 @@
|
|||||||
"allowed": false
|
"allowed": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"CSharp": {
|
||||||
|
"language_servers": ["roslyn", "!omnisharp", "..."]
|
||||||
|
},
|
||||||
"CSS": {
|
"CSS": {
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"allowed": true
|
"allowed": true
|
||||||
|
|||||||
@@ -1372,7 +1372,7 @@ impl AcpThread {
|
|||||||
let path_style = self.project.read(cx).path_style(cx);
|
let path_style = self.project.read(cx).path_style(cx);
|
||||||
let id = update.tool_call_id.clone();
|
let id = update.tool_call_id.clone();
|
||||||
|
|
||||||
let agent = self.connection().telemetry_id();
|
let agent_telemetry_id = self.connection().telemetry_id();
|
||||||
let session = self.session_id();
|
let session = self.session_id();
|
||||||
if let ToolCallStatus::Completed | ToolCallStatus::Failed = status {
|
if let ToolCallStatus::Completed | ToolCallStatus::Failed = status {
|
||||||
let status = if matches!(status, ToolCallStatus::Completed) {
|
let status = if matches!(status, ToolCallStatus::Completed) {
|
||||||
@@ -1380,7 +1380,12 @@ impl AcpThread {
|
|||||||
} else {
|
} else {
|
||||||
"failed"
|
"failed"
|
||||||
};
|
};
|
||||||
telemetry::event!("Agent Tool Call Completed", agent, session, status);
|
telemetry::event!(
|
||||||
|
"Agent Tool Call Completed",
|
||||||
|
agent_telemetry_id,
|
||||||
|
session,
|
||||||
|
status
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ix) = self.index_for_tool_call(&id) {
|
if let Some(ix) = self.index_for_tool_call(&id) {
|
||||||
@@ -3556,8 +3561,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AgentConnection for FakeAgentConnection {
|
impl AgentConnection for FakeAgentConnection {
|
||||||
fn telemetry_id(&self) -> &'static str {
|
fn telemetry_id(&self) -> SharedString {
|
||||||
"fake"
|
"fake".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn auth_methods(&self) -> &[acp::AuthMethod] {
|
fn auth_methods(&self) -> &[acp::AuthMethod] {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ impl UserMessageId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait AgentConnection {
|
pub trait AgentConnection {
|
||||||
fn telemetry_id(&self) -> &'static str;
|
fn telemetry_id(&self) -> SharedString;
|
||||||
|
|
||||||
fn new_thread(
|
fn new_thread(
|
||||||
self: Rc<Self>,
|
self: Rc<Self>,
|
||||||
@@ -322,8 +322,8 @@ mod test_support {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AgentConnection for StubAgentConnection {
|
impl AgentConnection for StubAgentConnection {
|
||||||
fn telemetry_id(&self) -> &'static str {
|
fn telemetry_id(&self) -> SharedString {
|
||||||
"stub"
|
"stub".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn auth_methods(&self) -> &[acp::AuthMethod] {
|
fn auth_methods(&self) -> &[acp::AuthMethod] {
|
||||||
|
|||||||
@@ -777,7 +777,7 @@ impl ActionLog {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ActionLogTelemetry {
|
pub struct ActionLogTelemetry {
|
||||||
pub agent_telemetry_id: &'static str,
|
pub agent_telemetry_id: SharedString,
|
||||||
pub session_id: Arc<str>,
|
pub session_id: Arc<str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -947,8 +947,8 @@ impl acp_thread::AgentModelSelector for NativeAgentModelSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl acp_thread::AgentConnection for NativeAgentConnection {
|
impl acp_thread::AgentConnection for NativeAgentConnection {
|
||||||
fn telemetry_id(&self) -> &'static str {
|
fn telemetry_id(&self) -> SharedString {
|
||||||
"zed"
|
"zed".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_thread(
|
fn new_thread(
|
||||||
|
|||||||
@@ -21,10 +21,6 @@ impl NativeAgentServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AgentServer for NativeAgentServer {
|
impl AgentServer for NativeAgentServer {
|
||||||
fn telemetry_id(&self) -> &'static str {
|
|
||||||
"zed"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> SharedString {
|
fn name(&self) -> SharedString {
|
||||||
"Zed Agent".into()
|
"Zed Agent".into()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ use futures::io::BufReader;
|
|||||||
use project::Project;
|
use project::Project;
|
||||||
use project::agent_server_store::AgentServerCommand;
|
use project::agent_server_store::AgentServerCommand;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use settings::Settings as _;
|
||||||
|
use task::ShellBuilder;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use task::ShellKind;
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -21,7 +25,7 @@ use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString, Task, WeakEntit
|
|||||||
|
|
||||||
use acp_thread::{AcpThread, AuthRequired, LoadError, TerminalProviderEvent};
|
use acp_thread::{AcpThread, AuthRequired, LoadError, TerminalProviderEvent};
|
||||||
use terminal::TerminalBuilder;
|
use terminal::TerminalBuilder;
|
||||||
use terminal::terminal_settings::{AlternateScroll, CursorShape};
|
use terminal::terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[error("Unsupported version")]
|
#[error("Unsupported version")]
|
||||||
@@ -29,7 +33,7 @@ pub struct UnsupportedVersion;
|
|||||||
|
|
||||||
pub struct AcpConnection {
|
pub struct AcpConnection {
|
||||||
server_name: SharedString,
|
server_name: SharedString,
|
||||||
telemetry_id: &'static str,
|
telemetry_id: SharedString,
|
||||||
connection: Rc<acp::ClientSideConnection>,
|
connection: Rc<acp::ClientSideConnection>,
|
||||||
sessions: Rc<RefCell<HashMap<acp::SessionId, AcpSession>>>,
|
sessions: Rc<RefCell<HashMap<acp::SessionId, AcpSession>>>,
|
||||||
auth_methods: Vec<acp::AuthMethod>,
|
auth_methods: Vec<acp::AuthMethod>,
|
||||||
@@ -54,7 +58,6 @@ pub struct AcpSession {
|
|||||||
|
|
||||||
pub async fn connect(
|
pub async fn connect(
|
||||||
server_name: SharedString,
|
server_name: SharedString,
|
||||||
telemetry_id: &'static str,
|
|
||||||
command: AgentServerCommand,
|
command: AgentServerCommand,
|
||||||
root_dir: &Path,
|
root_dir: &Path,
|
||||||
default_mode: Option<acp::SessionModeId>,
|
default_mode: Option<acp::SessionModeId>,
|
||||||
@@ -64,7 +67,6 @@ pub async fn connect(
|
|||||||
) -> Result<Rc<dyn AgentConnection>> {
|
) -> Result<Rc<dyn AgentConnection>> {
|
||||||
let conn = AcpConnection::stdio(
|
let conn = AcpConnection::stdio(
|
||||||
server_name,
|
server_name,
|
||||||
telemetry_id,
|
|
||||||
command.clone(),
|
command.clone(),
|
||||||
root_dir,
|
root_dir,
|
||||||
default_mode,
|
default_mode,
|
||||||
@@ -81,7 +83,6 @@ const MINIMUM_SUPPORTED_VERSION: acp::ProtocolVersion = acp::ProtocolVersion::V1
|
|||||||
impl AcpConnection {
|
impl AcpConnection {
|
||||||
pub async fn stdio(
|
pub async fn stdio(
|
||||||
server_name: SharedString,
|
server_name: SharedString,
|
||||||
telemetry_id: &'static str,
|
|
||||||
command: AgentServerCommand,
|
command: AgentServerCommand,
|
||||||
root_dir: &Path,
|
root_dir: &Path,
|
||||||
default_mode: Option<acp::SessionModeId>,
|
default_mode: Option<acp::SessionModeId>,
|
||||||
@@ -89,9 +90,26 @@ impl AcpConnection {
|
|||||||
is_remote: bool,
|
is_remote: bool,
|
||||||
cx: &mut AsyncApp,
|
cx: &mut AsyncApp,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let mut child = util::command::new_smol_command(&command.path);
|
let shell = cx.update(|cx| TerminalSettings::get(None, cx).shell.clone())?;
|
||||||
|
let builder = ShellBuilder::new(&shell, cfg!(windows));
|
||||||
|
#[cfg(windows)]
|
||||||
|
let kind = builder.kind();
|
||||||
|
let (cmd, args) = builder.build(Some(command.path.display().to_string()), &command.args);
|
||||||
|
|
||||||
|
let mut child = util::command::new_smol_command(cmd);
|
||||||
|
#[cfg(windows)]
|
||||||
|
if kind == ShellKind::Cmd {
|
||||||
|
use smol::process::windows::CommandExt;
|
||||||
|
for arg in args {
|
||||||
|
child.raw_arg(arg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
child.args(args);
|
||||||
|
}
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
child.args(args);
|
||||||
|
|
||||||
child
|
child
|
||||||
.args(command.args.iter().map(|arg| arg.as_str()))
|
|
||||||
.envs(command.env.iter().flatten())
|
.envs(command.env.iter().flatten())
|
||||||
.stdin(std::process::Stdio::piped())
|
.stdin(std::process::Stdio::piped())
|
||||||
.stdout(std::process::Stdio::piped())
|
.stdout(std::process::Stdio::piped())
|
||||||
@@ -199,6 +217,13 @@ impl AcpConnection {
|
|||||||
return Err(UnsupportedVersion.into());
|
return Err(UnsupportedVersion.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let telemetry_id = response
|
||||||
|
.agent_info
|
||||||
|
// Use the one the agent provides if we have one
|
||||||
|
.map(|info| info.name.into())
|
||||||
|
// Otherwise, just use the name
|
||||||
|
.unwrap_or_else(|| server_name.clone());
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
auth_methods: response.auth_methods,
|
auth_methods: response.auth_methods,
|
||||||
root_dir: root_dir.to_owned(),
|
root_dir: root_dir.to_owned(),
|
||||||
@@ -233,8 +258,8 @@ impl Drop for AcpConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AgentConnection for AcpConnection {
|
impl AgentConnection for AcpConnection {
|
||||||
fn telemetry_id(&self) -> &'static str {
|
fn telemetry_id(&self) -> SharedString {
|
||||||
self.telemetry_id
|
self.telemetry_id.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_thread(
|
fn new_thread(
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ impl AgentServerDelegate {
|
|||||||
pub trait AgentServer: Send {
|
pub trait AgentServer: Send {
|
||||||
fn logo(&self) -> ui::IconName;
|
fn logo(&self) -> ui::IconName;
|
||||||
fn name(&self) -> SharedString;
|
fn name(&self) -> SharedString;
|
||||||
fn telemetry_id(&self) -> &'static str;
|
|
||||||
fn default_mode(&self, _cx: &mut App) -> Option<agent_client_protocol::SessionModeId> {
|
fn default_mode(&self, _cx: &mut App) -> Option<agent_client_protocol::SessionModeId> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,6 @@ pub struct AgentServerLoginCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AgentServer for ClaudeCode {
|
impl AgentServer for ClaudeCode {
|
||||||
fn telemetry_id(&self) -> &'static str {
|
|
||||||
"claude-code"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> SharedString {
|
fn name(&self) -> SharedString {
|
||||||
"Claude Code".into()
|
"Claude Code".into()
|
||||||
}
|
}
|
||||||
@@ -83,7 +79,6 @@ impl AgentServer for ClaudeCode {
|
|||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
||||||
let name = self.name();
|
let name = self.name();
|
||||||
let telemetry_id = self.telemetry_id();
|
|
||||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||||
let store = delegate.store.downgrade();
|
let store = delegate.store.downgrade();
|
||||||
@@ -108,7 +103,6 @@ impl AgentServer for ClaudeCode {
|
|||||||
.await?;
|
.await?;
|
||||||
let connection = crate::acp::connect(
|
let connection = crate::acp::connect(
|
||||||
name,
|
name,
|
||||||
telemetry_id,
|
|
||||||
command,
|
command,
|
||||||
root_dir.as_ref(),
|
root_dir.as_ref(),
|
||||||
default_mode,
|
default_mode,
|
||||||
|
|||||||
@@ -23,10 +23,6 @@ pub(crate) mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AgentServer for Codex {
|
impl AgentServer for Codex {
|
||||||
fn telemetry_id(&self) -> &'static str {
|
|
||||||
"codex"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> SharedString {
|
fn name(&self) -> SharedString {
|
||||||
"Codex".into()
|
"Codex".into()
|
||||||
}
|
}
|
||||||
@@ -84,7 +80,6 @@ impl AgentServer for Codex {
|
|||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
||||||
let name = self.name();
|
let name = self.name();
|
||||||
let telemetry_id = self.telemetry_id();
|
|
||||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||||
let store = delegate.store.downgrade();
|
let store = delegate.store.downgrade();
|
||||||
@@ -110,7 +105,6 @@ impl AgentServer for Codex {
|
|||||||
|
|
||||||
let connection = crate::acp::connect(
|
let connection = crate::acp::connect(
|
||||||
name,
|
name,
|
||||||
telemetry_id,
|
|
||||||
command,
|
command,
|
||||||
root_dir.as_ref(),
|
root_dir.as_ref(),
|
||||||
default_mode,
|
default_mode,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{AgentServerDelegate, load_proxy_env};
|
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
||||||
use acp_thread::AgentConnection;
|
use acp_thread::AgentConnection;
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
@@ -20,11 +20,7 @@ impl CustomAgentServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::AgentServer for CustomAgentServer {
|
impl AgentServer for CustomAgentServer {
|
||||||
fn telemetry_id(&self) -> &'static str {
|
|
||||||
"custom"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> SharedString {
|
fn name(&self) -> SharedString {
|
||||||
self.name.clone()
|
self.name.clone()
|
||||||
}
|
}
|
||||||
@@ -112,14 +108,12 @@ impl crate::AgentServer for CustomAgentServer {
|
|||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
||||||
let name = self.name();
|
let name = self.name();
|
||||||
let telemetry_id = self.telemetry_id();
|
|
||||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||||
let default_mode = self.default_mode(cx);
|
let default_mode = self.default_mode(cx);
|
||||||
let default_model = self.default_model(cx);
|
let default_model = self.default_model(cx);
|
||||||
let store = delegate.store.downgrade();
|
let store = delegate.store.downgrade();
|
||||||
let extra_env = load_proxy_env(cx);
|
let extra_env = load_proxy_env(cx);
|
||||||
|
|
||||||
cx.spawn(async move |cx| {
|
cx.spawn(async move |cx| {
|
||||||
let (command, root_dir, login) = store
|
let (command, root_dir, login) = store
|
||||||
.update(cx, |store, cx| {
|
.update(cx, |store, cx| {
|
||||||
@@ -139,7 +133,6 @@ impl crate::AgentServer for CustomAgentServer {
|
|||||||
.await?;
|
.await?;
|
||||||
let connection = crate::acp::connect(
|
let connection = crate::acp::connect(
|
||||||
name,
|
name,
|
||||||
telemetry_id,
|
|
||||||
command,
|
command,
|
||||||
root_dir.as_ref(),
|
root_dir.as_ref(),
|
||||||
default_mode,
|
default_mode,
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ use project::agent_server_store::GEMINI_NAME;
|
|||||||
pub struct Gemini;
|
pub struct Gemini;
|
||||||
|
|
||||||
impl AgentServer for Gemini {
|
impl AgentServer for Gemini {
|
||||||
fn telemetry_id(&self) -> &'static str {
|
|
||||||
"gemini-cli"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> SharedString {
|
fn name(&self) -> SharedString {
|
||||||
"Gemini CLI".into()
|
"Gemini CLI".into()
|
||||||
}
|
}
|
||||||
@@ -31,7 +27,6 @@ impl AgentServer for Gemini {
|
|||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
||||||
let name = self.name();
|
let name = self.name();
|
||||||
let telemetry_id = self.telemetry_id();
|
|
||||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||||
let store = delegate.store.downgrade();
|
let store = delegate.store.downgrade();
|
||||||
@@ -66,7 +61,6 @@ impl AgentServer for Gemini {
|
|||||||
|
|
||||||
let connection = crate::acp::connect(
|
let connection = crate::acp::connect(
|
||||||
name,
|
name,
|
||||||
telemetry_id,
|
|
||||||
command,
|
command,
|
||||||
root_dir.as_ref(),
|
root_dir.as_ref(),
|
||||||
default_mode,
|
default_mode,
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ ui.workspace = true
|
|||||||
ui_input.workspace = true
|
ui_input.workspace = true
|
||||||
url.workspace = true
|
url.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
|
uuid.workspace = true
|
||||||
watch.workspace = true
|
watch.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
zed_actions.workspace = true
|
zed_actions.workspace = true
|
||||||
|
|||||||
@@ -565,7 +565,33 @@ impl MessageEditor {
|
|||||||
if let Some((workspace, selections)) =
|
if let Some((workspace, selections)) =
|
||||||
self.workspace.upgrade().zip(editor_clipboard_selections)
|
self.workspace.upgrade().zip(editor_clipboard_selections)
|
||||||
{
|
{
|
||||||
|
let Some(first_selection) = selections.first() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(file_path) = &first_selection.file_path {
|
||||||
|
// In case someone pastes selections from another window
|
||||||
|
// with a different project, we don't want to insert the
|
||||||
|
// crease (containing the absolute path) since the agent
|
||||||
|
// cannot access files outside the project.
|
||||||
|
let is_in_project = workspace
|
||||||
|
.read(cx)
|
||||||
|
.project()
|
||||||
|
.read(cx)
|
||||||
|
.project_path_for_absolute_path(file_path, cx)
|
||||||
|
.is_some();
|
||||||
|
if !is_in_project {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
|
let insertion_target = self
|
||||||
|
.editor
|
||||||
|
.read(cx)
|
||||||
|
.selections
|
||||||
|
.newest_anchor()
|
||||||
|
.start
|
||||||
|
.text_anchor;
|
||||||
|
|
||||||
let project = workspace.read(cx).project().clone();
|
let project = workspace.read(cx).project().clone();
|
||||||
for selection in selections {
|
for selection in selections {
|
||||||
@@ -587,8 +613,7 @@ impl MessageEditor {
|
|||||||
let snapshot = buffer.snapshot(cx);
|
let snapshot = buffer.snapshot(cx);
|
||||||
let (excerpt_id, _, buffer_snapshot) =
|
let (excerpt_id, _, buffer_snapshot) =
|
||||||
snapshot.as_singleton().unwrap();
|
snapshot.as_singleton().unwrap();
|
||||||
let start_offset = buffer_snapshot.len();
|
let text_anchor = insertion_target.bias_left(&buffer_snapshot);
|
||||||
let text_anchor = buffer_snapshot.anchor_before(start_offset);
|
|
||||||
|
|
||||||
editor.insert(&mention_text, window, cx);
|
editor.insert(&mention_text, window, cx);
|
||||||
editor.insert(" ", window, cx);
|
editor.insert(" ", window, cx);
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ impl ThreadFeedbackState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let session_id = thread.read(cx).session_id().clone();
|
let session_id = thread.read(cx).session_id().clone();
|
||||||
let agent = thread.read(cx).connection().telemetry_id();
|
let agent_telemetry_id = thread.read(cx).connection().telemetry_id();
|
||||||
let task = telemetry.thread_data(&session_id, cx);
|
let task = telemetry.thread_data(&session_id, cx);
|
||||||
let rating = match feedback {
|
let rating = match feedback {
|
||||||
ThreadFeedback::Positive => "positive",
|
ThreadFeedback::Positive => "positive",
|
||||||
@@ -180,7 +180,7 @@ impl ThreadFeedbackState {
|
|||||||
let thread = task.await?;
|
let thread = task.await?;
|
||||||
telemetry::event!(
|
telemetry::event!(
|
||||||
"Agent Thread Rated",
|
"Agent Thread Rated",
|
||||||
agent = agent,
|
agent = agent_telemetry_id,
|
||||||
session_id = session_id,
|
session_id = session_id,
|
||||||
rating = rating,
|
rating = rating,
|
||||||
thread = thread
|
thread = thread
|
||||||
@@ -207,13 +207,13 @@ impl ThreadFeedbackState {
|
|||||||
self.comments_editor.take();
|
self.comments_editor.take();
|
||||||
|
|
||||||
let session_id = thread.read(cx).session_id().clone();
|
let session_id = thread.read(cx).session_id().clone();
|
||||||
let agent = thread.read(cx).connection().telemetry_id();
|
let agent_telemetry_id = thread.read(cx).connection().telemetry_id();
|
||||||
let task = telemetry.thread_data(&session_id, cx);
|
let task = telemetry.thread_data(&session_id, cx);
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let thread = task.await?;
|
let thread = task.await?;
|
||||||
telemetry::event!(
|
telemetry::event!(
|
||||||
"Agent Thread Feedback Comments",
|
"Agent Thread Feedback Comments",
|
||||||
agent = agent,
|
agent = agent_telemetry_id,
|
||||||
session_id = session_id,
|
session_id = session_id,
|
||||||
comments = comments,
|
comments = comments,
|
||||||
thread = thread
|
thread = thread
|
||||||
@@ -333,6 +333,7 @@ impl AcpThreadView {
|
|||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
history_store: Entity<HistoryStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
|
track_load_event: bool,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -391,8 +392,9 @@ impl AcpThreadView {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
let show_codex_windows_warning = crate::ExternalAgent::parse_built_in(agent.as_ref())
|
let show_codex_windows_warning = cfg!(windows)
|
||||||
== Some(crate::ExternalAgent::Codex);
|
&& project.read(cx).is_local()
|
||||||
|
&& agent.clone().downcast::<agent_servers::Codex>().is_some();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
agent: agent.clone(),
|
agent: agent.clone(),
|
||||||
@@ -404,6 +406,7 @@ impl AcpThreadView {
|
|||||||
resume_thread.clone(),
|
resume_thread.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
project.clone(),
|
project.clone(),
|
||||||
|
track_load_event,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
),
|
),
|
||||||
@@ -448,6 +451,7 @@ impl AcpThreadView {
|
|||||||
self.resume_thread_metadata.clone(),
|
self.resume_thread_metadata.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
self.project.clone(),
|
self.project.clone(),
|
||||||
|
true,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
@@ -461,6 +465,7 @@ impl AcpThreadView {
|
|||||||
resume_thread: Option<DbThreadMetadata>,
|
resume_thread: Option<DbThreadMetadata>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
|
track_load_event: bool,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> ThreadState {
|
) -> ThreadState {
|
||||||
@@ -519,6 +524,10 @@ impl AcpThreadView {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if track_load_event {
|
||||||
|
telemetry::event!("Agent Thread Started", agent = connection.telemetry_id());
|
||||||
|
}
|
||||||
|
|
||||||
let result = if let Some(native_agent) = connection
|
let result = if let Some(native_agent) = connection
|
||||||
.clone()
|
.clone()
|
||||||
.downcast::<agent::NativeAgentConnection>()
|
.downcast::<agent::NativeAgentConnection>()
|
||||||
@@ -1133,8 +1142,8 @@ impl AcpThreadView {
|
|||||||
let Some(thread) = self.thread() else {
|
let Some(thread) = self.thread() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let agent_telemetry_id = self.agent.telemetry_id();
|
|
||||||
let session_id = thread.read(cx).session_id().clone();
|
let session_id = thread.read(cx).session_id().clone();
|
||||||
|
let agent_telemetry_id = thread.read(cx).connection().telemetry_id();
|
||||||
let thread = thread.downgrade();
|
let thread = thread.downgrade();
|
||||||
if self.should_be_following {
|
if self.should_be_following {
|
||||||
self.workspace
|
self.workspace
|
||||||
@@ -1512,6 +1521,7 @@ impl AcpThreadView {
|
|||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let agent_telemetry_id = connection.telemetry_id();
|
||||||
|
|
||||||
// Check for the experimental "terminal-auth" _meta field
|
// Check for the experimental "terminal-auth" _meta field
|
||||||
let auth_method = connection.auth_methods().iter().find(|m| m.id == method);
|
let auth_method = connection.auth_methods().iter().find(|m| m.id == method);
|
||||||
@@ -1579,19 +1589,18 @@ impl AcpThreadView {
|
|||||||
);
|
);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
self.auth_task = Some(cx.spawn_in(window, {
|
self.auth_task = Some(cx.spawn_in(window, {
|
||||||
let agent = self.agent.clone();
|
|
||||||
async move |this, cx| {
|
async move |this, cx| {
|
||||||
let result = authenticate.await;
|
let result = authenticate.await;
|
||||||
|
|
||||||
match &result {
|
match &result {
|
||||||
Ok(_) => telemetry::event!(
|
Ok(_) => telemetry::event!(
|
||||||
"Authenticate Agent Succeeded",
|
"Authenticate Agent Succeeded",
|
||||||
agent = agent.telemetry_id()
|
agent = agent_telemetry_id
|
||||||
),
|
),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
telemetry::event!(
|
telemetry::event!(
|
||||||
"Authenticate Agent Failed",
|
"Authenticate Agent Failed",
|
||||||
agent = agent.telemetry_id(),
|
agent = agent_telemetry_id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1675,6 +1684,7 @@ impl AcpThreadView {
|
|||||||
None,
|
None,
|
||||||
this.workspace.clone(),
|
this.workspace.clone(),
|
||||||
this.project.clone(),
|
this.project.clone(),
|
||||||
|
true,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -1730,43 +1740,38 @@ impl AcpThreadView {
|
|||||||
connection.authenticate(method, cx)
|
connection.authenticate(method, cx)
|
||||||
};
|
};
|
||||||
cx.notify();
|
cx.notify();
|
||||||
self.auth_task =
|
self.auth_task = Some(cx.spawn_in(window, {
|
||||||
Some(cx.spawn_in(window, {
|
async move |this, cx| {
|
||||||
let agent = self.agent.clone();
|
let result = authenticate.await;
|
||||||
async move |this, cx| {
|
|
||||||
let result = authenticate.await;
|
|
||||||
|
|
||||||
match &result {
|
match &result {
|
||||||
Ok(_) => telemetry::event!(
|
Ok(_) => telemetry::event!(
|
||||||
"Authenticate Agent Succeeded",
|
"Authenticate Agent Succeeded",
|
||||||
agent = agent.telemetry_id()
|
agent = agent_telemetry_id
|
||||||
),
|
),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
telemetry::event!(
|
telemetry::event!("Authenticate Agent Failed", agent = agent_telemetry_id,)
|
||||||
"Authenticate Agent Failed",
|
|
||||||
agent = agent.telemetry_id(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update_in(cx, |this, window, cx| {
|
|
||||||
if let Err(err) = result {
|
|
||||||
if let ThreadState::Unauthenticated {
|
|
||||||
pending_auth_method,
|
|
||||||
..
|
|
||||||
} = &mut this.thread_state
|
|
||||||
{
|
|
||||||
pending_auth_method.take();
|
|
||||||
}
|
|
||||||
this.handle_thread_error(err, cx);
|
|
||||||
} else {
|
|
||||||
this.reset(window, cx);
|
|
||||||
}
|
|
||||||
this.auth_task.take()
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
}));
|
|
||||||
|
this.update_in(cx, |this, window, cx| {
|
||||||
|
if let Err(err) = result {
|
||||||
|
if let ThreadState::Unauthenticated {
|
||||||
|
pending_auth_method,
|
||||||
|
..
|
||||||
|
} = &mut this.thread_state
|
||||||
|
{
|
||||||
|
pending_auth_method.take();
|
||||||
|
}
|
||||||
|
this.handle_thread_error(err, cx);
|
||||||
|
} else {
|
||||||
|
this.reset(window, cx);
|
||||||
|
}
|
||||||
|
this.auth_task.take()
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_external_agent_login(
|
fn spawn_external_agent_login(
|
||||||
@@ -1896,10 +1901,11 @@ impl AcpThreadView {
|
|||||||
let Some(thread) = self.thread() else {
|
let Some(thread) = self.thread() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let agent_telemetry_id = thread.read(cx).connection().telemetry_id();
|
||||||
|
|
||||||
telemetry::event!(
|
telemetry::event!(
|
||||||
"Agent Tool Call Authorized",
|
"Agent Tool Call Authorized",
|
||||||
agent = self.agent.telemetry_id(),
|
agent = agent_telemetry_id,
|
||||||
session = thread.read(cx).session_id(),
|
session = thread.read(cx).session_id(),
|
||||||
option = option_kind
|
option = option_kind
|
||||||
);
|
);
|
||||||
@@ -3509,7 +3515,9 @@ impl AcpThreadView {
|
|||||||
(method.id.0.clone(), method.name.clone())
|
(method.id.0.clone(), method.name.clone())
|
||||||
};
|
};
|
||||||
|
|
||||||
Button::new(SharedString::from(method_id.clone()), name)
|
let agent_telemetry_id = connection.telemetry_id();
|
||||||
|
|
||||||
|
Button::new(method_id.clone(), name)
|
||||||
.label_size(LabelSize::Small)
|
.label_size(LabelSize::Small)
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
if ix == 0 {
|
if ix == 0 {
|
||||||
@@ -3528,7 +3536,7 @@ impl AcpThreadView {
|
|||||||
cx.listener(move |this, _, window, cx| {
|
cx.listener(move |this, _, window, cx| {
|
||||||
telemetry::event!(
|
telemetry::event!(
|
||||||
"Authenticate Agent Started",
|
"Authenticate Agent Started",
|
||||||
agent = this.agent.telemetry_id(),
|
agent = agent_telemetry_id,
|
||||||
method = method_id
|
method = method_id
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -5376,47 +5384,39 @@ impl AcpThreadView {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_codex_windows_warning(&self, cx: &mut Context<Self>) -> Option<Callout> {
|
fn render_codex_windows_warning(&self, cx: &mut Context<Self>) -> Callout {
|
||||||
if self.show_codex_windows_warning {
|
Callout::new()
|
||||||
Some(
|
.icon(IconName::Warning)
|
||||||
Callout::new()
|
.severity(Severity::Warning)
|
||||||
.icon(IconName::Warning)
|
.title("Codex on Windows")
|
||||||
.severity(Severity::Warning)
|
.description("For best performance, run Codex in Windows Subsystem for Linux (WSL2)")
|
||||||
.title("Codex on Windows")
|
.actions_slot(
|
||||||
.description(
|
Button::new("open-wsl-modal", "Open in WSL")
|
||||||
"For best performance, run Codex in Windows Subsystem for Linux (WSL2)",
|
.icon_size(IconSize::Small)
|
||||||
)
|
.icon_color(Color::Muted)
|
||||||
.actions_slot(
|
.on_click(cx.listener({
|
||||||
Button::new("open-wsl-modal", "Open in WSL")
|
move |_, _, _window, cx| {
|
||||||
.icon_size(IconSize::Small)
|
#[cfg(windows)]
|
||||||
.icon_color(Color::Muted)
|
_window.dispatch_action(
|
||||||
.on_click(cx.listener({
|
zed_actions::wsl_actions::OpenWsl::default().boxed_clone(),
|
||||||
move |_, _, _window, cx| {
|
cx,
|
||||||
#[cfg(windows)]
|
);
|
||||||
_window.dispatch_action(
|
cx.notify();
|
||||||
zed_actions::wsl_actions::OpenWsl::default().boxed_clone(),
|
}
|
||||||
cx,
|
})),
|
||||||
);
|
)
|
||||||
cx.notify();
|
.dismiss_action(
|
||||||
}
|
IconButton::new("dismiss", IconName::Close)
|
||||||
})),
|
.icon_size(IconSize::Small)
|
||||||
)
|
.icon_color(Color::Muted)
|
||||||
.dismiss_action(
|
.tooltip(Tooltip::text("Dismiss Warning"))
|
||||||
IconButton::new("dismiss", IconName::Close)
|
.on_click(cx.listener({
|
||||||
.icon_size(IconSize::Small)
|
move |this, _, _, cx| {
|
||||||
.icon_color(Color::Muted)
|
this.show_codex_windows_warning = false;
|
||||||
.tooltip(Tooltip::text("Dismiss Warning"))
|
cx.notify();
|
||||||
.on_click(cx.listener({
|
}
|
||||||
move |this, _, _, cx| {
|
})),
|
||||||
this.show_codex_windows_warning = false;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_thread_error(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
|
fn render_thread_error(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
|
||||||
@@ -5936,12 +5936,8 @@ impl Render for AcpThreadView {
|
|||||||
_ => this,
|
_ => this,
|
||||||
})
|
})
|
||||||
.children(self.render_thread_retry_status_callout(window, cx))
|
.children(self.render_thread_retry_status_callout(window, cx))
|
||||||
.children({
|
.when(self.show_codex_windows_warning, |this| {
|
||||||
if cfg!(windows) && self.project.read(cx).is_local() {
|
this.child(self.render_codex_windows_warning(cx))
|
||||||
self.render_codex_windows_warning(cx)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.children(self.render_thread_error(window, cx))
|
.children(self.render_thread_error(window, cx))
|
||||||
.when_some(
|
.when_some(
|
||||||
@@ -6398,6 +6394,7 @@ pub(crate) mod tests {
|
|||||||
project,
|
project,
|
||||||
history_store,
|
history_store,
|
||||||
None,
|
None,
|
||||||
|
false,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -6475,10 +6472,6 @@ pub(crate) mod tests {
|
|||||||
where
|
where
|
||||||
C: 'static + AgentConnection + Send + Clone,
|
C: 'static + AgentConnection + Send + Clone,
|
||||||
{
|
{
|
||||||
fn telemetry_id(&self) -> &'static str {
|
|
||||||
"test"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn logo(&self) -> ui::IconName {
|
fn logo(&self) -> ui::IconName {
|
||||||
ui::IconName::Ai
|
ui::IconName::Ai
|
||||||
}
|
}
|
||||||
@@ -6505,8 +6498,8 @@ pub(crate) mod tests {
|
|||||||
struct SaboteurAgentConnection;
|
struct SaboteurAgentConnection;
|
||||||
|
|
||||||
impl AgentConnection for SaboteurAgentConnection {
|
impl AgentConnection for SaboteurAgentConnection {
|
||||||
fn telemetry_id(&self) -> &'static str {
|
fn telemetry_id(&self) -> SharedString {
|
||||||
"saboteur"
|
"saboteur".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_thread(
|
fn new_thread(
|
||||||
@@ -6569,8 +6562,8 @@ pub(crate) mod tests {
|
|||||||
struct RefusalAgentConnection;
|
struct RefusalAgentConnection;
|
||||||
|
|
||||||
impl AgentConnection for RefusalAgentConnection {
|
impl AgentConnection for RefusalAgentConnection {
|
||||||
fn telemetry_id(&self) -> &'static str {
|
fn telemetry_id(&self) -> SharedString {
|
||||||
"refusal"
|
"refusal".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_thread(
|
fn new_thread(
|
||||||
@@ -6671,6 +6664,7 @@ pub(crate) mod tests {
|
|||||||
project.clone(),
|
project.clone(),
|
||||||
history_store.clone(),
|
history_store.clone(),
|
||||||
None,
|
None,
|
||||||
|
false,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -838,7 +838,7 @@ impl AgentConfiguration {
|
|||||||
.min_w_0()
|
.min_w_0()
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(SharedString::from(format!("tooltip-{}", item_id)))
|
.id(format!("tooltip-{}", item_id))
|
||||||
.h_full()
|
.h_full()
|
||||||
.w_3()
|
.w_3()
|
||||||
.mr_2()
|
.mr_2()
|
||||||
@@ -977,7 +977,10 @@ impl AgentConfiguration {
|
|||||||
} else {
|
} else {
|
||||||
AgentIcon::Name(IconName::Ai)
|
AgentIcon::Name(IconName::Ai)
|
||||||
};
|
};
|
||||||
(name, icon)
|
let display_name = agent_server_store
|
||||||
|
.agent_display_name(&name)
|
||||||
|
.unwrap_or_else(|| name.0.clone());
|
||||||
|
(name, icon, display_name)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -1084,6 +1087,7 @@ impl AgentConfiguration {
|
|||||||
.child(self.render_agent_server(
|
.child(self.render_agent_server(
|
||||||
AgentIcon::Name(IconName::AiClaude),
|
AgentIcon::Name(IconName::AiClaude),
|
||||||
"Claude Code",
|
"Claude Code",
|
||||||
|
"Claude Code",
|
||||||
false,
|
false,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
@@ -1091,6 +1095,7 @@ impl AgentConfiguration {
|
|||||||
.child(self.render_agent_server(
|
.child(self.render_agent_server(
|
||||||
AgentIcon::Name(IconName::AiOpenAi),
|
AgentIcon::Name(IconName::AiOpenAi),
|
||||||
"Codex CLI",
|
"Codex CLI",
|
||||||
|
"Codex CLI",
|
||||||
false,
|
false,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
@@ -1098,16 +1103,23 @@ impl AgentConfiguration {
|
|||||||
.child(self.render_agent_server(
|
.child(self.render_agent_server(
|
||||||
AgentIcon::Name(IconName::AiGemini),
|
AgentIcon::Name(IconName::AiGemini),
|
||||||
"Gemini CLI",
|
"Gemini CLI",
|
||||||
|
"Gemini CLI",
|
||||||
false,
|
false,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
.map(|mut parent| {
|
.map(|mut parent| {
|
||||||
for (name, icon) in user_defined_agents {
|
for (name, icon, display_name) in user_defined_agents {
|
||||||
parent = parent
|
parent = parent
|
||||||
.child(
|
.child(
|
||||||
Divider::horizontal().color(DividerColor::BorderFaded),
|
Divider::horizontal().color(DividerColor::BorderFaded),
|
||||||
)
|
)
|
||||||
.child(self.render_agent_server(icon, name, true, cx));
|
.child(self.render_agent_server(
|
||||||
|
icon,
|
||||||
|
name,
|
||||||
|
display_name,
|
||||||
|
true,
|
||||||
|
cx,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
parent
|
parent
|
||||||
}),
|
}),
|
||||||
@@ -1118,11 +1130,13 @@ impl AgentConfiguration {
|
|||||||
fn render_agent_server(
|
fn render_agent_server(
|
||||||
&self,
|
&self,
|
||||||
icon: AgentIcon,
|
icon: AgentIcon,
|
||||||
name: impl Into<SharedString>,
|
id: impl Into<SharedString>,
|
||||||
|
display_name: impl Into<SharedString>,
|
||||||
external: bool,
|
external: bool,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let name = name.into();
|
let id = id.into();
|
||||||
|
let display_name = display_name.into();
|
||||||
let icon = match icon {
|
let icon = match icon {
|
||||||
AgentIcon::Name(icon_name) => Icon::new(icon_name)
|
AgentIcon::Name(icon_name) => Icon::new(icon_name)
|
||||||
.size(IconSize::Small)
|
.size(IconSize::Small)
|
||||||
@@ -1132,12 +1146,15 @@ impl AgentConfiguration {
|
|||||||
.color(Color::Muted),
|
.color(Color::Muted),
|
||||||
};
|
};
|
||||||
|
|
||||||
let tooltip_id = SharedString::new(format!("agent-source-{}", name));
|
let tooltip_id = SharedString::new(format!("agent-source-{}", id));
|
||||||
let tooltip_message = format!("The {} agent was installed from an extension.", name);
|
let tooltip_message = format!(
|
||||||
|
"The {} agent was installed from an extension.",
|
||||||
|
display_name
|
||||||
|
);
|
||||||
|
|
||||||
let agent_server_name = ExternalAgentServerName(name.clone());
|
let agent_server_name = ExternalAgentServerName(id.clone());
|
||||||
|
|
||||||
let uninstall_btn_id = SharedString::from(format!("uninstall-{}", name));
|
let uninstall_btn_id = SharedString::from(format!("uninstall-{}", id));
|
||||||
let uninstall_button = IconButton::new(uninstall_btn_id, IconName::Trash)
|
let uninstall_button = IconButton::new(uninstall_btn_id, IconName::Trash)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
@@ -1161,7 +1178,7 @@ impl AgentConfiguration {
|
|||||||
h_flex()
|
h_flex()
|
||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
.child(icon)
|
.child(icon)
|
||||||
.child(Label::new(name))
|
.child(Label::new(display_name))
|
||||||
.when(external, |this| {
|
.when(external, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
div()
|
div()
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ impl ConfigureContextServerToolsModal {
|
|||||||
v_flex()
|
v_flex()
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(SharedString::from(format!("tool-header-{}", index)))
|
.id(format!("tool-header-{}", index))
|
||||||
.py_1()
|
.py_1()
|
||||||
.pl_1()
|
.pl_1()
|
||||||
.pr_2()
|
.pr_2()
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ impl ManageProfilesModal {
|
|||||||
let is_focused = profile.navigation.focus_handle.contains_focused(window, cx);
|
let is_focused = profile.navigation.focus_handle.contains_focused(window, cx);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id(SharedString::from(format!("profile-{}", profile.id)))
|
.id(format!("profile-{}", profile.id))
|
||||||
.track_focus(&profile.navigation.focus_handle)
|
.track_focus(&profile.navigation.focus_handle)
|
||||||
.on_action({
|
.on_action({
|
||||||
let profile_id = profile.id.clone();
|
let profile_id = profile.id.clone();
|
||||||
@@ -431,7 +431,7 @@ impl ManageProfilesModal {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
ListItem::new(SharedString::from(format!("profile-{}", profile.id)))
|
ListItem::new(format!("profile-{}", profile.id))
|
||||||
.toggle_state(is_focused)
|
.toggle_state(is_focused)
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ impl AgentModelSelector {
|
|||||||
pub fn toggle(&self, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn toggle(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.menu_handle.toggle(window, cx);
|
self.menu_handle.toggle(window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn active_model(&self, cx: &App) -> Option<language_model::ConfiguredModel> {
|
||||||
|
self.selector.read(cx).delegate.active_model(cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for AgentModelSelector {
|
impl Render for AgentModelSelector {
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ impl ActiveView {
|
|||||||
project,
|
project,
|
||||||
history_store,
|
history_store,
|
||||||
prompt_store,
|
prompt_store,
|
||||||
|
false,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -885,10 +886,6 @@ impl AgentPanel {
|
|||||||
|
|
||||||
let server = ext_agent.server(fs, history);
|
let server = ext_agent.server(fs, history);
|
||||||
|
|
||||||
if !loading {
|
|
||||||
telemetry::event!("Agent Thread Started", agent = server.telemetry_id());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
let selected_agent = ext_agent.into();
|
let selected_agent = ext_agent.into();
|
||||||
if this.selected_agent != selected_agent {
|
if this.selected_agent != selected_agent {
|
||||||
@@ -905,6 +902,7 @@ impl AgentPanel {
|
|||||||
project,
|
project,
|
||||||
this.history_store.clone(),
|
this.history_store.clone(),
|
||||||
this.prompt_store.clone(),
|
this.prompt_store.clone(),
|
||||||
|
!loading,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -2083,8 +2081,11 @@ impl AgentPanel {
|
|||||||
|
|
||||||
for agent_name in agent_names {
|
for agent_name in agent_names {
|
||||||
let icon_path = agent_server_store.agent_icon(&agent_name);
|
let icon_path = agent_server_store.agent_icon(&agent_name);
|
||||||
|
let display_name = agent_server_store
|
||||||
|
.agent_display_name(&agent_name)
|
||||||
|
.unwrap_or_else(|| agent_name.0.clone());
|
||||||
|
|
||||||
let mut entry = ContextMenuEntry::new(agent_name.clone());
|
let mut entry = ContextMenuEntry::new(display_name);
|
||||||
|
|
||||||
if let Some(icon_path) = icon_path {
|
if let Some(icon_path) = icon_path {
|
||||||
entry = entry.custom_icon_svg(icon_path);
|
entry = entry.custom_icon_svg(icon_path);
|
||||||
|
|||||||
@@ -160,16 +160,6 @@ pub enum ExternalAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ExternalAgent {
|
impl ExternalAgent {
|
||||||
pub fn parse_built_in(server: &dyn agent_servers::AgentServer) -> Option<Self> {
|
|
||||||
match server.telemetry_id() {
|
|
||||||
"gemini-cli" => Some(Self::Gemini),
|
|
||||||
"claude-code" => Some(Self::ClaudeCode),
|
|
||||||
"codex" => Some(Self::Codex),
|
|
||||||
"zed" => Some(Self::NativeAgent),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn server(
|
pub fn server(
|
||||||
&self,
|
&self,
|
||||||
fs: Arc<dyn fs::Fs>,
|
fs: Arc<dyn fs::Fs>,
|
||||||
|
|||||||
@@ -119,6 +119,10 @@ impl BufferCodegen {
|
|||||||
.push(cx.subscribe(&codegen, |_, _, event, cx| cx.emit(*event)));
|
.push(cx.subscribe(&codegen, |_, _, event, cx| cx.emit(*event)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn active_completion(&self, cx: &App) -> Option<String> {
|
||||||
|
self.active_alternative().read(cx).current_completion()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn active_alternative(&self) -> &Entity<CodegenAlternative> {
|
pub fn active_alternative(&self) -> &Entity<CodegenAlternative> {
|
||||||
&self.alternatives[self.active_alternative]
|
&self.alternatives[self.active_alternative]
|
||||||
}
|
}
|
||||||
@@ -241,6 +245,10 @@ impl BufferCodegen {
|
|||||||
pub fn last_equal_ranges<'a>(&self, cx: &'a App) -> &'a [Range<Anchor>] {
|
pub fn last_equal_ranges<'a>(&self, cx: &'a App) -> &'a [Range<Anchor>] {
|
||||||
self.active_alternative().read(cx).last_equal_ranges()
|
self.active_alternative().read(cx).last_equal_ranges()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn selected_text<'a>(&self, cx: &'a App) -> Option<&'a str> {
|
||||||
|
self.active_alternative().read(cx).selected_text()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<CodegenEvent> for BufferCodegen {}
|
impl EventEmitter<CodegenEvent> for BufferCodegen {}
|
||||||
@@ -264,6 +272,7 @@ pub struct CodegenAlternative {
|
|||||||
line_operations: Vec<LineOperation>,
|
line_operations: Vec<LineOperation>,
|
||||||
elapsed_time: Option<f64>,
|
elapsed_time: Option<f64>,
|
||||||
completion: Option<String>,
|
completion: Option<String>,
|
||||||
|
selected_text: Option<String>,
|
||||||
pub message_id: Option<String>,
|
pub message_id: Option<String>,
|
||||||
pub model_explanation: Option<SharedString>,
|
pub model_explanation: Option<SharedString>,
|
||||||
}
|
}
|
||||||
@@ -323,6 +332,7 @@ impl CodegenAlternative {
|
|||||||
range,
|
range,
|
||||||
elapsed_time: None,
|
elapsed_time: None,
|
||||||
completion: None,
|
completion: None,
|
||||||
|
selected_text: None,
|
||||||
model_explanation: None,
|
model_explanation: None,
|
||||||
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
|
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
|
||||||
}
|
}
|
||||||
@@ -608,6 +618,8 @@ impl CodegenAlternative {
|
|||||||
.text_for_range(self.range.start..self.range.end)
|
.text_for_range(self.range.start..self.range.end)
|
||||||
.collect::<Rope>();
|
.collect::<Rope>();
|
||||||
|
|
||||||
|
self.selected_text = Some(selected_text.to_string());
|
||||||
|
|
||||||
let selection_start = self.range.start.to_point(&snapshot);
|
let selection_start = self.range.start.to_point(&snapshot);
|
||||||
|
|
||||||
// Start with the indentation of the first line in the selection
|
// Start with the indentation of the first line in the selection
|
||||||
@@ -868,6 +880,14 @@ impl CodegenAlternative {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn current_completion(&self) -> Option<String> {
|
||||||
|
self.completion.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selected_text(&self) -> Option<&str> {
|
||||||
|
self.selected_text.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stop(&mut self, cx: &mut Context<Self>) {
|
pub fn stop(&mut self, cx: &mut Context<Self>) {
|
||||||
self.last_equal_ranges.clear();
|
self.last_equal_ranges.clear();
|
||||||
if self.diff.is_empty() {
|
if self.diff.is_empty() {
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ use editor::{
|
|||||||
ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
|
ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
|
||||||
actions::{MoveDown, MoveUp},
|
actions::{MoveDown, MoveUp},
|
||||||
};
|
};
|
||||||
|
use feature_flags::{FeatureFlag, FeatureFlagAppExt};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, Context, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
|
AnyElement, App, ClipboardItem, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
TextStyle, TextStyleRefinement, WeakEntity, Window,
|
Subscription, TextStyle, TextStyleRefinement, WeakEntity, Window, actions,
|
||||||
};
|
};
|
||||||
use language_model::{LanguageModel, LanguageModelRegistry};
|
use language_model::{LanguageModel, LanguageModelRegistry};
|
||||||
use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle};
|
use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle};
|
||||||
@@ -19,14 +20,16 @@ use parking_lot::Mutex;
|
|||||||
use project::Project;
|
use project::Project;
|
||||||
use prompt_store::PromptStore;
|
use prompt_store::PromptStore;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::cmp;
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::{cmp, mem};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::utils::WithRemSize;
|
use ui::utils::WithRemSize;
|
||||||
use ui::{IconButtonShape, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};
|
use ui::{IconButtonShape, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};
|
||||||
use workspace::Workspace;
|
use uuid::Uuid;
|
||||||
|
use workspace::notifications::NotificationId;
|
||||||
|
use workspace::{Toast, Workspace};
|
||||||
use zed_actions::agent::ToggleModelSelector;
|
use zed_actions::agent::ToggleModelSelector;
|
||||||
|
|
||||||
use crate::agent_model_selector::AgentModelSelector;
|
use crate::agent_model_selector::AgentModelSelector;
|
||||||
@@ -39,6 +42,58 @@ use crate::mention_set::{MentionSet, crease_for_mention};
|
|||||||
use crate::terminal_codegen::TerminalCodegen;
|
use crate::terminal_codegen::TerminalCodegen;
|
||||||
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist, ModelUsageContext};
|
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist, ModelUsageContext};
|
||||||
|
|
||||||
|
actions!(inline_assistant, [ThumbsUpResult, ThumbsDownResult]);
|
||||||
|
|
||||||
|
pub struct InlineAssistRatingFeatureFlag;
|
||||||
|
|
||||||
|
impl FeatureFlag for InlineAssistRatingFeatureFlag {
|
||||||
|
const NAME: &'static str = "inline-assist-rating";
|
||||||
|
|
||||||
|
fn enabled_for_staff() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RatingState {
|
||||||
|
Pending,
|
||||||
|
GeneratedCompletion(Option<String>),
|
||||||
|
Rated(Uuid),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RatingState {
|
||||||
|
fn is_pending(&self) -> bool {
|
||||||
|
matches!(self, RatingState::Pending)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rating_id(&self) -> Option<Uuid> {
|
||||||
|
match self {
|
||||||
|
RatingState::Pending => None,
|
||||||
|
RatingState::GeneratedCompletion(_) => None,
|
||||||
|
RatingState::Rated(id) => Some(*id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rate(&mut self) -> (Uuid, Option<String>) {
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
let old_state = mem::replace(self, RatingState::Rated(id));
|
||||||
|
let completion = match old_state {
|
||||||
|
RatingState::Pending => None,
|
||||||
|
RatingState::GeneratedCompletion(completion) => completion,
|
||||||
|
RatingState::Rated(_) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
(id, completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
*self = RatingState::Pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generated_completion(&mut self, generated_completion: Option<String>) {
|
||||||
|
*self = RatingState::GeneratedCompletion(generated_completion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct PromptEditor<T> {
|
pub struct PromptEditor<T> {
|
||||||
pub editor: Entity<Editor>,
|
pub editor: Entity<Editor>,
|
||||||
mode: PromptEditorMode,
|
mode: PromptEditorMode,
|
||||||
@@ -54,6 +109,7 @@ pub struct PromptEditor<T> {
|
|||||||
_codegen_subscription: Subscription,
|
_codegen_subscription: Subscription,
|
||||||
editor_subscriptions: Vec<Subscription>,
|
editor_subscriptions: Vec<Subscription>,
|
||||||
show_rate_limit_notice: bool,
|
show_rate_limit_notice: bool,
|
||||||
|
rated: RatingState,
|
||||||
_phantom: std::marker::PhantomData<T>,
|
_phantom: std::marker::PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +209,8 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||||||
.on_action(cx.listener(Self::cancel))
|
.on_action(cx.listener(Self::cancel))
|
||||||
.on_action(cx.listener(Self::move_up))
|
.on_action(cx.listener(Self::move_up))
|
||||||
.on_action(cx.listener(Self::move_down))
|
.on_action(cx.listener(Self::move_down))
|
||||||
|
.on_action(cx.listener(Self::thumbs_up))
|
||||||
|
.on_action(cx.listener(Self::thumbs_down))
|
||||||
.capture_action(cx.listener(Self::cycle_prev))
|
.capture_action(cx.listener(Self::cycle_prev))
|
||||||
.capture_action(cx.listener(Self::cycle_next))
|
.capture_action(cx.listener(Self::cycle_next))
|
||||||
.child(
|
.child(
|
||||||
@@ -429,6 +487,7 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.edited_since_done = true;
|
self.edited_since_done = true;
|
||||||
|
self.rated.reset();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
EditorEvent::Blurred => {
|
EditorEvent::Blurred => {
|
||||||
@@ -516,6 +575,121 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn thumbs_up(&mut self, _: &ThumbsUpResult, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if self.rated.is_pending() {
|
||||||
|
self.toast("Still generating...", None, cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rating_id) = self.rated.rating_id() {
|
||||||
|
self.toast("Already rated this completion", Some(rating_id), cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (rating_id, completion) = self.rated.rate();
|
||||||
|
|
||||||
|
let selected_text = match &self.mode {
|
||||||
|
PromptEditorMode::Buffer { codegen, .. } => {
|
||||||
|
codegen.read(cx).selected_text(cx).map(|s| s.to_string())
|
||||||
|
}
|
||||||
|
PromptEditorMode::Terminal { .. } => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let model_info = self.model_selector.read(cx).active_model(cx);
|
||||||
|
let model_id = {
|
||||||
|
let Some(configured_model) = model_info else {
|
||||||
|
self.toast("No configured model", None, cx);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
configured_model.model.telemetry_id()
|
||||||
|
};
|
||||||
|
|
||||||
|
let prompt = self.editor.read(cx).text(cx);
|
||||||
|
|
||||||
|
telemetry::event!(
|
||||||
|
"Inline Assistant Rated",
|
||||||
|
rating = "positive",
|
||||||
|
model = model_id,
|
||||||
|
prompt = prompt,
|
||||||
|
completion = completion,
|
||||||
|
selected_text = selected_text,
|
||||||
|
rating_id = rating_id.to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thumbs_down(&mut self, _: &ThumbsDownResult, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if self.rated.is_pending() {
|
||||||
|
self.toast("Still generating...", None, cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(rating_id) = self.rated.rating_id() {
|
||||||
|
self.toast("Already rated this completion", Some(rating_id), cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (rating_id, completion) = self.rated.rate();
|
||||||
|
|
||||||
|
let selected_text = match &self.mode {
|
||||||
|
PromptEditorMode::Buffer { codegen, .. } => {
|
||||||
|
codegen.read(cx).selected_text(cx).map(|s| s.to_string())
|
||||||
|
}
|
||||||
|
PromptEditorMode::Terminal { .. } => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let model_info = self.model_selector.read(cx).active_model(cx);
|
||||||
|
let model_telemetry_id = {
|
||||||
|
let Some(configured_model) = model_info else {
|
||||||
|
self.toast("No configured model", None, cx);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
configured_model.model.telemetry_id()
|
||||||
|
};
|
||||||
|
|
||||||
|
let prompt = self.editor.read(cx).text(cx);
|
||||||
|
|
||||||
|
telemetry::event!(
|
||||||
|
"Inline Assistant Rated",
|
||||||
|
rating = "negative",
|
||||||
|
model = model_telemetry_id,
|
||||||
|
prompt = prompt,
|
||||||
|
completion = completion,
|
||||||
|
selected_text = selected_text,
|
||||||
|
rating_id = rating_id.to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toast(&mut self, msg: &str, uuid: Option<Uuid>, cx: &mut Context<'_, PromptEditor<T>>) {
|
||||||
|
self.workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
enum InlinePromptRating {}
|
||||||
|
workspace.show_toast(
|
||||||
|
{
|
||||||
|
let mut toast = Toast::new(
|
||||||
|
NotificationId::unique::<InlinePromptRating>(),
|
||||||
|
msg.to_string(),
|
||||||
|
)
|
||||||
|
.autohide();
|
||||||
|
|
||||||
|
if let Some(uuid) = uuid {
|
||||||
|
toast = toast.on_click("Click to copy rating ID", move |_, cx| {
|
||||||
|
cx.write_to_clipboard(ClipboardItem::new_string(uuid.to_string()));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
toast
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
|
fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if let Some(ix) = self.prompt_history_ix {
|
if let Some(ix) = self.prompt_history_ix {
|
||||||
if ix > 0 {
|
if ix > 0 {
|
||||||
@@ -621,6 +795,9 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
|
let show_rating_buttons = cx.has_flag::<InlineAssistRatingFeatureFlag>();
|
||||||
|
let rated = self.rated.rating_id().is_some();
|
||||||
|
|
||||||
let accept = IconButton::new("accept", IconName::Check)
|
let accept = IconButton::new("accept", IconName::Check)
|
||||||
.icon_color(Color::Info)
|
.icon_color(Color::Info)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
@@ -632,25 +809,59 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
}))
|
}))
|
||||||
.into_any_element();
|
.into_any_element();
|
||||||
|
|
||||||
match &self.mode {
|
let mut buttons = Vec::new();
|
||||||
PromptEditorMode::Terminal { .. } => vec![
|
|
||||||
accept,
|
if show_rating_buttons {
|
||||||
IconButton::new("confirm", IconName::PlayFilled)
|
buttons.push(
|
||||||
.icon_color(Color::Info)
|
IconButton::new("thumbs-down", IconName::ThumbsDown)
|
||||||
|
.icon_color(if rated { Color::Muted } else { Color::Default })
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(|_window, cx| {
|
.disabled(rated)
|
||||||
Tooltip::for_action(
|
.tooltip(Tooltip::text("Bad result"))
|
||||||
"Execute Generated Command",
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
&menu::SecondaryConfirm,
|
this.thumbs_down(&ThumbsDownResult, window, cx);
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.on_click(cx.listener(|_, _, _, cx| {
|
|
||||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
|
|
||||||
}))
|
}))
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
],
|
);
|
||||||
PromptEditorMode::Buffer { .. } => vec![accept],
|
|
||||||
|
buttons.push(
|
||||||
|
IconButton::new("thumbs-up", IconName::ThumbsUp)
|
||||||
|
.icon_color(if rated { Color::Muted } else { Color::Default })
|
||||||
|
.shape(IconButtonShape::Square)
|
||||||
|
.disabled(rated)
|
||||||
|
.tooltip(Tooltip::text("Good result"))
|
||||||
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
|
this.thumbs_up(&ThumbsUpResult, window, cx);
|
||||||
|
}))
|
||||||
|
.into_any_element(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.push(accept);
|
||||||
|
|
||||||
|
match &self.mode {
|
||||||
|
PromptEditorMode::Terminal { .. } => {
|
||||||
|
buttons.push(
|
||||||
|
IconButton::new("confirm", IconName::PlayFilled)
|
||||||
|
.icon_color(Color::Info)
|
||||||
|
.shape(IconButtonShape::Square)
|
||||||
|
.tooltip(|_window, cx| {
|
||||||
|
Tooltip::for_action(
|
||||||
|
"Execute Generated Command",
|
||||||
|
&menu::SecondaryConfirm,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.on_click(cx.listener(|_, _, _, cx| {
|
||||||
|
cx.emit(PromptEditorEvent::ConfirmRequested {
|
||||||
|
execute: true,
|
||||||
|
});
|
||||||
|
}))
|
||||||
|
.into_any_element(),
|
||||||
|
);
|
||||||
|
buttons
|
||||||
|
}
|
||||||
|
PromptEditorMode::Buffer { .. } => buttons,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -979,6 +1190,7 @@ impl PromptEditor<BufferCodegen> {
|
|||||||
editor_subscriptions: Vec::new(),
|
editor_subscriptions: Vec::new(),
|
||||||
show_rate_limit_notice: false,
|
show_rate_limit_notice: false,
|
||||||
mode,
|
mode,
|
||||||
|
rated: RatingState::Pending,
|
||||||
_phantom: Default::default(),
|
_phantom: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -989,7 +1201,7 @@ impl PromptEditor<BufferCodegen> {
|
|||||||
|
|
||||||
fn handle_codegen_changed(
|
fn handle_codegen_changed(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Entity<BufferCodegen>,
|
codegen: Entity<BufferCodegen>,
|
||||||
cx: &mut Context<PromptEditor<BufferCodegen>>,
|
cx: &mut Context<PromptEditor<BufferCodegen>>,
|
||||||
) {
|
) {
|
||||||
match self.codegen_status(cx) {
|
match self.codegen_status(cx) {
|
||||||
@@ -998,10 +1210,13 @@ impl PromptEditor<BufferCodegen> {
|
|||||||
.update(cx, |editor, _| editor.set_read_only(false));
|
.update(cx, |editor, _| editor.set_read_only(false));
|
||||||
}
|
}
|
||||||
CodegenStatus::Pending => {
|
CodegenStatus::Pending => {
|
||||||
|
self.rated.reset();
|
||||||
self.editor
|
self.editor
|
||||||
.update(cx, |editor, _| editor.set_read_only(true));
|
.update(cx, |editor, _| editor.set_read_only(true));
|
||||||
}
|
}
|
||||||
CodegenStatus::Done => {
|
CodegenStatus::Done => {
|
||||||
|
let completion = codegen.read(cx).active_completion(cx);
|
||||||
|
self.rated.generated_completion(completion);
|
||||||
self.edited_since_done = false;
|
self.edited_since_done = false;
|
||||||
self.editor
|
self.editor
|
||||||
.update(cx, |editor, _| editor.set_read_only(false));
|
.update(cx, |editor, _| editor.set_read_only(false));
|
||||||
@@ -1122,6 +1337,7 @@ impl PromptEditor<TerminalCodegen> {
|
|||||||
editor_subscriptions: Vec::new(),
|
editor_subscriptions: Vec::new(),
|
||||||
mode,
|
mode,
|
||||||
show_rate_limit_notice: false,
|
show_rate_limit_notice: false,
|
||||||
|
rated: RatingState::Pending,
|
||||||
_phantom: Default::default(),
|
_phantom: Default::default(),
|
||||||
};
|
};
|
||||||
this.count_lines(cx);
|
this.count_lines(cx);
|
||||||
@@ -1154,17 +1370,20 @@ impl PromptEditor<TerminalCodegen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_codegen_changed(&mut self, _: Entity<TerminalCodegen>, cx: &mut Context<Self>) {
|
fn handle_codegen_changed(&mut self, codegen: Entity<TerminalCodegen>, cx: &mut Context<Self>) {
|
||||||
match &self.codegen().read(cx).status {
|
match &self.codegen().read(cx).status {
|
||||||
CodegenStatus::Idle => {
|
CodegenStatus::Idle => {
|
||||||
self.editor
|
self.editor
|
||||||
.update(cx, |editor, _| editor.set_read_only(false));
|
.update(cx, |editor, _| editor.set_read_only(false));
|
||||||
}
|
}
|
||||||
CodegenStatus::Pending => {
|
CodegenStatus::Pending => {
|
||||||
|
self.rated = RatingState::Pending;
|
||||||
self.editor
|
self.editor
|
||||||
.update(cx, |editor, _| editor.set_read_only(true));
|
.update(cx, |editor, _| editor.set_read_only(true));
|
||||||
}
|
}
|
||||||
CodegenStatus::Done | CodegenStatus::Error(_) => {
|
CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||||
|
self.rated
|
||||||
|
.generated_completion(codegen.read(cx).completion());
|
||||||
self.edited_since_done = false;
|
self.edited_since_done = false;
|
||||||
self.editor
|
self.editor
|
||||||
.update(cx, |editor, _| editor.set_read_only(false));
|
.update(cx, |editor, _| editor.set_read_only(false));
|
||||||
|
|||||||
@@ -542,7 +542,7 @@ impl PickerDelegate for ProfilePickerDelegate {
|
|||||||
let is_active = active_id == candidate.id;
|
let is_active = active_id == candidate.id;
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
ListItem::new(SharedString::from(candidate.id.0.clone()))
|
ListItem::new(candidate.id.0.clone())
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.toggle_state(selected)
|
.toggle_state(selected)
|
||||||
|
|||||||
@@ -135,6 +135,12 @@ impl TerminalCodegen {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn completion(&self) -> Option<String> {
|
||||||
|
self.transaction
|
||||||
|
.as_ref()
|
||||||
|
.map(|transaction| transaction.completion.clone())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stop(&mut self, cx: &mut Context<Self>) {
|
pub fn stop(&mut self, cx: &mut Context<Self>) {
|
||||||
self.status = CodegenStatus::Done;
|
self.status = CodegenStatus::Done;
|
||||||
self.generation = Task::ready(());
|
self.generation = Task::ready(());
|
||||||
@@ -167,27 +173,32 @@ pub const CLEAR_INPUT: &str = "\x03";
|
|||||||
const CARRIAGE_RETURN: &str = "\x0d";
|
const CARRIAGE_RETURN: &str = "\x0d";
|
||||||
|
|
||||||
struct TerminalTransaction {
|
struct TerminalTransaction {
|
||||||
|
completion: String,
|
||||||
terminal: Entity<Terminal>,
|
terminal: Entity<Terminal>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalTransaction {
|
impl TerminalTransaction {
|
||||||
pub fn start(terminal: Entity<Terminal>) -> Self {
|
pub fn start(terminal: Entity<Terminal>) -> Self {
|
||||||
Self { terminal }
|
Self {
|
||||||
|
completion: String::new(),
|
||||||
|
terminal,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, hunk: String, cx: &mut App) {
|
pub fn push(&mut self, hunk: String, cx: &mut App) {
|
||||||
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
|
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
|
||||||
let input = Self::sanitize_input(hunk);
|
let input = Self::sanitize_input(hunk);
|
||||||
|
self.completion.push_str(&input);
|
||||||
self.terminal
|
self.terminal
|
||||||
.update(cx, |terminal, _| terminal.input(input.into_bytes()));
|
.update(cx, |terminal, _| terminal.input(input.into_bytes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn undo(&self, cx: &mut App) {
|
pub fn undo(self, cx: &mut App) {
|
||||||
self.terminal
|
self.terminal
|
||||||
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.as_bytes()));
|
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.as_bytes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn complete(&self, cx: &mut App) {
|
pub fn complete(self, cx: &mut App) {
|
||||||
self.terminal
|
self.terminal
|
||||||
.update(cx, |terminal, _| terminal.input(CARRIAGE_RETURN.as_bytes()));
|
.update(cx, |terminal, _| terminal.input(CARRIAGE_RETURN.as_bytes()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ struct Detect;
|
|||||||
|
|
||||||
trait InstalledApp {
|
trait InstalledApp {
|
||||||
fn zed_version_string(&self) -> String;
|
fn zed_version_string(&self) -> String;
|
||||||
fn launch(&self, ipc_url: String) -> anyhow::Result<()>;
|
fn launch(&self, ipc_url: String, user_data_dir: Option<&str>) -> anyhow::Result<()>;
|
||||||
fn run_foreground(
|
fn run_foreground(
|
||||||
&self,
|
&self,
|
||||||
ipc_url: String,
|
ipc_url: String,
|
||||||
@@ -588,7 +588,7 @@ fn main() -> Result<()> {
|
|||||||
if args.foreground {
|
if args.foreground {
|
||||||
app.run_foreground(url, user_data_dir.as_deref())?;
|
app.run_foreground(url, user_data_dir.as_deref())?;
|
||||||
} else {
|
} else {
|
||||||
app.launch(url)?;
|
app.launch(url, user_data_dir.as_deref())?;
|
||||||
sender.join().unwrap()?;
|
sender.join().unwrap()?;
|
||||||
if let Some(handle) = stdin_pipe_handle {
|
if let Some(handle) = stdin_pipe_handle {
|
||||||
handle.join().unwrap()?;
|
handle.join().unwrap()?;
|
||||||
@@ -709,14 +709,18 @@ mod linux {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
|
fn launch(&self, ipc_url: String, user_data_dir: Option<&str>) -> anyhow::Result<()> {
|
||||||
let sock_path = paths::data_dir().join(format!(
|
let data_dir = user_data_dir
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(|| paths::data_dir().clone());
|
||||||
|
|
||||||
|
let sock_path = data_dir.join(format!(
|
||||||
"zed-{}.sock",
|
"zed-{}.sock",
|
||||||
*release_channel::RELEASE_CHANNEL_NAME
|
*release_channel::RELEASE_CHANNEL_NAME
|
||||||
));
|
));
|
||||||
let sock = UnixDatagram::unbound()?;
|
let sock = UnixDatagram::unbound()?;
|
||||||
if sock.connect(&sock_path).is_err() {
|
if sock.connect(&sock_path).is_err() {
|
||||||
self.boot_background(ipc_url)?;
|
self.boot_background(ipc_url, user_data_dir)?;
|
||||||
} else {
|
} else {
|
||||||
sock.send(ipc_url.as_bytes())?;
|
sock.send(ipc_url.as_bytes())?;
|
||||||
}
|
}
|
||||||
@@ -742,7 +746,11 @@ mod linux {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn boot_background(&self, ipc_url: String) -> anyhow::Result<()> {
|
fn boot_background(
|
||||||
|
&self,
|
||||||
|
ipc_url: String,
|
||||||
|
user_data_dir: Option<&str>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let path = &self.0;
|
let path = &self.0;
|
||||||
|
|
||||||
match fork::fork() {
|
match fork::fork() {
|
||||||
@@ -756,8 +764,13 @@ mod linux {
|
|||||||
if fork::close_fd().is_err() {
|
if fork::close_fd().is_err() {
|
||||||
eprintln!("failed to close_fd: {}", std::io::Error::last_os_error());
|
eprintln!("failed to close_fd: {}", std::io::Error::last_os_error());
|
||||||
}
|
}
|
||||||
let error =
|
let mut args: Vec<OsString> =
|
||||||
exec::execvp(path.clone(), &[path.as_os_str(), &OsString::from(ipc_url)]);
|
vec![path.as_os_str().to_owned(), OsString::from(ipc_url)];
|
||||||
|
if let Some(dir) = user_data_dir {
|
||||||
|
args.push(OsString::from("--user-data-dir"));
|
||||||
|
args.push(OsString::from(dir));
|
||||||
|
}
|
||||||
|
let error = exec::execvp(path.clone(), &args);
|
||||||
// if exec succeeded, we never get here.
|
// if exec succeeded, we never get here.
|
||||||
eprintln!("failed to exec {:?}: {}", path, error);
|
eprintln!("failed to exec {:?}: {}", path, error);
|
||||||
process::exit(1)
|
process::exit(1)
|
||||||
@@ -943,11 +956,14 @@ mod windows {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
|
fn launch(&self, ipc_url: String, user_data_dir: Option<&str>) -> anyhow::Result<()> {
|
||||||
if check_single_instance() {
|
if check_single_instance() {
|
||||||
std::process::Command::new(self.0.clone())
|
let mut cmd = std::process::Command::new(self.0.clone());
|
||||||
.arg(ipc_url)
|
cmd.arg(ipc_url);
|
||||||
.spawn()?;
|
if let Some(dir) = user_data_dir {
|
||||||
|
cmd.arg("--user-data-dir").arg(dir);
|
||||||
|
}
|
||||||
|
cmd.spawn()?;
|
||||||
} else {
|
} else {
|
||||||
unsafe {
|
unsafe {
|
||||||
let pipe = CreateFileW(
|
let pipe = CreateFileW(
|
||||||
@@ -1096,7 +1112,7 @@ mod mac_os {
|
|||||||
format!("Zed {} – {}", self.version(), self.path().display(),)
|
format!("Zed {} – {}", self.version(), self.path().display(),)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn launch(&self, url: String) -> anyhow::Result<()> {
|
fn launch(&self, url: String, user_data_dir: Option<&str>) -> anyhow::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Self::App { app_bundle, .. } => {
|
Self::App { app_bundle, .. } => {
|
||||||
let app_path = app_bundle;
|
let app_path = app_bundle;
|
||||||
@@ -1146,8 +1162,11 @@ mod mac_os {
|
|||||||
format!("Cloning descriptor for file {subprocess_stdout_file:?}")
|
format!("Cloning descriptor for file {subprocess_stdout_file:?}")
|
||||||
})?;
|
})?;
|
||||||
let mut command = std::process::Command::new(executable);
|
let mut command = std::process::Command::new(executable);
|
||||||
let command = command
|
command.env(FORCE_CLI_MODE_ENV_VAR_NAME, "");
|
||||||
.env(FORCE_CLI_MODE_ENV_VAR_NAME, "")
|
if let Some(dir) = user_data_dir {
|
||||||
|
command.arg("--user-data-dir").arg(dir);
|
||||||
|
}
|
||||||
|
command
|
||||||
.stderr(subprocess_stdout_file)
|
.stderr(subprocess_stdout_file)
|
||||||
.stdout(subprocess_stdin_file)
|
.stdout(subprocess_stdin_file)
|
||||||
.arg(url);
|
.arg(url);
|
||||||
|
|||||||
@@ -63,15 +63,3 @@ Deployment is triggered by pushing to the `collab-staging` (or `collab-productio
|
|||||||
- `./script/deploy-collab production`
|
- `./script/deploy-collab production`
|
||||||
|
|
||||||
You can tell what is currently deployed with `./script/what-is-deployed`.
|
You can tell what is currently deployed with `./script/what-is-deployed`.
|
||||||
|
|
||||||
# Database Migrations
|
|
||||||
|
|
||||||
To create a new migration:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./script/create-migration <name>
|
|
||||||
```
|
|
||||||
|
|
||||||
Migrations are run automatically on service start, so run `foreman start` again. The service will crash if the migrations fail.
|
|
||||||
|
|
||||||
When you create a new migration, you also need to update the [SQLite schema](./migrations.sqlite/20221109000000_test_schema.sql) that is used for testing.
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
apiVersion: batch/v1
|
|
||||||
kind: Job
|
|
||||||
metadata:
|
|
||||||
namespace: ${ZED_KUBE_NAMESPACE}
|
|
||||||
name: ${ZED_MIGRATE_JOB_NAME}
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
restartPolicy: Never
|
|
||||||
containers:
|
|
||||||
- name: migrator
|
|
||||||
imagePullPolicy: Always
|
|
||||||
image: ${ZED_IMAGE_ID}
|
|
||||||
args:
|
|
||||||
- migrate
|
|
||||||
env:
|
|
||||||
- name: DATABASE_URL
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: database
|
|
||||||
key: url
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "sessions" (
|
|
||||||
"id" VARCHAR NOT NULL PRIMARY KEY,
|
|
||||||
"expires" TIMESTAMP WITH TIME ZONE NULL,
|
|
||||||
"session" TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "users" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"github_login" VARCHAR,
|
|
||||||
"admin" BOOLEAN
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_users_github_login" ON "users" ("github_login");
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "signups" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"github_login" VARCHAR,
|
|
||||||
"email_address" VARCHAR,
|
|
||||||
"about" TEXT
|
|
||||||
);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "access_tokens" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"user_id" INTEGER REFERENCES users (id),
|
|
||||||
"hash" VARCHAR(128)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX "index_access_tokens_user_id" ON "access_tokens" ("user_id");
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "orgs" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"name" VARCHAR NOT NULL,
|
|
||||||
"slug" VARCHAR NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_orgs_slug" ON "orgs" ("slug");
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "org_memberships" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"org_id" INTEGER REFERENCES orgs (id) NOT NULL,
|
|
||||||
"user_id" INTEGER REFERENCES users (id) NOT NULL,
|
|
||||||
"admin" BOOLEAN NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX "index_org_memberships_user_id" ON "org_memberships" ("user_id");
|
|
||||||
CREATE UNIQUE INDEX "index_org_memberships_org_id_and_user_id" ON "org_memberships" ("org_id", "user_id");
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "channels" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"owner_id" INTEGER NOT NULL,
|
|
||||||
"owner_is_user" BOOLEAN NOT NULL,
|
|
||||||
"name" VARCHAR NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_channels_owner_and_name" ON "channels" ("owner_is_user", "owner_id", "name");
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "channel_memberships" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"channel_id" INTEGER REFERENCES channels (id) NOT NULL,
|
|
||||||
"user_id" INTEGER REFERENCES users (id) NOT NULL,
|
|
||||||
"admin" BOOLEAN NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX "index_channel_memberships_user_id" ON "channel_memberships" ("user_id");
|
|
||||||
CREATE UNIQUE INDEX "index_channel_memberships_channel_id_and_user_id" ON "channel_memberships" ("channel_id", "user_id");
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "channel_messages" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"channel_id" INTEGER REFERENCES channels (id) NOT NULL,
|
|
||||||
"sender_id" INTEGER REFERENCES users (id) NOT NULL,
|
|
||||||
"body" TEXT NOT NULL,
|
|
||||||
"sent_at" TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX "index_channel_messages_channel_id" ON "channel_messages" ("channel_id");
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
ALTER TABLE "channel_messages"
|
|
||||||
ADD "nonce" UUID NOT NULL DEFAULT gen_random_uuid();
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_channel_messages_nonce" ON "channel_messages" ("nonce");
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
ALTER TABLE "signups"
|
|
||||||
ADD "wants_releases" BOOLEAN,
|
|
||||||
ADD "wants_updates" BOOLEAN,
|
|
||||||
ADD "wants_community" BOOLEAN;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS "signups";
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
|
||||||
CREATE INDEX trigram_index_users_on_github_login ON users USING GIN(github_login gin_trgm_ops);
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "contacts" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"user_id_a" INTEGER REFERENCES users (id) NOT NULL,
|
|
||||||
"user_id_b" INTEGER REFERENCES users (id) NOT NULL,
|
|
||||||
"a_to_b" BOOLEAN NOT NULL,
|
|
||||||
"should_notify" BOOLEAN NOT NULL,
|
|
||||||
"accepted" BOOLEAN NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_contacts_user_ids" ON "contacts" ("user_id_a", "user_id_b");
|
|
||||||
CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b");
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
ALTER TABLE users
|
|
||||||
ADD email_address VARCHAR(255) DEFAULT NULL,
|
|
||||||
ADD invite_code VARCHAR(64),
|
|
||||||
ADD invite_count INTEGER NOT NULL DEFAULT 0,
|
|
||||||
ADD inviter_id INTEGER REFERENCES users (id),
|
|
||||||
ADD connected_once BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
ADD created_at TIMESTAMP NOT NULL DEFAULT NOW();
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_invite_code_users" ON "users" ("invite_code");
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
ALTER TABLE contacts DROP CONSTRAINT contacts_user_id_a_fkey;
|
|
||||||
ALTER TABLE contacts DROP CONSTRAINT contacts_user_id_b_fkey;
|
|
||||||
ALTER TABLE contacts ADD CONSTRAINT contacts_user_id_a_fkey FOREIGN KEY (user_id_a) REFERENCES users(id) ON DELETE CASCADE;
|
|
||||||
ALTER TABLE contacts ADD CONSTRAINT contacts_user_id_b_fkey FOREIGN KEY (user_id_b) REFERENCES users(id) ON DELETE CASCADE;
|
|
||||||
ALTER TABLE users DROP CONSTRAINT users_inviter_id_fkey;
|
|
||||||
ALTER TABLE users ADD CONSTRAINT users_inviter_id_fkey FOREIGN KEY (inviter_id) REFERENCES users(id) ON DELETE SET NULL;
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "projects" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"host_user_id" INTEGER REFERENCES users (id) NOT NULL,
|
|
||||||
"unregistered" BOOLEAN NOT NULL DEFAULT false
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "worktree_extensions" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"project_id" INTEGER REFERENCES projects (id) NOT NULL,
|
|
||||||
"worktree_id" INTEGER NOT NULL,
|
|
||||||
"extension" VARCHAR(255),
|
|
||||||
"count" INTEGER NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "project_activity_periods" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"duration_millis" INTEGER NOT NULL,
|
|
||||||
"ended_at" TIMESTAMP NOT NULL,
|
|
||||||
"user_id" INTEGER REFERENCES users (id) NOT NULL,
|
|
||||||
"project_id" INTEGER REFERENCES projects (id) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX "index_project_activity_periods_on_ended_at" ON "project_activity_periods" ("ended_at");
|
|
||||||
CREATE UNIQUE INDEX "index_worktree_extensions_on_project_id_and_worktree_id_and_extension" ON "worktree_extensions" ("project_id", "worktree_id", "extension");
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "signups" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"email_address" VARCHAR NOT NULL,
|
|
||||||
"email_confirmation_code" VARCHAR(64) NOT NULL,
|
|
||||||
"email_confirmation_sent" BOOLEAN NOT NULL,
|
|
||||||
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"device_id" VARCHAR,
|
|
||||||
"user_id" INTEGER REFERENCES users (id) ON DELETE CASCADE,
|
|
||||||
"inviting_user_id" INTEGER REFERENCES users (id) ON DELETE SET NULL,
|
|
||||||
|
|
||||||
"platform_mac" BOOLEAN NOT NULL,
|
|
||||||
"platform_linux" BOOLEAN NOT NULL,
|
|
||||||
"platform_windows" BOOLEAN NOT NULL,
|
|
||||||
"platform_unknown" BOOLEAN NOT NULL,
|
|
||||||
|
|
||||||
"editor_features" VARCHAR[],
|
|
||||||
"programming_languages" VARCHAR[]
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_signups_on_email_address" ON "signups" ("email_address");
|
|
||||||
CREATE INDEX "index_signups_on_email_confirmation_sent" ON "signups" ("email_confirmation_sent");
|
|
||||||
|
|
||||||
ALTER TABLE "users"
|
|
||||||
ADD "github_user_id" INTEGER;
|
|
||||||
|
|
||||||
CREATE INDEX "index_users_on_email_address" ON "users" ("email_address");
|
|
||||||
CREATE INDEX "index_users_on_github_user_id" ON "users" ("github_user_id");
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE "users"
|
|
||||||
ADD "metrics_id" uuid NOT NULL DEFAULT gen_random_uuid();
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "rooms" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"live_kit_room" VARCHAR NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE "projects"
|
|
||||||
ADD "room_id" INTEGER REFERENCES rooms (id),
|
|
||||||
ADD "host_connection_id" INTEGER,
|
|
||||||
ADD "host_connection_epoch" UUID;
|
|
||||||
CREATE INDEX "index_projects_on_host_connection_epoch" ON "projects" ("host_connection_epoch");
|
|
||||||
|
|
||||||
CREATE TABLE "worktrees" (
|
|
||||||
"project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
|
|
||||||
"id" INT8 NOT NULL,
|
|
||||||
"root_name" VARCHAR NOT NULL,
|
|
||||||
"abs_path" VARCHAR NOT NULL,
|
|
||||||
"visible" BOOL NOT NULL,
|
|
||||||
"scan_id" INT8 NOT NULL,
|
|
||||||
"is_complete" BOOL NOT NULL,
|
|
||||||
PRIMARY KEY(project_id, id)
|
|
||||||
);
|
|
||||||
CREATE INDEX "index_worktrees_on_project_id" ON "worktrees" ("project_id");
|
|
||||||
|
|
||||||
CREATE TABLE "worktree_entries" (
|
|
||||||
"project_id" INTEGER NOT NULL,
|
|
||||||
"worktree_id" INT8 NOT NULL,
|
|
||||||
"id" INT8 NOT NULL,
|
|
||||||
"is_dir" BOOL NOT NULL,
|
|
||||||
"path" VARCHAR NOT NULL,
|
|
||||||
"inode" INT8 NOT NULL,
|
|
||||||
"mtime_seconds" INT8 NOT NULL,
|
|
||||||
"mtime_nanos" INTEGER NOT NULL,
|
|
||||||
"is_symlink" BOOL NOT NULL,
|
|
||||||
"is_ignored" BOOL NOT NULL,
|
|
||||||
PRIMARY KEY(project_id, worktree_id, id),
|
|
||||||
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
CREATE INDEX "index_worktree_entries_on_project_id" ON "worktree_entries" ("project_id");
|
|
||||||
CREATE INDEX "index_worktree_entries_on_project_id_and_worktree_id" ON "worktree_entries" ("project_id", "worktree_id");
|
|
||||||
|
|
||||||
CREATE TABLE "worktree_diagnostic_summaries" (
|
|
||||||
"project_id" INTEGER NOT NULL,
|
|
||||||
"worktree_id" INT8 NOT NULL,
|
|
||||||
"path" VARCHAR NOT NULL,
|
|
||||||
"language_server_id" INT8 NOT NULL,
|
|
||||||
"error_count" INTEGER NOT NULL,
|
|
||||||
"warning_count" INTEGER NOT NULL,
|
|
||||||
PRIMARY KEY(project_id, worktree_id, path),
|
|
||||||
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
CREATE INDEX "index_worktree_diagnostic_summaries_on_project_id" ON "worktree_diagnostic_summaries" ("project_id");
|
|
||||||
CREATE INDEX "index_worktree_diagnostic_summaries_on_project_id_and_worktree_id" ON "worktree_diagnostic_summaries" ("project_id", "worktree_id");
|
|
||||||
|
|
||||||
CREATE TABLE "language_servers" (
|
|
||||||
"project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
|
|
||||||
"id" INT8 NOT NULL,
|
|
||||||
"name" VARCHAR NOT NULL,
|
|
||||||
PRIMARY KEY(project_id, id)
|
|
||||||
);
|
|
||||||
CREATE INDEX "index_language_servers_on_project_id" ON "language_servers" ("project_id");
|
|
||||||
|
|
||||||
CREATE TABLE "project_collaborators" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
|
|
||||||
"connection_id" INTEGER NOT NULL,
|
|
||||||
"connection_epoch" UUID NOT NULL,
|
|
||||||
"user_id" INTEGER NOT NULL,
|
|
||||||
"replica_id" INTEGER NOT NULL,
|
|
||||||
"is_host" BOOLEAN NOT NULL
|
|
||||||
);
|
|
||||||
CREATE INDEX "index_project_collaborators_on_project_id" ON "project_collaborators" ("project_id");
|
|
||||||
CREATE UNIQUE INDEX "index_project_collaborators_on_project_id_and_replica_id" ON "project_collaborators" ("project_id", "replica_id");
|
|
||||||
CREATE INDEX "index_project_collaborators_on_connection_epoch" ON "project_collaborators" ("connection_epoch");
|
|
||||||
|
|
||||||
CREATE TABLE "room_participants" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"room_id" INTEGER NOT NULL REFERENCES rooms (id),
|
|
||||||
"user_id" INTEGER NOT NULL REFERENCES users (id),
|
|
||||||
"answering_connection_id" INTEGER,
|
|
||||||
"answering_connection_epoch" UUID,
|
|
||||||
"location_kind" INTEGER,
|
|
||||||
"location_project_id" INTEGER,
|
|
||||||
"initial_project_id" INTEGER,
|
|
||||||
"calling_user_id" INTEGER NOT NULL REFERENCES users (id),
|
|
||||||
"calling_connection_id" INTEGER NOT NULL,
|
|
||||||
"calling_connection_epoch" UUID NOT NULL
|
|
||||||
);
|
|
||||||
CREATE UNIQUE INDEX "index_room_participants_on_user_id" ON "room_participants" ("user_id");
|
|
||||||
CREATE INDEX "index_room_participants_on_answering_connection_epoch" ON "room_participants" ("answering_connection_epoch");
|
|
||||||
CREATE INDEX "index_room_participants_on_calling_connection_epoch" ON "room_participants" ("calling_connection_epoch");
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE "signups"
|
|
||||||
ADD "added_to_mailing_list" BOOLEAN NOT NULL DEFAULT FALSE;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
ALTER TABLE "room_participants"
|
|
||||||
ADD "answering_connection_lost" BOOLEAN NOT NULL DEFAULT FALSE;
|
|
||||||
|
|
||||||
CREATE INDEX "index_project_collaborators_on_connection_id" ON "project_collaborators" ("connection_id");
|
|
||||||
CREATE UNIQUE INDEX "index_project_collaborators_on_project_id_connection_id_and_epoch" ON "project_collaborators" ("project_id", "connection_id", "connection_epoch");
|
|
||||||
CREATE INDEX "index_room_participants_on_answering_connection_id" ON "room_participants" ("answering_connection_id");
|
|
||||||
CREATE UNIQUE INDEX "index_room_participants_on_answering_connection_id_and_answering_connection_epoch" ON "room_participants" ("answering_connection_id", "answering_connection_epoch");
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
CREATE INDEX "index_room_participants_on_room_id" ON "room_participants" ("room_id");
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
CREATE TABLE servers (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
environment VARCHAR NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
DROP TABLE worktree_extensions;
|
|
||||||
DROP TABLE project_activity_periods;
|
|
||||||
DELETE from projects;
|
|
||||||
ALTER TABLE projects
|
|
||||||
DROP COLUMN host_connection_epoch,
|
|
||||||
ADD COLUMN host_connection_server_id INTEGER REFERENCES servers (id) ON DELETE CASCADE;
|
|
||||||
CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id");
|
|
||||||
CREATE INDEX "index_projects_on_host_connection_id_and_host_connection_server_id" ON "projects" ("host_connection_id", "host_connection_server_id");
|
|
||||||
|
|
||||||
DELETE FROM project_collaborators;
|
|
||||||
ALTER TABLE project_collaborators
|
|
||||||
DROP COLUMN connection_epoch,
|
|
||||||
ADD COLUMN connection_server_id INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE;
|
|
||||||
CREATE INDEX "index_project_collaborators_on_connection_server_id" ON "project_collaborators" ("connection_server_id");
|
|
||||||
CREATE UNIQUE INDEX "index_project_collaborators_on_project_id_connection_id_and_server_id" ON "project_collaborators" ("project_id", "connection_id", "connection_server_id");
|
|
||||||
|
|
||||||
DELETE FROM room_participants;
|
|
||||||
ALTER TABLE room_participants
|
|
||||||
DROP COLUMN answering_connection_epoch,
|
|
||||||
DROP COLUMN calling_connection_epoch,
|
|
||||||
ADD COLUMN answering_connection_server_id INTEGER REFERENCES servers (id) ON DELETE CASCADE,
|
|
||||||
ADD COLUMN calling_connection_server_id INTEGER REFERENCES servers (id) ON DELETE SET NULL;
|
|
||||||
CREATE INDEX "index_room_participants_on_answering_connection_server_id" ON "room_participants" ("answering_connection_server_id");
|
|
||||||
CREATE INDEX "index_room_participants_on_calling_connection_server_id" ON "room_participants" ("calling_connection_server_id");
|
|
||||||
CREATE UNIQUE INDEX "index_room_participants_on_answering_connection_id_and_answering_connection_server_id" ON "room_participants" ("answering_connection_id", "answering_connection_server_id");
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE "worktree_entries"
|
|
||||||
ADD COLUMN "scan_id" INT8,
|
|
||||||
ADD COLUMN "is_deleted" BOOL;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE worktrees
|
|
||||||
ALTER COLUMN is_complete SET DEFAULT FALSE,
|
|
||||||
ADD COLUMN completed_scan_id INT8;
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "followers" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"room_id" INTEGER NOT NULL REFERENCES rooms (id) ON DELETE CASCADE,
|
|
||||||
"project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
|
|
||||||
"leader_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
|
|
||||||
"leader_connection_id" INTEGER NOT NULL,
|
|
||||||
"follower_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
|
|
||||||
"follower_connection_id" INTEGER NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX
|
|
||||||
"index_followers_on_project_id_and_leader_connection_server_id_and_leader_connection_id_and_follower_connection_server_id_and_follower_connection_id"
|
|
||||||
ON "followers" ("project_id", "leader_connection_server_id", "leader_connection_id", "follower_connection_server_id", "follower_connection_id");
|
|
||||||
|
|
||||||
CREATE INDEX "index_followers_on_room_id" ON "followers" ("room_id");
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
CREATE TABLE "worktree_repositories" (
|
|
||||||
"project_id" INTEGER NOT NULL,
|
|
||||||
"worktree_id" INT8 NOT NULL,
|
|
||||||
"work_directory_id" INT8 NOT NULL,
|
|
||||||
"scan_id" INT8 NOT NULL,
|
|
||||||
"branch" VARCHAR,
|
|
||||||
"is_deleted" BOOL NOT NULL,
|
|
||||||
PRIMARY KEY(project_id, worktree_id, work_directory_id),
|
|
||||||
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
CREATE INDEX "index_worktree_repositories_on_project_id" ON "worktree_repositories" ("project_id");
|
|
||||||
CREATE INDEX "index_worktree_repositories_on_project_id_and_worktree_id" ON "worktree_repositories" ("project_id", "worktree_id");
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
CREATE TABLE "worktree_repository_statuses" (
|
|
||||||
"project_id" INTEGER NOT NULL,
|
|
||||||
"worktree_id" INT8 NOT NULL,
|
|
||||||
"work_directory_id" INT8 NOT NULL,
|
|
||||||
"repo_path" VARCHAR NOT NULL,
|
|
||||||
"status" INT8 NOT NULL,
|
|
||||||
"scan_id" INT8 NOT NULL,
|
|
||||||
"is_deleted" BOOL NOT NULL,
|
|
||||||
PRIMARY KEY(project_id, worktree_id, work_directory_id, repo_path),
|
|
||||||
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
CREATE INDEX "index_wt_repos_statuses_on_project_id" ON "worktree_repository_statuses" ("project_id");
|
|
||||||
CREATE INDEX "index_wt_repos_statuses_on_project_id_and_wt_id" ON "worktree_repository_statuses" ("project_id", "worktree_id");
|
|
||||||
CREATE INDEX "index_wt_repos_statuses_on_project_id_and_wt_id_and_wd_id" ON "worktree_repository_statuses" ("project_id", "worktree_id", "work_directory_id");
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
CREATE TABLE "worktree_settings_files" (
|
|
||||||
"project_id" INTEGER NOT NULL,
|
|
||||||
"worktree_id" INT8 NOT NULL,
|
|
||||||
"path" VARCHAR NOT NULL,
|
|
||||||
"content" TEXT NOT NULL,
|
|
||||||
PRIMARY KEY(project_id, worktree_id, path),
|
|
||||||
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
CREATE INDEX "index_settings_files_on_project_id" ON "worktree_settings_files" ("project_id");
|
|
||||||
CREATE INDEX "index_settings_files_on_project_id_and_wt_id" ON "worktree_settings_files" ("project_id", "worktree_id");
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE "worktree_entries"
|
|
||||||
ADD "git_status" INT8;
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE "worktree_entries"
|
|
||||||
ADD "is_external" BOOL NOT NULL DEFAULT FALSE;
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
DROP TABLE "channel_messages";
|
|
||||||
DROP TABLE "channel_memberships";
|
|
||||||
DROP TABLE "org_memberships";
|
|
||||||
DROP TABLE "orgs";
|
|
||||||
DROP TABLE "channels";
|
|
||||||
|
|
||||||
CREATE TABLE "channels" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"name" VARCHAR NOT NULL,
|
|
||||||
"created_at" TIMESTAMP NOT NULL DEFAULT now()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "channel_paths" (
|
|
||||||
"id_path" VARCHAR NOT NULL PRIMARY KEY,
|
|
||||||
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
CREATE INDEX "index_channel_paths_on_channel_id" ON "channel_paths" ("channel_id");
|
|
||||||
|
|
||||||
CREATE TABLE "channel_members" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
|
|
||||||
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
|
||||||
"admin" BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
"accepted" BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
"updated_at" TIMESTAMP NOT NULL DEFAULT now()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_channel_members_on_channel_id_and_user_id" ON "channel_members" ("channel_id", "user_id");
|
|
||||||
|
|
||||||
ALTER TABLE rooms ADD COLUMN "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE;
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
CREATE TABLE "buffers" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
|
|
||||||
"epoch" INTEGER NOT NULL DEFAULT 0
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX "index_buffers_on_channel_id" ON "buffers" ("channel_id");
|
|
||||||
|
|
||||||
CREATE TABLE "buffer_operations" (
|
|
||||||
"buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
|
|
||||||
"epoch" INTEGER NOT NULL,
|
|
||||||
"replica_id" INTEGER NOT NULL,
|
|
||||||
"lamport_timestamp" INTEGER NOT NULL,
|
|
||||||
"value" BYTEA NOT NULL,
|
|
||||||
PRIMARY KEY(buffer_id, epoch, lamport_timestamp, replica_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "buffer_snapshots" (
|
|
||||||
"buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
|
|
||||||
"epoch" INTEGER NOT NULL,
|
|
||||||
"text" TEXT NOT NULL,
|
|
||||||
"operation_serialization_version" INTEGER NOT NULL,
|
|
||||||
PRIMARY KEY(buffer_id, epoch)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "channel_buffer_collaborators" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
|
|
||||||
"connection_id" INTEGER NOT NULL,
|
|
||||||
"connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
|
|
||||||
"connection_lost" BOOLEAN NOT NULL DEFAULT FALSE,
|
|
||||||
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
|
||||||
"replica_id" INTEGER NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX "index_channel_buffer_collaborators_on_channel_id" ON "channel_buffer_collaborators" ("channel_id");
|
|
||||||
CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_and_replica_id" ON "channel_buffer_collaborators" ("channel_id", "replica_id");
|
|
||||||
CREATE INDEX "index_channel_buffer_collaborators_on_connection_server_id" ON "channel_buffer_collaborators" ("connection_server_id");
|
|
||||||
CREATE INDEX "index_channel_buffer_collaborators_on_connection_id" ON "channel_buffer_collaborators" ("connection_id");
|
|
||||||
CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_connection_id_and_server_id" ON "channel_buffer_collaborators" ("channel_id", "connection_id", "connection_server_id");
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
CREATE TABLE "feature_flags" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"flag" VARCHAR(255) NOT NULL UNIQUE
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_feature_flags" ON "feature_flags" ("id");
|
|
||||||
|
|
||||||
CREATE TABLE "user_features" (
|
|
||||||
"user_id" INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
||||||
"feature_id" INTEGER NOT NULL REFERENCES feature_flags(id) ON DELETE CASCADE,
|
|
||||||
PRIMARY KEY (user_id, feature_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_user_features_user_id_and_feature_id" ON "user_features" ("user_id", "feature_id");
|
|
||||||
CREATE INDEX "index_user_features_on_user_id" ON "user_features" ("user_id");
|
|
||||||
CREATE INDEX "index_user_features_on_feature_id" ON "user_features" ("feature_id");
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "channel_messages" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
|
|
||||||
"sender_id" INTEGER NOT NULL REFERENCES users (id),
|
|
||||||
"body" TEXT NOT NULL,
|
|
||||||
"sent_at" TIMESTAMP,
|
|
||||||
"nonce" UUID NOT NULL
|
|
||||||
);
|
|
||||||
CREATE INDEX "index_channel_messages_on_channel_id" ON "channel_messages" ("channel_id");
|
|
||||||
CREATE UNIQUE INDEX "index_channel_messages_on_nonce" ON "channel_messages" ("nonce");
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "channel_chat_participants" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"user_id" INTEGER NOT NULL REFERENCES users (id),
|
|
||||||
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
|
|
||||||
"connection_id" INTEGER NOT NULL,
|
|
||||||
"connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
CREATE INDEX "index_channel_chat_participants_on_channel_id" ON "channel_chat_participants" ("channel_id");
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "observed_buffer_edits" (
|
|
||||||
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
|
||||||
"buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
|
|
||||||
"epoch" INTEGER NOT NULL,
|
|
||||||
"lamport_timestamp" INTEGER NOT NULL,
|
|
||||||
"replica_id" INTEGER NOT NULL,
|
|
||||||
PRIMARY KEY (user_id, buffer_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_observed_buffer_user_and_buffer_id" ON "observed_buffer_edits" ("user_id", "buffer_id");
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "observed_channel_messages" (
|
|
||||||
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
|
||||||
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
|
|
||||||
"channel_message_id" INTEGER NOT NULL,
|
|
||||||
PRIMARY KEY (user_id, channel_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_observed_channel_messages_user_and_channel_id" ON "observed_channel_messages" ("user_id", "channel_id");
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE room_participants ADD COLUMN participant_index INTEGER;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
CREATE TABLE "notification_kinds" (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"name" VARCHAR NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_notification_kinds_on_name" ON "notification_kinds" ("name");
|
|
||||||
|
|
||||||
CREATE TABLE notifications (
|
|
||||||
"id" SERIAL PRIMARY KEY,
|
|
||||||
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
|
|
||||||
"recipient_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
|
||||||
"kind" INTEGER NOT NULL REFERENCES notification_kinds (id),
|
|
||||||
"entity_id" INTEGER,
|
|
||||||
"content" TEXT,
|
|
||||||
"is_read" BOOLEAN NOT NULL DEFAULT FALSE,
|
|
||||||
"response" BOOLEAN
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX
|
|
||||||
"index_notifications_on_recipient_id_is_read_kind_entity_id"
|
|
||||||
ON "notifications"
|
|
||||||
("recipient_id", "is_read", "kind", "entity_id");
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE rooms ADD COLUMN enviroment TEXT;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id");
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
ALTER TABLE channel_members ADD COLUMN role TEXT;
|
|
||||||
UPDATE channel_members SET role = CASE WHEN admin THEN 'admin' ELSE 'member' END;
|
|
||||||
|
|
||||||
ALTER TABLE channels ADD COLUMN visibility TEXT NOT NULL DEFAULT 'members';
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
-- Add migration script here
|
|
||||||
|
|
||||||
ALTER TABLE projects
|
|
||||||
DROP CONSTRAINT projects_room_id_fkey,
|
|
||||||
ADD CONSTRAINT projects_room_id_fkey
|
|
||||||
FOREIGN KEY (room_id)
|
|
||||||
REFERENCES rooms (id)
|
|
||||||
ON DELETE CASCADE;
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
CREATE TABLE "channel_message_mentions" (
|
|
||||||
"message_id" INTEGER NOT NULL REFERENCES channel_messages (id) ON DELETE CASCADE,
|
|
||||||
"start_offset" INTEGER NOT NULL,
|
|
||||||
"end_offset" INTEGER NOT NULL,
|
|
||||||
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
|
||||||
PRIMARY KEY(message_id, start_offset)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- We use 'on conflict update' with this index, so it should be per-user.
|
|
||||||
CREATE UNIQUE INDEX "index_channel_messages_on_sender_id_nonce" ON "channel_messages" ("sender_id", "nonce");
|
|
||||||
DROP INDEX "index_channel_messages_on_nonce";
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
ALTER TABLE channels ADD COLUMN parent_path TEXT;
|
|
||||||
|
|
||||||
UPDATE channels
|
|
||||||
SET parent_path = substr(
|
|
||||||
channel_paths.id_path,
|
|
||||||
2,
|
|
||||||
length(channel_paths.id_path) - length('/' || channel_paths.channel_id::text || '/')
|
|
||||||
)
|
|
||||||
FROM channel_paths
|
|
||||||
WHERE channel_paths.channel_id = channels.id;
|
|
||||||
|
|
||||||
CREATE INDEX "index_channels_on_parent_path" ON "channels" ("parent_path");
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE room_participants ADD COLUMN role TEXT;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE rooms ADD COLUMN environment TEXT;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE access_tokens ADD COLUMN impersonated_user_id integer;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
CREATE TABLE contributors (
|
|
||||||
user_id INTEGER REFERENCES users(id),
|
|
||||||
signed_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
||||||
PRIMARY KEY (user_id)
|
|
||||||
);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE "channels" ADD COLUMN "requires_zed_cla" BOOLEAN NOT NULL DEFAULT FALSE;
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
-- Add migration script here
|
|
||||||
|
|
||||||
DROP INDEX index_channels_on_parent_path;
|
|
||||||
CREATE INDEX index_channels_on_parent_path ON channels (parent_path text_pattern_ops);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE channel_messages ADD reply_to_message_id INTEGER DEFAULT NULL
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
-- Add migration script here
|
|
||||||
|
|
||||||
ALTER TABLE room_participants ADD COLUMN in_call BOOL NOT NULL DEFAULT FALSE;
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
-- Add migration script here
|
|
||||||
ALTER TABLE rooms DROP COLUMN enviroment;
|
|
||||||
ALTER TABLE rooms DROP COLUMN environment;
|
|
||||||
ALTER TABLE room_participants DROP COLUMN in_call;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS extensions (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
external_id TEXT NOT NULL,
|
|
||||||
latest_version TEXT NOT NULL,
|
|
||||||
total_download_count BIGINT NOT NULL DEFAULT 0
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS extension_versions (
|
|
||||||
extension_id INTEGER REFERENCES extensions(id),
|
|
||||||
version TEXT NOT NULL,
|
|
||||||
published_at TIMESTAMP NOT NULL DEFAULT now(),
|
|
||||||
authors TEXT NOT NULL,
|
|
||||||
repository TEXT NOT NULL,
|
|
||||||
description TEXT NOT NULL,
|
|
||||||
download_count BIGINT NOT NULL DEFAULT 0,
|
|
||||||
PRIMARY KEY(extension_id, version)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_extensions_external_id" ON "extensions" ("external_id");
|
|
||||||
CREATE INDEX "trigram_index_extensions_name" ON "extensions" USING GIN(name gin_trgm_ops);
|
|
||||||
CREATE INDEX "index_extensions_total_download_count" ON "extensions" ("total_download_count");
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS rate_buckets (
|
|
||||||
user_id INT NOT NULL,
|
|
||||||
rate_limit_name VARCHAR(255) NOT NULL,
|
|
||||||
token_count INT NOT NULL,
|
|
||||||
last_refill TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
|
||||||
PRIMARY KEY (user_id, rate_limit_name),
|
|
||||||
CONSTRAINT fk_user
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_user_id_rate_limit ON rate_buckets (user_id, rate_limit_name);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE channel_messages ADD edited_at TIMESTAMP DEFAULT NULL;
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
-- Add migration script here
|
|
||||||
|
|
||||||
CREATE TABLE hosted_projects (
|
|
||||||
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
|
||||||
channel_id INT NOT NULL REFERENCES channels(id),
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
visibility TEXT NOT NULL,
|
|
||||||
deleted_at TIMESTAMP NULL
|
|
||||||
);
|
|
||||||
CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id);
|
|
||||||
CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL);
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
-- Add migration script here
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX uix_channels_parent_path_name ON channels(parent_path, name) WHERE (parent_path IS NOT NULL AND parent_path != '');
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
-- Add migration script here
|
|
||||||
ALTER TABLE projects ALTER COLUMN host_user_id DROP NOT NULL;
|
|
||||||
ALTER TABLE projects ADD COLUMN hosted_project_id INTEGER REFERENCES hosted_projects(id) UNIQUE NULL;
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
-- Add migration script here
|
|
||||||
|
|
||||||
ALTER TABLE buffers ADD COLUMN latest_operation_epoch INTEGER;
|
|
||||||
ALTER TABLE buffers ADD COLUMN latest_operation_lamport_timestamp INTEGER;
|
|
||||||
ALTER TABLE buffers ADD COLUMN latest_operation_replica_id INTEGER;
|
|
||||||
|
|
||||||
WITH ops AS (
|
|
||||||
SELECT DISTINCT ON (buffer_id) buffer_id, epoch, lamport_timestamp, replica_id
|
|
||||||
FROM buffer_operations
|
|
||||||
ORDER BY buffer_id, epoch DESC, lamport_timestamp DESC, replica_id DESC
|
|
||||||
)
|
|
||||||
UPDATE buffers
|
|
||||||
SET latest_operation_epoch = ops.epoch,
|
|
||||||
latest_operation_lamport_timestamp = ops.lamport_timestamp,
|
|
||||||
latest_operation_replica_id = ops.replica_id
|
|
||||||
FROM ops
|
|
||||||
WHERE buffers.id = ops.buffer_id;
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
-- Add migration script here
|
|
||||||
|
|
||||||
ALTER TABLE channel_members ALTER role SET NOT NULL;
|
|
||||||
ALTER TABLE channel_members DROP COLUMN admin;
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
-- Add migration script here
|
|
||||||
ALTER TABLE channels ALTER parent_path SET NOT NULL;
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
-- Add migration script here
|
|
||||||
ALTER TABLE extension_versions ADD COLUMN schema_version INTEGER NOT NULL DEFAULT 0;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
CREATE TABLE dev_servers (
|
|
||||||
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
|
||||||
channel_id INT NOT NULL REFERENCES channels(id),
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
hashed_token TEXT NOT NULL
|
|
||||||
);
|
|
||||||
CREATE INDEX idx_dev_servers_on_channel_id ON dev_servers (channel_id);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE extension_versions ADD COLUMN wasm_api_version TEXT;
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
CREATE TABLE remote_projects (
|
|
||||||
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
|
||||||
channel_id INT NOT NULL REFERENCES channels(id),
|
|
||||||
dev_server_id INT NOT NULL REFERENCES dev_servers(id),
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
path TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE projects ADD COLUMN remote_project_id INTEGER REFERENCES remote_projects(id);
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "embeddings" (
|
|
||||||
"model" TEXT,
|
|
||||||
"digest" BYTEA,
|
|
||||||
"dimensions" FLOAT4[1536],
|
|
||||||
"retrieved_at" TIMESTAMP NOT NULL DEFAULT now(),
|
|
||||||
PRIMARY KEY ("model", "digest")
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS "idx_retrieved_at_on_embeddings" ON "embeddings" ("retrieved_at");
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
DELETE FROM remote_projects;
|
|
||||||
DELETE FROM dev_servers;
|
|
||||||
|
|
||||||
ALTER TABLE dev_servers DROP COLUMN channel_id;
|
|
||||||
ALTER TABLE dev_servers ADD COLUMN user_id INT NOT NULL REFERENCES users(id);
|
|
||||||
|
|
||||||
ALTER TABLE remote_projects DROP COLUMN channel_id;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE remote_projects DROP COLUMN name;
|
|
||||||
ALTER TABLE remote_projects
|
|
||||||
ADD CONSTRAINT unique_path_constraint UNIQUE(dev_server_id, path);
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user