Compare commits

..

1 Commits

Author SHA1 Message Date
Jason Mancuso
56b3d3bad1 exploring SWE-bench-verified structure and eval plan
Co-Authored-By: Richard <richard@zed.dev>
2024-08-21 14:43:26 -04:00
64 changed files with 999 additions and 2185 deletions

View File

@@ -363,7 +363,7 @@ jobs:
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
- uses: rui314/setup-mold@0bf4f07ef9048ec62a45f9dbf2f098afa49695f0 # v1
- uses: rui314/setup-mold@2e332a0b602c2fc65d2d3995941b1b29a5f554a0 # v1
with:
mold-version: 2.32.0

View File

@@ -157,7 +157,7 @@ jobs:
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
- uses: rui314/setup-mold@0bf4f07ef9048ec62a45f9dbf2f098afa49695f0 # v1
- uses: rui314/setup-mold@2e332a0b602c2fc65d2d3995941b1b29a5f554a0 # v1
with:
mold-version: 2.32.0

View File

@@ -24,8 +24,7 @@ Conrad Irwin <conrad@zed.dev>
Conrad Irwin <conrad@zed.dev> <conrad.irwin@gmail.com>
Danilo Leal <danilo@zed.dev>
Danilo Leal <danilo@zed.dev> <67129314+danilo-leal@users.noreply.github.com>
Evren Sen <146845123+evrensen467@users.noreply.github.com>
Evren Sen <146845123+evrensen467@users.noreply.github.com> <146845123+evrsen@users.noreply.github.com>
Evren Sen <146845123+evrsen@users.noreply.github.com>
Fernando Tagawa <tagawafernando@gmail.com>
Fernando Tagawa <tagawafernando@gmail.com> <fernando.tagawa.gamail.com@gmail.com>
Greg Morenz <greg-morenz@droid.cafe>

58
Cargo.lock generated
View File

@@ -282,9 +282,9 @@ checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a"
[[package]]
name = "arrayvec"
version = "0.7.6"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "as-raw-xcb-connection"
@@ -1055,9 +1055,9 @@ dependencies = [
[[package]]
name = "aws-sdk-s3"
version = "1.46.0"
version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4abf69a87be33b6f125a93d5046b5f7395c26d1f449bf8d3927f5577463b6de0"
checksum = "9ccda7e730ace3cb8bbd4071bc650c6d294364891f9564bd4e43adfc8dea3177"
dependencies = [
"ahash 0.8.11",
"aws-credential-types",
@@ -1269,9 +1269,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime"
version = "1.6.3"
version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0abbf454960d0db2ad12684a1640120e7557294b0ff8e2f11236290a1b293225"
checksum = "ce87155eba55e11768b8c1afa607f3e864ae82f03caf63258b37455b0ad02537"
dependencies = [
"aws-smithy-async",
"aws-smithy-http",
@@ -1313,9 +1313,9 @@ dependencies = [
[[package]]
name = "aws-smithy-types"
version = "1.2.2"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cee7cadb433c781d3299b916fbf620fea813bf38f49db282fb6858141a05cc8"
checksum = "cfe321a6b21f5d8eabd0ade9c55d3d0335f3c3157fc2b3e87f05f34b539e4df5"
dependencies = [
"base64-simd",
"bytes 1.7.1",
@@ -1541,7 +1541,7 @@ dependencies = [
"bitflags 2.6.0",
"cexpr",
"clang-sys",
"itertools 0.10.5",
"itertools 0.12.1",
"lazy_static",
"lazycell",
"proc-macro2",
@@ -1624,7 +1624,7 @@ dependencies = [
[[package]]
name = "blade-graphics"
version = "0.4.0"
source = "git+https://github.com/kvark/blade?rev=7f54ddfc001edc48225e6602d3c38ebb855421aa#7f54ddfc001edc48225e6602d3c38ebb855421aa"
source = "git+https://github.com/kvark/blade?rev=ac25c77ed8d86c386a541c935ffe0a0f6024e701#ac25c77ed8d86c386a541c935ffe0a0f6024e701"
dependencies = [
"ash",
"ash-window",
@@ -1654,7 +1654,7 @@ dependencies = [
[[package]]
name = "blade-macros"
version = "0.2.1"
source = "git+https://github.com/kvark/blade?rev=7f54ddfc001edc48225e6602d3c38ebb855421aa#7f54ddfc001edc48225e6602d3c38ebb855421aa"
source = "git+https://github.com/kvark/blade?rev=ac25c77ed8d86c386a541c935ffe0a0f6024e701#ac25c77ed8d86c386a541c935ffe0a0f6024e701"
dependencies = [
"proc-macro2",
"quote",
@@ -1664,7 +1664,7 @@ dependencies = [
[[package]]
name = "blade-util"
version = "0.1.0"
source = "git+https://github.com/kvark/blade?rev=7f54ddfc001edc48225e6602d3c38ebb855421aa#7f54ddfc001edc48225e6602d3c38ebb855421aa"
source = "git+https://github.com/kvark/blade?rev=ac25c77ed8d86c386a541c935ffe0a0f6024e701#ac25c77ed8d86c386a541c935ffe0a0f6024e701"
dependencies = [
"blade-graphics",
"bytemuck",
@@ -2211,9 +2211,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.16"
version = "4.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc"
dependencies = [
"clap_builder",
"clap_derive",
@@ -5874,9 +5874,9 @@ dependencies = [
[[package]]
name = "khronos-egl"
version = "6.0.0"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
checksum = "d1382b16c04aeb821453d6215a3c80ba78f24c6595c5aa85653378aabe0c83e3"
dependencies = [
"libc",
"libloading",
@@ -6174,7 +6174,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if",
"windows-targets 0.48.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -8127,7 +8127,7 @@ dependencies = [
"text",
"unindent",
"util",
"which 6.0.3",
"which 6.0.2",
"worktree",
]
@@ -9573,18 +9573,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.208"
version = "1.0.207"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.208"
version = "1.0.207"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e"
dependencies = [
"proc-macro2",
"quote",
@@ -11290,9 +11290,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.39.3"
version = "1.39.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
dependencies = [
"backtrace",
"bytes 1.7.1",
@@ -11687,9 +11687,9 @@ dependencies = [
[[package]]
name = "tree-sitter-go"
version = "0.21.2"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8d702a98d3c7e70e466456e58ff2b1ac550bf1e29b97e5770676d2fdabec00d"
checksum = "55cb318be5ccf75f44e054acf6898a5c95d59b53443eed578e16be0cd7ec037f"
dependencies = [
"cc",
"tree-sitter",
@@ -12879,9 +12879,9 @@ dependencies = [
[[package]]
name = "which"
version = "6.0.3"
version = "6.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f"
checksum = "3d9c5ed668ee1f17edb3b627225343d210006a90bb1e3745ce1f30b1fb115075"
dependencies = [
"either",
"home",
@@ -14161,7 +14161,7 @@ dependencies = [
[[package]]
name = "zed_zig"
version = "0.3.0"
version = "0.2.0"
dependencies = [
"zed_extension_api 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

View File

@@ -321,9 +321,9 @@ async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.22"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "7f54ddfc001edc48225e6602d3c38ebb855421aa" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "7f54ddfc001edc48225e6602d3c38ebb855421aa" }
blade-util = { git = "https://github.com/kvark/blade", rev = "7f54ddfc001edc48225e6602d3c38ebb855421aa" }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "ac25c77ed8d86c386a541c935ffe0a0f6024e701" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "ac25c77ed8d86c386a541c935ffe0a0f6024e701" }
blade-util = { git = "https://github.com/kvark/blade", rev = "ac25c77ed8d86c386a541c935ffe0a0f6024e701" }
cargo_metadata = "0.18"
cargo_toml = "0.20"
chrono = { version = "0.4", features = ["serde"] }

View File

@@ -92,7 +92,6 @@
"g y": "editor::GoToTypeDefinition",
"g shift-i": "editor::GoToImplementation",
"g x": "editor::OpenUrl",
"g f": "editor::OpenFile",
"g n": "vim::SelectNextMatch",
"g shift-n": "vim::SelectPreviousMatch",
"g l": "vim::SelectNext",
@@ -177,19 +176,19 @@
"ctrl-w ctrl-p": "workspace::ActivatePreviousPane",
"ctrl-w shift-w": "workspace::ActivatePreviousPane",
"ctrl-w ctrl-shift-w": "workspace::ActivatePreviousPane",
"ctrl-w v": "pane::SplitVertical",
"ctrl-w ctrl-v": "pane::SplitVertical",
"ctrl-w s": "pane::SplitHorizontal",
"ctrl-w shift-s": "pane::SplitHorizontal",
"ctrl-w ctrl-s": "pane::SplitHorizontal",
"ctrl-w v": "pane::SplitLeft",
"ctrl-w ctrl-v": "pane::SplitLeft",
"ctrl-w s": "pane::SplitUp",
"ctrl-w shift-s": "pane::SplitUp",
"ctrl-w ctrl-s": "pane::SplitUp",
"ctrl-w c": "pane::CloseAllItems",
"ctrl-w ctrl-c": "pane::CloseAllItems",
"ctrl-w q": "pane::CloseAllItems",
"ctrl-w ctrl-q": "pane::CloseAllItems",
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w n": "workspace::NewFileSplitHorizontal",
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
"ctrl-w n": ["workspace::NewFileInDirection", "Up"],
"ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"],
"ctrl-w d": "editor::GoToDefinitionSplit",
"ctrl-w g d": "editor::GoToDefinitionSplit",
"ctrl-w shift-d": "editor::GoToTypeDefinitionSplit",

View File

@@ -69,10 +69,6 @@
// The factor to grow the active pane by. Defaults to 1.0
// which gives the same size as all other panes.
"active_pane_magnification": 1.0,
// The direction that you want to split panes horizontally. Defaults to "up"
"pane_split_direction_horizontal": "up",
// The direction that you want to split panes horizontally. Defaults to "left"
"pane_split_direction_vertical": "left",
// Centered layout related settings.
"centered_layout": {
// The relative width of the left padding of the central pane from the
@@ -842,7 +838,6 @@
"language_servers": ["starpls", "!buck2-lsp", "..."]
},
"Svelte": {
"language_servers": ["svelte-language-server", "..."],
"prettier": {
"allowed": true,
"plugins": ["prettier-plugin-svelte"]
@@ -866,7 +861,6 @@
}
},
"Vue.js": {
"language_servers": ["vue-language-server", "..."],
"prettier": {
"allowed": true
}

View File

@@ -3,9 +3,10 @@ use editor::Editor;
use extension::ExtensionStore;
use futures::StreamExt;
use gpui::{
actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter,
InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
actions, anchored, deferred, percentage, Animation, AnimationExt as _, AppContext, CursorStyle,
DismissEvent, EventEmitter, InteractiveElement as _, Model, ParentElement as _, Render,
SharedString, StatefulInteractiveElement, Styled, Transformation, View, ViewContext,
VisualContext as _,
};
use language::{
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId, LanguageServerName,
@@ -13,7 +14,7 @@ use language::{
use project::{LanguageServerProgress, Project};
use smallvec::SmallVec;
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle};
use ui::{prelude::*, ContextMenu};
use workspace::{item::ItemHandle, StatusItemView, Workspace};
actions!(activity_indicator, [ShowErrorMessage]);
@@ -26,7 +27,7 @@ pub struct ActivityIndicator {
statuses: Vec<LspStatus>,
project: Model<Project>,
auto_updater: Option<Model<AutoUpdater>>,
context_menu_handle: PopoverMenuHandle<ContextMenu>,
context_menu: Option<View<ContextMenu>>,
}
struct LspStatus {
@@ -78,7 +79,7 @@ impl ActivityIndicator {
statuses: Default::default(),
project: project.clone(),
auto_updater,
context_menu_handle: Default::default(),
context_menu: None,
}
});
@@ -367,7 +368,72 @@ impl ActivityIndicator {
}
fn toggle_language_server_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
self.context_menu_handle.toggle(cx);
if self.context_menu.take().is_some() {
return;
}
self.build_lsp_work_context_menu(cx);
cx.notify();
}
fn build_lsp_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
let mut has_work = false;
let this = cx.view().downgrade();
let context_menu = ContextMenu::build(cx, |mut menu, cx| {
for work in self.pending_language_server_work(cx) {
has_work = true;
let this = this.clone();
let title = SharedString::from(
work.progress
.title
.as_deref()
.unwrap_or(work.progress_token)
.to_string(),
);
if work.progress.is_cancellable {
let language_server_id = work.language_server_id;
let token = work.progress_token.to_string();
menu = menu.custom_entry(
move |_| {
h_flex()
.w_full()
.justify_between()
.child(Label::new(title.clone()))
.child(Icon::new(IconName::XCircle))
.into_any_element()
},
move |cx| {
this.update(cx, |this, cx| {
this.project.update(cx, |project, cx| {
project.cancel_language_server_work(
language_server_id,
Some(token.clone()),
cx,
);
});
this.context_menu.take();
})
.ok();
},
);
} else {
menu = menu.label(title.clone());
}
}
menu
});
if has_work {
cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
this.context_menu.take();
cx.notify();
})
.detach();
cx.focus_view(&context_menu);
self.context_menu = Some(context_menu);
cx.notify();
}
}
}
@@ -389,72 +455,19 @@ impl Render for ActivityIndicator {
on_click(this, cx);
}))
}
let this = cx.view().downgrade();
result.gap_2().child(
PopoverMenu::new("activity-indicator-popover")
.trigger(
ButtonLike::new("activity-indicator-trigger").child(
h_flex()
.gap_2()
.children(content.icon)
.child(Label::new(content.message).size(LabelSize::Small)),
),
result
.gap_2()
.children(content.icon)
.child(Label::new(SharedString::from(content.message)).size(LabelSize::Small))
.children(self.context_menu.as_ref().map(|menu| {
deferred(
anchored()
.anchor(gpui::AnchorCorner::BottomLeft)
.child(menu.clone()),
)
.anchor(gpui::AnchorCorner::BottomLeft)
.menu(move |cx| {
let strong_this = this.upgrade()?;
ContextMenu::build(cx, |mut menu, cx| {
for work in strong_this.read(cx).pending_language_server_work(cx) {
let this = this.clone();
let mut title = work
.progress
.title
.as_deref()
.unwrap_or(work.progress_token)
.to_owned();
if work.progress.is_cancellable {
let language_server_id = work.language_server_id;
let token = work.progress_token.to_string();
let title = SharedString::from(title);
menu = menu.custom_entry(
move |_| {
h_flex()
.w_full()
.justify_between()
.child(Label::new(title.clone()))
.child(Icon::new(IconName::XCircle))
.into_any_element()
},
move |cx| {
this.update(cx, |this, cx| {
this.project.update(cx, |project, cx| {
project.cancel_language_server_work(
language_server_id,
Some(token.clone()),
cx,
);
});
this.context_menu_handle.hide(cx);
cx.notify();
})
.ok();
},
);
} else {
if let Some(progress_message) = work.progress.message.as_ref() {
title.push_str(": ");
title.push_str(progress_message);
}
menu = menu.label(title);
}
}
menu
})
.into()
}),
)
.with_priority(1)
}))
}
}

View File

@@ -36,10 +36,10 @@ use fs::Fs;
use gpui::{
canvas, div, img, percentage, point, pulsating_between, size, Action, Animation, AnimationExt,
AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardEntry, ClipboardItem,
Context as _, Empty, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, FontWeight,
InteractiveElement, IntoElement, Model, ParentElement, Pixels, ReadGlobal, Render, RenderImage,
SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation,
UpdateGlobal, View, VisualContext, WeakView, WindowContext,
Context as _, DismissEvent, Empty, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
FontWeight, InteractiveElement, IntoElement, Model, ParentElement, Pixels, ReadGlobal, Render,
RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task,
Transformation, UpdateGlobal, View, VisualContext, WeakView, WindowContext,
};
use indexed_docs::IndexedDocsStore;
use language::{
@@ -349,7 +349,6 @@ impl AssistantPanel {
model_summary_editor.clone(),
)
});
let pane = cx.new_view(|cx| {
let mut pane = Pane::new(
workspace.weak_handle(),
@@ -386,7 +385,6 @@ impl AssistantPanel {
pane.active_item()
.map_or(false, |item| item.downcast::<ContextHistory>().is_some()),
);
let _pane = cx.view().clone();
let right_children = h_flex()
.gap(Spacing::Small.rems(cx))
.child(
@@ -397,27 +395,32 @@ impl AssistantPanel {
.tooltip(|cx| Tooltip::for_action("New Context", &NewFile, cx)),
)
.child(
PopoverMenu::new("assistant-panel-popover-menu")
.trigger(
IconButton::new("menu", IconName::Menu).icon_size(IconSize::Small),
)
.menu(move |cx| {
let zoom_label = if _pane.read(cx).is_zoomed() {
IconButton::new("menu", IconName::Menu)
.icon_size(IconSize::Small)
.on_click(cx.listener(|pane, _, cx| {
let zoom_label = if pane.is_zoomed() {
"Zoom Out"
} else {
"Zoom In"
};
let focus_handle = _pane.focus_handle(cx);
Some(ContextMenu::build(cx, move |menu, _| {
menu.context(focus_handle.clone())
let menu = ContextMenu::build(cx, |menu, cx| {
menu.context(pane.focus_handle(cx))
.action("New Context", Box::new(NewFile))
.action("History", Box::new(DeployHistory))
.action("Prompt Library", Box::new(DeployPromptLibrary))
.action("Configure", Box::new(ShowConfiguration))
.action(zoom_label, Box::new(ToggleZoom))
}))
}),
});
cx.subscribe(&menu, |pane, _, _: &DismissEvent, _| {
pane.new_item_menu = None;
})
.detach();
pane.new_item_menu = Some(menu);
})),
)
.when_some(pane.new_item_menu.as_ref(), |el, new_item_menu| {
el.child(Pane::render_menu_overlay(new_item_menu))
})
.into_any_element()
.into();

View File

@@ -197,7 +197,6 @@ fn tab_items_for_queries(
}
let mut timestamps_by_entity_id = HashMap::default();
let mut visited_buffers = HashSet::default();
let mut open_buffers = Vec::new();
for pane in workspace.panes() {
@@ -212,11 +211,9 @@ fn tab_items_for_queries(
if let Some(timestamp) =
timestamps_by_entity_id.get(&editor.entity_id())
{
if visited_buffers.insert(buffer.read(cx).remote_id()) {
let snapshot = buffer.read(cx).snapshot();
let full_path = snapshot.resolve_file_path(cx, true);
open_buffers.push((full_path, snapshot, *timestamp));
}
let snapshot = buffer.read(cx).snapshot();
let full_path = snapshot.resolve_file_path(cx, true);
open_buffers.push((full_path, snapshot, *timestamp));
}
}
}

View File

@@ -18,7 +18,6 @@ use axum::{
Extension, Json, Router, TypedHeader,
};
use chrono::{DateTime, Duration, Utc};
use collections::HashMap;
use db::{usage_measure::UsageMeasure, ActiveUserCount, LlmDatabase};
use futures::{Stream, StreamExt as _};
use http_client::IsahcHttpClient;
@@ -30,7 +29,6 @@ use std::{
sync::Arc,
task::{Context, Poll},
};
use strum::IntoEnumIterator;
use telemetry::{report_llm_rate_limit, report_llm_usage, LlmRateLimitEventRow, LlmUsageEventRow};
use tokio::sync::RwLock;
use util::ResultExt;
@@ -43,8 +41,7 @@ pub struct LlmState {
pub db: Arc<LlmDatabase>,
pub http_client: IsahcHttpClient,
pub clickhouse_client: Option<clickhouse::Client>,
active_user_count_by_model:
RwLock<HashMap<(LanguageModelProvider, String), (DateTime<Utc>, ActiveUserCount)>>,
active_user_count: RwLock<Option<(DateTime<Utc>, ActiveUserCount)>>,
}
const ACTIVE_USER_COUNT_CACHE_DURATION: Duration = Duration::seconds(30);
@@ -72,6 +69,9 @@ impl LlmState {
.build()
.context("failed to construct http client")?;
let initial_active_user_count =
Some((Utc::now(), db.get_active_user_count(Utc::now()).await?));
let this = Self {
executor,
db,
@@ -80,34 +80,25 @@ impl LlmState {
.clickhouse_url
.as_ref()
.and_then(|_| build_clickhouse_client(&config).log_err()),
active_user_count_by_model: RwLock::new(HashMap::default()),
active_user_count: RwLock::new(initial_active_user_count),
config,
};
Ok(Arc::new(this))
}
pub async fn get_active_user_count(
&self,
provider: LanguageModelProvider,
model: &str,
) -> Result<ActiveUserCount> {
pub async fn get_active_user_count(&self) -> Result<ActiveUserCount> {
let now = Utc::now();
{
let active_user_count_by_model = self.active_user_count_by_model.read().await;
if let Some((last_updated, count)) =
active_user_count_by_model.get(&(provider, model.to_string()))
{
if now - *last_updated < ACTIVE_USER_COUNT_CACHE_DURATION {
return Ok(*count);
}
if let Some((last_updated, count)) = self.active_user_count.read().await.as_ref() {
if now - *last_updated < ACTIVE_USER_COUNT_CACHE_DURATION {
return Ok(*count);
}
}
let mut cache = self.active_user_count_by_model.write().await;
let new_count = self.db.get_active_user_count(provider, model, now).await?;
cache.insert((provider, model.to_string()), (now, new_count));
let mut cache = self.active_user_count.write().await;
let new_count = self.db.get_active_user_count(now).await?;
*cache = Some((now, new_count));
Ok(new_count)
}
}
@@ -428,7 +419,7 @@ async fn check_usage_limit(
)
.await?;
let active_users = state.get_active_user_count(provider, model_name).await?;
let active_users = state.get_active_user_count().await?;
let users_in_recent_minutes = active_users.users_in_recent_minutes.max(1);
let users_in_recent_days = active_users.users_in_recent_days.max(1);
@@ -472,24 +463,6 @@ async fn check_usage_limit(
};
if let Some(client) = state.clickhouse_client.as_ref() {
tracing::info!(
target: "user rate limit",
user_id = claims.user_id,
login = claims.github_user_login,
authn.jti = claims.jti,
is_staff = claims.is_staff,
provider = provider.to_string(),
model = model.name,
requests_this_minute = usage.requests_this_minute,
tokens_this_minute = usage.tokens_this_minute,
tokens_this_day = usage.tokens_this_day,
users_in_recent_minutes = users_in_recent_minutes,
users_in_recent_days = users_in_recent_days,
max_requests_per_minute = per_user_max_requests_per_minute,
max_tokens_per_minute = per_user_max_tokens_per_minute,
max_tokens_per_day = per_user_max_tokens_per_day,
);
report_llm_rate_limit(
client,
LlmRateLimitEventRow {
@@ -632,39 +605,23 @@ pub fn log_usage_periodically(state: Arc<LlmState>) {
.sleep(std::time::Duration::from_secs(30))
.await;
for provider in LanguageModelProvider::iter() {
for model in state.db.model_names_for_provider(provider) {
if let Some(active_user_count) = state
.get_active_user_count(provider, &model)
.await
.log_err()
{
tracing::info!(
target: "active user counts",
provider = provider.to_string(),
model = model,
users_in_recent_minutes = active_user_count.users_in_recent_minutes,
users_in_recent_days = active_user_count.users_in_recent_days,
);
}
}
}
if let Some(usages) = state
let Some(usages) = state
.db
.get_application_wide_usages_by_model(Utc::now())
.await
.log_err()
{
for usage in usages {
tracing::info!(
target: "computed usage",
provider = usage.provider.to_string(),
model = usage.model,
requests_this_minute = usage.requests_this_minute,
tokens_this_minute = usage.tokens_this_minute,
);
}
else {
continue;
};
for usage in usages {
tracing::info!(
target: "computed usage",
provider = usage.provider.to_string(),
model = usage.model,
requests_this_minute = usage.requests_this_minute,
tokens_this_minute = usage.tokens_this_minute,
);
}
}
})

View File

@@ -343,27 +343,15 @@ impl LlmDatabase {
.await
}
/// Returns the active user count for the specified model.
pub async fn get_active_user_count(
&self,
provider: LanguageModelProvider,
model_name: &str,
now: DateTimeUtc,
) -> Result<ActiveUserCount> {
pub async fn get_active_user_count(&self, now: DateTimeUtc) -> Result<ActiveUserCount> {
self.transaction(|tx| async move {
let minute_since = now - Duration::minutes(5);
let day_since = now - Duration::days(5);
let model = self
.models
.get(&(provider, model_name.to_string()))
.ok_or_else(|| anyhow!("unknown model {provider}:{model_name}"))?;
let users_in_recent_minutes = usage::Entity::find()
.filter(
usage::Column::ModelId
.eq(model.id)
.and(usage::Column::Timestamp.gte(minute_since.naive_utc()))
usage::Column::Timestamp
.gte(minute_since.naive_utc())
.and(usage::Column::IsStaff.eq(false)),
)
.select_only()
@@ -374,9 +362,8 @@ impl LlmDatabase {
let users_in_recent_days = usage::Entity::find()
.filter(
usage::Column::ModelId
.eq(model.id)
.and(usage::Column::Timestamp.gte(day_since.naive_utc()))
usage::Column::Timestamp
.gte(day_since.naive_utc())
.and(usage::Column::IsStaff.eq(false)),
)
.select_only()

View File

@@ -302,7 +302,10 @@ async fn handle_liveness_probe(
}
if let Some(llm_state) = llm_state {
llm_state.db.list_providers().await?;
llm_state
.db
.get_active_user_count(chrono::Utc::now())
.await?;
}
Ok("ok".to_string())

View File

@@ -1060,7 +1060,7 @@ mod tests {
editor.change_selections(None, cx, |selections| {
selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
});
editor.refresh_inline_completion(true, false, cx);
editor.next_inline_completion(&Default::default(), cx);
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
@@ -1070,7 +1070,7 @@ mod tests {
editor.change_selections(None, cx, |s| {
s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
});
editor.refresh_inline_completion(true, false, cx);
editor.next_inline_completion(&Default::default(), cx);
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);

View File

@@ -262,7 +262,6 @@ gpui::actions!(
OpenExcerptsSplit,
OpenPermalinkToLine,
OpenUrl,
OpenFile,
Outdent,
PageDown,
PageUp,
@@ -307,7 +306,6 @@ gpui::actions!(
SortLinesCaseInsensitive,
SortLinesCaseSensitive,
SplitSelectionIntoLines,
SwitchSourceHeader,
Tab,
TabPrev,
ToggleAutoSignatureHelp,

View File

@@ -1,93 +0,0 @@
use std::path::PathBuf;
use anyhow::Context as _;
use gpui::{View, ViewContext, WindowContext};
use language::Language;
use url::Url;
use crate::lsp_ext::find_specific_language_server_in_selection;
use crate::{element::register_action, Editor, SwitchSourceHeader};
static CLANGD_SERVER_NAME: &str = "clangd";
fn is_c_language(language: &Language) -> bool {
return language.name().as_ref() == "C++" || language.name().as_ref() == "C";
}
pub fn switch_source_header(
editor: &mut Editor,
_: &SwitchSourceHeader,
cx: &mut ViewContext<'_, Editor>,
) {
let Some(project) = &editor.project else {
return;
};
let Some(workspace) = editor.workspace() else {
return;
};
let Some((_, _, server_to_query, buffer)) =
find_specific_language_server_in_selection(&editor, cx, &is_c_language, CLANGD_SERVER_NAME)
else {
return;
};
let project = project.clone();
let buffer_snapshot = buffer.read(cx).snapshot();
let source_file = buffer_snapshot
.file()
.unwrap()
.file_name(cx)
.to_str()
.unwrap()
.to_owned();
let switch_source_header_task = project.update(cx, |project, cx| {
project.request_lsp(
buffer,
project::LanguageServerToQuery::Other(server_to_query),
project::lsp_ext_command::SwitchSourceHeader,
cx,
)
});
cx.spawn(|_editor, mut cx| async move {
let switch_source_header = switch_source_header_task
.await
.with_context(|| format!("Switch source/header LSP request for path \"{}\" failed", source_file))?;
if switch_source_header.0.is_empty() {
log::info!("Clangd returned an empty string when requesting to switch source/header from \"{}\"", source_file);
return Ok(());
}
let goto = Url::parse(&switch_source_header.0).with_context(|| {
format!(
"Parsing URL \"{}\" returned from switch source/header failed",
switch_source_header.0
)
})?;
workspace
.update(&mut cx, |workspace, view_cx| {
workspace.open_abs_path(PathBuf::from(goto.path()), false, view_cx)
})
.with_context(|| {
format!(
"Switch source/header could not open \"{}\" in workspace",
goto.path()
)
})?
.await
.map(|_| ())
})
.detach_and_log_err(cx);
}
pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
if editor.update(cx, |e, cx| {
find_specific_language_server_in_selection(e, cx, &is_c_language, CLANGD_SERVER_NAME)
.is_some()
}) {
register_action(editor, cx, switch_source_header);
}
}

View File

@@ -15,7 +15,6 @@
pub mod actions;
mod blame_entry_tooltip;
mod blink_manager;
mod clangd_ext;
mod debounced_delay;
pub mod display_map;
mod editor_settings;
@@ -31,7 +30,6 @@ mod inlay_hint_cache;
mod inline_completion_provider;
pub mod items;
mod linked_editing_ranges;
mod lsp_ext;
mod mouse_context_menu;
pub mod movement;
mod persistence;
@@ -99,7 +97,7 @@ use language::{point_to_lsp, BufferRow, Runnable, RunnableRange};
use linked_editing_ranges::refresh_linked_ranges;
use task::{ResolvedTask, TaskTemplate, TaskVariables};
use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
use hover_links::{HoverLink, HoveredLinkState, InlayHighlight};
pub use lsp::CompletionContext;
use lsp::{
CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity, InsertTextFormat,
@@ -298,8 +296,7 @@ pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace.register_action(Editor::new_file);
workspace.register_action(Editor::new_file_vertical);
workspace.register_action(Editor::new_file_horizontal);
workspace.register_action(Editor::new_file_in_direction);
},
)
.detach();
@@ -2069,29 +2066,14 @@ impl Editor {
})
}
fn new_file_vertical(
pub fn new_file_in_direction(
workspace: &mut Workspace,
_: &workspace::NewFileSplitVertical,
cx: &mut ViewContext<Workspace>,
) {
Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), cx)
}
fn new_file_horizontal(
workspace: &mut Workspace,
_: &workspace::NewFileSplitHorizontal,
cx: &mut ViewContext<Workspace>,
) {
Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), cx)
}
fn new_file_in_direction(
workspace: &mut Workspace,
direction: SplitDirection,
action: &workspace::NewFileInDirection,
cx: &mut ViewContext<Workspace>,
) {
let project = workspace.project().clone();
let create = project.update(cx, |project, cx| project.create_buffer(cx));
let direction = action.0;
cx.spawn(|workspace, mut cx| async move {
let buffer = create.await?;
@@ -2217,7 +2199,7 @@ impl Editor {
}),
provider: Arc::new(provider),
});
self.refresh_inline_completion(false, false, cx);
self.refresh_inline_completion(false, cx);
}
pub fn placeholder_text(&self, _cx: &WindowContext) -> Option<&str> {
@@ -3335,7 +3317,7 @@ impl Editor {
let trigger_in_words = !had_active_inline_completion;
this.trigger_completion_on_input(&text, trigger_in_words, cx);
linked_editing_ranges::refresh_linked_ranges(this, cx);
this.refresh_inline_completion(true, false, cx);
this.refresh_inline_completion(true, cx);
});
}
@@ -3521,7 +3503,7 @@ impl Editor {
.collect();
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
this.refresh_inline_completion(true, false, cx);
this.refresh_inline_completion(true, cx);
});
}
@@ -4409,7 +4391,7 @@ impl Editor {
})
}
this.refresh_inline_completion(true, false, cx);
this.refresh_inline_completion(true, cx);
});
let show_new_completions_on_confirm = completion
@@ -4909,19 +4891,17 @@ impl Editor {
None
}
pub fn refresh_inline_completion(
fn refresh_inline_completion(
&mut self,
debounce: bool,
user_requested: bool,
cx: &mut ViewContext<Self>,
) -> Option<()> {
let provider = self.inline_completion_provider()?;
let cursor = self.selections.newest_anchor().head();
let (buffer, cursor_buffer_position) =
self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
if !user_requested
&& (!self.show_inline_completions
|| !provider.is_enabled(&buffer, cursor_buffer_position, cx))
if !self.show_inline_completions
|| !provider.is_enabled(&buffer, cursor_buffer_position, cx)
{
self.discard_inline_completion(false, cx);
return None;
@@ -4955,7 +4935,7 @@ impl Editor {
pub fn show_inline_completion(&mut self, _: &ShowInlineCompletion, cx: &mut ViewContext<Self>) {
if !self.has_active_inline_completion(cx) {
self.refresh_inline_completion(false, true, cx);
self.refresh_inline_completion(false, cx);
return;
}
@@ -4984,7 +4964,7 @@ impl Editor {
if self.has_active_inline_completion(cx) {
self.cycle_inline_completion(Direction::Next, cx);
} else {
let is_copilot_disabled = self.refresh_inline_completion(false, true, cx).is_none();
let is_copilot_disabled = self.refresh_inline_completion(false, cx).is_none();
if is_copilot_disabled {
cx.propagate();
}
@@ -4999,7 +4979,7 @@ impl Editor {
if self.has_active_inline_completion(cx) {
self.cycle_inline_completion(Direction::Prev, cx);
} else {
let is_copilot_disabled = self.refresh_inline_completion(false, true, cx).is_none();
let is_copilot_disabled = self.refresh_inline_completion(false, cx).is_none();
if is_copilot_disabled {
cx.propagate();
}
@@ -5027,7 +5007,7 @@ impl Editor {
self.change_selections(None, cx, |s| s.select_ranges([range]))
}
self.insert_with_autoindent_mode(&completion.text.to_string(), None, cx);
self.refresh_inline_completion(true, true, cx);
self.refresh_inline_completion(true, cx);
cx.notify();
}
@@ -5063,7 +5043,7 @@ impl Editor {
}
self.insert_with_autoindent_mode(&partial_completion, None, cx);
self.refresh_inline_completion(true, true, cx);
self.refresh_inline_completion(true, cx);
cx.notify();
}
}
@@ -5529,7 +5509,7 @@ impl Editor {
this.edit(edits, None, cx);
})
}
this.refresh_inline_completion(true, false, cx);
this.refresh_inline_completion(true, cx);
linked_editing_ranges::refresh_linked_ranges(this, cx);
});
}
@@ -5548,7 +5528,7 @@ impl Editor {
})
});
this.insert("", cx);
this.refresh_inline_completion(true, false, cx);
this.refresh_inline_completion(true, cx);
});
}
@@ -5635,7 +5615,7 @@ impl Editor {
self.transact(cx, |this, cx| {
this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
this.refresh_inline_completion(true, false, cx);
this.refresh_inline_completion(true, cx);
});
}
@@ -6803,7 +6783,7 @@ impl Editor {
}
self.request_autoscroll(Autoscroll::fit(), cx);
self.unmark_text(cx);
self.refresh_inline_completion(true, false, cx);
self.refresh_inline_completion(true, cx);
cx.emit(EditorEvent::Edited { transaction_id });
cx.emit(EditorEvent::TransactionUndone { transaction_id });
}
@@ -6824,7 +6804,7 @@ impl Editor {
}
self.request_autoscroll(Autoscroll::fit(), cx);
self.unmark_text(cx);
self.refresh_inline_completion(true, false, cx);
self.refresh_inline_completion(true, cx);
cx.emit(EditorEvent::Edited { transaction_id });
}
}
@@ -9195,38 +9175,6 @@ impl Editor {
.detach();
}
pub fn open_file(&mut self, _: &OpenFile, cx: &mut ViewContext<Self>) {
let Some(workspace) = self.workspace() else {
return;
};
let position = self.selections.newest_anchor().head();
let Some((buffer, buffer_position)) =
self.buffer.read(cx).text_anchor_for_position(position, cx)
else {
return;
};
let Some(project) = self.project.clone() else {
return;
};
cx.spawn(|_, mut cx| async move {
let result = find_file(&buffer, project, buffer_position, &mut cx).await;
if let Some((_, path)) = result {
workspace
.update(&mut cx, |workspace, cx| {
workspace.open_resolved_path(path, cx)
})?
.await?;
}
anyhow::Ok(())
})
.detach();
}
pub(crate) fn navigate_to_hover_links(
&mut self,
kind: Option<GotoDefinitionKind>,
@@ -9237,49 +9185,21 @@ impl Editor {
// If there is one definition, just open it directly
if definitions.len() == 1 {
let definition = definitions.pop().unwrap();
enum TargetTaskResult {
Location(Option<Location>),
AlreadyNavigated,
}
let target_task = match definition {
HoverLink::Text(link) => {
Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
}
HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
HoverLink::InlayHint(lsp_location, server_id) => {
let computation = self.compute_target_location(lsp_location, server_id, cx);
cx.background_executor().spawn(async move {
let location = computation.await?;
Ok(TargetTaskResult::Location(location))
})
self.compute_target_location(lsp_location, server_id, cx)
}
HoverLink::Url(url) => {
cx.open_url(&url);
Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
}
HoverLink::File(path) => {
if let Some(workspace) = self.workspace() {
cx.spawn(|_, mut cx| async move {
workspace
.update(&mut cx, |workspace, cx| {
workspace.open_resolved_path(path, cx)
})?
.await
.map(|_| TargetTaskResult::AlreadyNavigated)
})
} else {
Task::ready(Ok(TargetTaskResult::Location(None)))
}
Task::ready(Ok(None))
}
};
cx.spawn(|editor, mut cx| async move {
let target = match target_task.await.context("target resolution task")? {
TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
TargetTaskResult::Location(None) => return Ok(Navigated::No),
TargetTaskResult::Location(Some(target)) => target,
let target = target_task.await.context("target resolution task")?;
let Some(target) = target else {
return Ok(Navigated::No);
};
editor.update(&mut cx, |editor, cx| {
let Some(workspace) = editor.workspace() else {
return Navigated::No;
@@ -9357,7 +9277,6 @@ impl Editor {
}),
HoverLink::InlayHint(_, _) => None,
HoverLink::Url(_) => None,
HoverLink::File(_) => None,
})
.unwrap_or(tab_kind.to_string());
let location_tasks = definitions
@@ -9368,7 +9287,6 @@ impl Editor {
editor.compute_target_location(lsp_location, server_id, cx)
}
HoverLink::Url(_) => Task::ready(Ok(None)),
HoverLink::File(_) => Task::ready(Ok(None)),
})
.collect::<Vec<_>>();
(title, location_tasks, editor.workspace().clone())
@@ -11500,7 +11418,7 @@ impl Editor {
fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
self.tasks_update_task = Some(self.refresh_runnables(cx));
self.refresh_inline_completion(true, false, cx);
self.refresh_inline_completion(true, cx);
self.refresh_inlay_hints(
InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
self.selections.newest_anchor().head(),

View File

@@ -165,7 +165,6 @@ impl EditorElement {
});
crate::rust_analyzer_ext::apply_related_actions(view, cx);
crate::clangd_ext::apply_related_actions(view, cx);
register_action(view, cx, Editor::move_left);
register_action(view, cx, Editor::move_right);
register_action(view, cx, Editor::move_down);
@@ -331,7 +330,6 @@ impl EditorElement {
.detach_and_log_err(cx);
});
register_action(view, cx, Editor::open_url);
register_action(view, cx, Editor::open_file);
register_action(view, cx, Editor::fold);
register_action(view, cx, Editor::fold_at);
register_action(view, cx, Editor::unfold_lines);

View File

@@ -9,8 +9,8 @@ use language::{Bias, ToOffset};
use linkify::{LinkFinder, LinkKind};
use lsp::LanguageServerId;
use project::{
HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink, Project,
ResolveState, ResolvedPath,
HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink,
ResolveState,
};
use std::ops::Range;
use theme::ActiveTheme as _;
@@ -63,7 +63,6 @@ impl RangeInEditor {
#[derive(Debug, Clone)]
pub enum HoverLink {
Url(String),
File(ResolvedPath),
Text(LocationLink),
InlayHint(lsp::Location, LanguageServerId),
}
@@ -523,54 +522,35 @@ pub fn show_link_definition(
})
.ok()
} else if let Some(project) = project {
if let Some((filename_range, filename)) =
find_file(&buffer, project.clone(), buffer_position, &mut cx).await
{
let range = maybe!({
let start =
snapshot.anchor_in_excerpt(excerpt_id, filename_range.start)?;
let end =
snapshot.anchor_in_excerpt(excerpt_id, filename_range.end)?;
Some(RangeInEditor::Text(start..end))
});
// query the LSP for definition info
project
.update(&mut cx, |project, cx| match preferred_kind {
LinkDefinitionKind::Symbol => {
project.definition(&buffer, buffer_position, cx)
}
Some((range, vec![HoverLink::File(filename)]))
} else {
// query the LSP for definition info
project
.update(&mut cx, |project, cx| match preferred_kind {
LinkDefinitionKind::Symbol => {
project.definition(&buffer, buffer_position, cx)
}
LinkDefinitionKind::Type => {
project.type_definition(&buffer, buffer_position, cx)
}
})?
.await
.ok()
.map(|definition_result| {
(
definition_result.iter().find_map(|link| {
link.origin.as_ref().and_then(|origin| {
let start = snapshot.anchor_in_excerpt(
excerpt_id,
origin.range.start,
)?;
let end = snapshot.anchor_in_excerpt(
excerpt_id,
origin.range.end,
)?;
Some(RangeInEditor::Text(start..end))
})
}),
definition_result
.into_iter()
.map(HoverLink::Text)
.collect(),
)
})
}
LinkDefinitionKind::Type => {
project.type_definition(&buffer, buffer_position, cx)
}
})?
.await
.ok()
.map(|definition_result| {
(
definition_result.iter().find_map(|link| {
link.origin.as_ref().and_then(|origin| {
let start = snapshot.anchor_in_excerpt(
excerpt_id,
origin.range.start,
)?;
let end = snapshot
.anchor_in_excerpt(excerpt_id, origin.range.end)?;
Some(RangeInEditor::Text(start..end))
})
}),
definition_result.into_iter().map(HoverLink::Text).collect(),
)
})
} else {
None
}
@@ -706,116 +686,6 @@ pub(crate) fn find_url(
None
}
pub(crate) async fn find_file(
buffer: &Model<language::Buffer>,
project: Model<Project>,
position: text::Anchor,
cx: &mut AsyncWindowContext,
) -> Option<(Range<text::Anchor>, ResolvedPath)> {
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()).ok()?;
let (range, candidate_file_path) = surrounding_filename(snapshot, position)?;
let existing_path = project
.update(cx, |project, cx| {
project.resolve_existing_file_path(&candidate_file_path, &buffer, cx)
})
.ok()?
.await?;
Some((range, existing_path))
}
fn surrounding_filename(
snapshot: language::BufferSnapshot,
position: text::Anchor,
) -> Option<(Range<text::Anchor>, String)> {
const LIMIT: usize = 2048;
let offset = position.to_offset(&snapshot);
let mut token_start = offset;
let mut token_end = offset;
let mut found_start = false;
let mut found_end = false;
let mut inside_quotes = false;
let mut filename = String::new();
let mut backwards = snapshot.reversed_chars_at(offset).take(LIMIT).peekable();
while let Some(ch) = backwards.next() {
// Escaped whitespace
if ch.is_whitespace() && backwards.peek() == Some(&'\\') {
filename.push(ch);
token_start -= ch.len_utf8();
backwards.next();
token_start -= '\\'.len_utf8();
continue;
}
if ch.is_whitespace() {
found_start = true;
break;
}
if (ch == '"' || ch == '\'') && !inside_quotes {
found_start = true;
inside_quotes = true;
break;
}
filename.push(ch);
token_start -= ch.len_utf8();
}
if !found_start && token_start != 0 {
return None;
}
filename = filename.chars().rev().collect();
let mut forwards = snapshot
.chars_at(offset)
.take(LIMIT - (offset - token_start))
.peekable();
while let Some(ch) = forwards.next() {
// Skip escaped whitespace
if ch == '\\' && forwards.peek().map_or(false, |ch| ch.is_whitespace()) {
token_end += ch.len_utf8();
let whitespace = forwards.next().unwrap();
token_end += whitespace.len_utf8();
filename.push(whitespace);
continue;
}
if ch.is_whitespace() {
found_end = true;
break;
}
if ch == '"' || ch == '\'' {
// If we're inside quotes, we stop when we come across the next quote
if inside_quotes {
found_end = true;
break;
} else {
// Otherwise, we skip the quote
inside_quotes = true;
continue;
}
}
filename.push(ch);
token_end += ch.len_utf8();
}
if !found_end && (token_end - token_start >= LIMIT) {
return None;
}
if filename.is_empty() {
return None;
}
let range = snapshot.anchor_before(token_start)..snapshot.anchor_after(token_end);
Some((range, filename))
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1398,184 +1268,4 @@ mod tests {
cx.simulate_click(screen_coord, Modifiers::secondary_key());
assert_eq!(cx.opened_url(), Some("https://zed.dev/releases".into()));
}
#[gpui::test]
async fn test_surrounding_filename(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
..Default::default()
},
cx,
)
.await;
let test_cases = [
("file ˇ name", None),
("ˇfile name", Some("file")),
("file ˇname", Some("name")),
("fiˇle name", Some("file")),
("filenˇame", Some("filename")),
// Absolute path
("foobar ˇ/home/user/f.txt", Some("/home/user/f.txt")),
("foobar /home/useˇr/f.txt", Some("/home/user/f.txt")),
// Windows
("C:\\Useˇrs\\user\\f.txt", Some("C:\\Users\\user\\f.txt")),
// Whitespace
("ˇfile\\ -\\ name.txt", Some("file - name.txt")),
("file\\ -\\ naˇme.txt", Some("file - name.txt")),
// Tilde
("ˇ~/file.txt", Some("~/file.txt")),
("~/fiˇle.txt", Some("~/file.txt")),
// Double quotes
("\"fˇile.txt\"", Some("file.txt")),
("ˇ\"file.txt\"", Some("file.txt")),
("ˇ\"fi\\ le.txt\"", Some("fi le.txt")),
// Single quotes
("'fˇile.txt'", Some("file.txt")),
("ˇ'file.txt'", Some("file.txt")),
("ˇ'fi\\ le.txt'", Some("fi le.txt")),
];
for (input, expected) in test_cases {
cx.set_state(input);
let (position, snapshot) = cx.editor(|editor, cx| {
let positions = editor.selections.newest_anchor().head().text_anchor;
let snapshot = editor
.buffer()
.clone()
.read(cx)
.as_singleton()
.unwrap()
.read(cx)
.snapshot();
(positions, snapshot)
});
let result = surrounding_filename(snapshot, position);
if let Some(expected) = expected {
assert!(result.is_some(), "Failed to find file path: {}", input);
let (_, path) = result.unwrap();
assert_eq!(&path, expected, "Incorrect file path for input: {}", input);
} else {
assert!(
result.is_none(),
"Expected no result, but got one: {:?}",
result
);
}
}
}
#[gpui::test]
async fn test_hover_filenames(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
..Default::default()
},
cx,
)
.await;
// Insert a new file
let fs = cx.update_workspace(|workspace, cx| workspace.project().read(cx).fs().clone());
fs.as_fake()
.insert_file("/root/dir/file2.rs", "This is file2.rs".as_bytes().to_vec())
.await;
cx.set_state(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to /root/dir/file2.rs if project is local.ˇ
"});
// File does not exist
let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that dˇoes_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to /root/dir/file2.rs if project is local.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
// No highlight
cx.update_editor(|editor, cx| {
assert!(editor
.snapshot(cx)
.text_highlight_ranges::<HoveredLinkState>()
.unwrap_or_default()
.1
.is_empty());
});
// Moving the mouse over a file that does exist should highlight it.
let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to fˇile2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to /root/dir/file2.rs if project is local.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to «file2.rsˇ» if you want.
Or go to ../dir/file2.rs if you want.
Or go to /root/dir/file2.rs if project is local.
"});
// Moving the mouse over a relative path that does exist should highlight it
let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/fˇile2.rs if you want.
Or go to /root/dir/file2.rs if project is local.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to «../dir/file2.rsˇ» if you want.
Or go to /root/dir/file2.rs if project is local.
"});
// Moving the mouse over an absolute path that does exist should highlight it
let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to /root/diˇr/file2.rs if project is local.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to «/root/dir/file2.rsˇ» if project is local.
"});
cx.simulate_click(screen_coord, Modifiers::secondary_key());
cx.update_workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
cx.update_workspace(|workspace, cx| {
let active_editor = workspace.active_item_as::<Editor>(cx).unwrap();
let buffer = active_editor
.read(cx)
.buffer()
.read(cx)
.as_singleton()
.unwrap();
let file = buffer.read(cx).file().unwrap();
let file_path = file.as_local().unwrap().abs_path(cx);
assert_eq!(file_path.to_str().unwrap(), "/root/dir/file2.rs");
});
}
}

View File

@@ -1,54 +0,0 @@
use std::sync::Arc;
use crate::Editor;
use gpui::{Model, WindowContext};
use language::Buffer;
use language::Language;
use lsp::LanguageServerId;
use multi_buffer::Anchor;
pub(crate) fn find_specific_language_server_in_selection<F>(
editor: &Editor,
cx: &WindowContext,
filter_language: F,
language_server_name: &str,
) -> Option<(Anchor, Arc<Language>, LanguageServerId, Model<Buffer>)>
where
F: Fn(&Language) -> bool,
{
let Some(project) = &editor.project else {
return None;
};
let multibuffer = editor.buffer().read(cx);
editor
.selections
.disjoint_anchors()
.into_iter()
.filter(|selection| selection.start == selection.end)
.filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
.filter_map(|(buffer_id, trigger_anchor)| {
let buffer = multibuffer.buffer(buffer_id)?;
let language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
if !filter_language(&language) {
return None;
}
Some((trigger_anchor, language, buffer))
})
.find_map(|(trigger_anchor, language, buffer)| {
project
.read(cx)
.language_servers_for_buffer(buffer.read(cx), cx)
.find_map(|(adapter, server)| {
if adapter.name.0.as_ref() == language_server_name {
Some((
trigger_anchor,
Arc::clone(&language),
server.server_id(),
buffer.clone(),
))
} else {
None
}
})
})
}

View File

@@ -1,3 +1,5 @@
use std::sync::Arc;
use anyhow::Context as _;
use gpui::{Context, View, ViewContext, VisualContext, WindowContext};
use language::Language;
@@ -5,24 +7,22 @@ use multi_buffer::MultiBuffer;
use project::lsp_ext_command::ExpandMacro;
use text::ToPointUtf16;
use crate::{
element::register_action, lsp_ext::find_specific_language_server_in_selection, Editor,
ExpandMacroRecursively,
};
static RUST_ANALYZER_NAME: &str = "rust-analyzer";
fn is_rust_language(language: &Language) -> bool {
language.name().as_ref() == "Rust"
}
use crate::{element::register_action, Editor, ExpandMacroRecursively};
pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
if editor
.update(cx, |e, cx| {
find_specific_language_server_in_selection(e, cx, &is_rust_language, RUST_ANALYZER_NAME)
})
.is_some()
{
let is_rust_related = editor.update(cx, |editor, cx| {
editor
.buffer()
.read(cx)
.all_buffers()
.iter()
.any(|b| match b.read(cx).language() {
Some(l) => is_rust_language(l),
None => false,
})
});
if is_rust_related {
register_action(editor, cx, expand_macro_recursively);
}
}
@@ -42,13 +42,39 @@ pub fn expand_macro_recursively(
return;
};
let Some((trigger_anchor, rust_language, server_to_query, buffer)) =
find_specific_language_server_in_selection(
&editor,
cx,
&is_rust_language,
RUST_ANALYZER_NAME,
)
let multibuffer = editor.buffer().read(cx);
let Some((trigger_anchor, rust_language, server_to_query, buffer)) = editor
.selections
.disjoint_anchors()
.into_iter()
.filter(|selection| selection.start == selection.end)
.filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
.filter_map(|(buffer_id, trigger_anchor)| {
let buffer = multibuffer.buffer(buffer_id)?;
let rust_language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
if !is_rust_language(&rust_language) {
return None;
}
Some((trigger_anchor, rust_language, buffer))
})
.find_map(|(trigger_anchor, rust_language, buffer)| {
project
.read(cx)
.language_servers_for_buffer(buffer.read(cx), cx)
.find_map(|(adapter, server)| {
if adapter.name.0.as_ref() == "rust-analyzer" {
Some((
trigger_anchor,
Arc::clone(&rust_language),
server.server_id(),
buffer.clone(),
))
} else {
None
}
})
})
else {
return;
};
@@ -94,3 +120,7 @@ pub fn expand_macro_recursively(
})
.detach_and_log_err(cx);
}
fn is_rust_language(language: &Language) -> bool {
language.name().as_ref() == "Rust"
}

View File

@@ -7,7 +7,7 @@
//!
//! # Element Basics
//!
//! Elements are constructed by calling [`Render::render()`] on the root view of the window,
//! Elements are constructed by calling [`Render::render()`] on the root view of the window, which
//! which recursively constructs the element tree from the current state of the application,.
//! These elements are then laid out by Taffy, and painted to the screen according to their own
//! implementation of [`Element::paint()`]. Before the start of the next frame, the entire element

View File

@@ -301,7 +301,7 @@ impl DirectWriteState {
continue;
};
if fonts.GetFontCount() == 0 {
log::error!("No matching font found for {}", family_name);
log::error!("No mathcing font find for {}", family_name);
continue;
}
let font = fonts.GetFontFaceReference(0)?.CreateFontFace()?;

View File

@@ -758,7 +758,7 @@ fn handle_dpi_changed_msg(
let new_dpi = wparam.loword() as f32;
let mut lock = state_ptr.state.borrow_mut();
lock.scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
lock.border_offset.update(handle).log_err();
lock.border_offset.udpate(handle).log_err();
drop(lock);
let rect = unsafe { &*(lparam.0 as *const RECT) };
@@ -796,7 +796,7 @@ fn handle_dpi_changed_msg(
fn handle_display_change_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
// NOTE:
// Even the `lParam` holds the resolution of the screen, we just ignore it.
// Because WM_DPICHANGED, WM_MOVE, WM_SIZE will come first, window reposition and resize
// Because WM_DPICHANGED, WM_MOVE, WM_SIEZ will come first, window reposition and resize
// are handled there.
// So we only care about if monitor is disconnected.
let previous_monitor = state_ptr.as_ref().state.borrow().display;
@@ -1087,7 +1087,7 @@ fn handle_system_settings_changed(
// mouse double click
lock.click_state.system_update();
// window border offset
lock.border_offset.update(handle).log_err();
lock.border_offset.udpate(handle).log_err();
Some(0)
}

View File

@@ -166,7 +166,7 @@ impl WindowsWindowState {
/// get the logical size of the app's drawable area.
///
/// Currently, GPUI uses the logical size of the app to handle mouse interactions (such as
/// Currently, GPUI uses logical size of the app to handle mouse interactions (such as
/// whether the mouse collides with other elements of GPUI).
fn content_size(&self) -> Size<Pixels> {
self.logical_size
@@ -187,13 +187,13 @@ impl WindowsWindowState {
}
fn title_bar_height(&self) -> Pixels {
// todo(windows) this is hardcoded to match the ui title bar
// todo(windows) this is hard set to match the ui title bar
// in the future the ui title bar component will report the size
px(32.) + self.title_bar_top_offset()
}
pub(crate) fn caption_button_width(&self) -> Pixels {
// todo(windows) this is hardcoded to match the ui title bar
// todo(windows) this is hard set to match the ui title bar
// in the future the ui title bar component will report the size
px(36.)
}
@@ -343,8 +343,8 @@ impl WindowsWindow {
};
let mut lock = state_ptr.state.borrow_mut();
let bounds = bounds.to_device_pixels(lock.scale_factor);
lock.border_offset.update(raw_hwnd)?;
placement.rcNormalPosition = calculate_window_rect(bounds, lock.border_offset);
lock.border_offset.udpate(raw_hwnd)?;
placement.rcNormalPosition = calcualte_window_rect(bounds, lock.border_offset);
drop(lock);
SetWindowPlacement(raw_hwnd, &placement)?;
}
@@ -404,7 +404,7 @@ impl PlatformWindow for WindowsWindow {
/// get the logical size of the app's drawable area.
///
/// Currently, GPUI uses the logical size of the app to handle mouse interactions (such as
/// Currently, GPUI uses logical size of the app to handle mouse interactions (such as
/// whether the mouse collides with other elements of GPUI).
fn content_size(&self) -> Size<Pixels> {
self.0.state.borrow().content_size()
@@ -897,7 +897,7 @@ pub(crate) struct WindowBorderOffset {
}
impl WindowBorderOffset {
pub(crate) fn update(&mut self, hwnd: HWND) -> anyhow::Result<()> {
pub(crate) fn udpate(&mut self, hwnd: HWND) -> anyhow::Result<()> {
let window_rect = unsafe {
let mut rect = std::mem::zeroed();
GetWindowRect(hwnd, &mut rect)?;
@@ -1015,9 +1015,9 @@ fn register_drag_drop(state_ptr: Rc<WindowsWindowStatePtr>) -> Result<()> {
Ok(())
}
fn calculate_window_rect(bounds: Bounds<DevicePixels>, border_offset: WindowBorderOffset) -> RECT {
fn calcualte_window_rect(bounds: Bounds<DevicePixels>, border_offset: WindowBorderOffset) -> RECT {
// NOTE:
// The reason we're not using `AdjustWindowRectEx()` here is
// The reason that not using `AdjustWindowRectEx()` here is
// that the size reported by this function is incorrect.
// You can test it, and there are similar discussions online.
// See: https://stackoverflow.com/questions/12423584/how-to-set-exact-client-size-for-overlapped-window-winapi
@@ -1032,11 +1032,11 @@ fn calculate_window_rect(bounds: Bounds<DevicePixels>, border_offset: WindowBord
let left_offset = border_offset.width_offset / 2;
let top_offset = border_offset.height_offset / 2;
let right_offset = border_offset.width_offset - left_offset;
let bottom_offset = border_offset.height_offset - top_offset;
let bottom_offet = border_offset.height_offset - top_offset;
rect.left -= left_offset;
rect.top -= top_offset;
rect.right += right_offset;
rect.bottom += bottom_offset;
rect.bottom += bottom_offet;
rect
}
@@ -1048,11 +1048,11 @@ fn calculate_client_rect(
let left_offset = border_offset.width_offset / 2;
let top_offset = border_offset.height_offset / 2;
let right_offset = border_offset.width_offset - left_offset;
let bottom_offset = border_offset.height_offset - top_offset;
let bottom_offet = border_offset.height_offset - top_offset;
let left = rect.left + left_offset;
let top = rect.top + top_offset;
let right = rect.right - right_offset;
let bottom = rect.bottom - bottom_offset;
let bottom = rect.bottom - bottom_offet;
let physical_size = size(DevicePixels(right - left), DevicePixels(bottom - top));
Bounds {
origin: logical_point(left as f32, top as f32, scale_factor),

View File

@@ -309,7 +309,7 @@ mod tests {
#[cfg(target_os = "macos")]
use crate as gpui;
// These seem to vary wildly based on the text system.
// These seem to vary wildly based on the the text system.
#[cfg(target_os = "macos")]
#[crate::test]
fn test_wrap_shaped_line(cx: &mut TestAppContext) {

View File

@@ -383,7 +383,7 @@ pub trait File: Send + Sync {
/// The file associated with a buffer, in the case where the file is on the local disk.
pub trait LocalFile: File {
/// Returns the absolute path of this file
/// Returns the absolute path of this file.
fn abs_path(&self, cx: &AppContext) -> PathBuf;
/// Loads the file's contents from disk.

View File

@@ -158,24 +158,6 @@ pub struct CachedLspAdapter {
cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>,
}
impl Debug for CachedLspAdapter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CachedLspAdapter")
.field("name", &self.name)
.field(
"disk_based_diagnostic_sources",
&self.disk_based_diagnostic_sources,
)
.field(
"disk_based_diagnostics_progress_token",
&self.disk_based_diagnostics_progress_token,
)
.field("language_ids", &self.language_ids)
.field("reinstall_attempt_count", &self.reinstall_attempt_count)
.finish_non_exhaustive()
}
}
impl CachedLspAdapter {
pub fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
let name = adapter.name();

View File

@@ -445,21 +445,6 @@ impl LanguageModel for CloudLanguageModel {
self.model.max_token_count()
}
fn cache_configuration(&self) -> Option<LanguageModelCacheConfiguration> {
match &self.model {
CloudModel::Anthropic(model) => {
model
.cache_configuration()
.map(|cache| LanguageModelCacheConfiguration {
max_cache_anchors: cache.max_cache_anchors,
should_speculate: cache.should_speculate,
min_total_token: cache.min_total_token,
})
}
CloudModel::OpenAi(_) | CloudModel::Google(_) | CloudModel::Zed(_) => None,
}
}
fn count_tokens(
&self,
request: LanguageModelRequest,

View File

@@ -8,9 +8,7 @@ use gpui::{
ViewContext, VisualContext, WeakModel, WindowContext,
};
use language::{LanguageServerId, LanguageServerName};
use lsp::{
notification::SetTrace, IoKind, LanguageServer, MessageType, SetTraceParams, TraceValue,
};
use lsp::{notification::SetTrace, IoKind, LanguageServer, SetTraceParams, TraceValue};
use project::{search::SearchQuery, Project};
use std::{borrow::Cow, sync::Arc};
use ui::{prelude::*, Button, Checkbox, ContextMenu, Label, PopoverMenu, Selection};
@@ -36,76 +34,15 @@ struct ProjectState {
_subscriptions: [gpui::Subscription; 2],
}
trait Message: AsRef<str> {
type Level: Copy + std::fmt::Debug;
fn should_include(&self, _: Self::Level) -> bool {
true
}
}
struct LogMessage {
message: String,
typ: MessageType,
}
impl AsRef<str> for LogMessage {
fn as_ref(&self) -> &str {
&self.message
}
}
impl Message for LogMessage {
type Level = MessageType;
fn should_include(&self, level: Self::Level) -> bool {
match (self.typ, level) {
(MessageType::ERROR, _) => true,
(_, MessageType::ERROR) => false,
(MessageType::WARNING, _) => true,
(_, MessageType::WARNING) => false,
(MessageType::INFO, _) => true,
(_, MessageType::INFO) => false,
_ => true,
}
}
}
struct TraceMessage {
message: String,
}
impl AsRef<str> for TraceMessage {
fn as_ref(&self) -> &str {
&self.message
}
}
impl Message for TraceMessage {
type Level = ();
}
struct RpcMessage {
message: String,
}
impl AsRef<str> for RpcMessage {
fn as_ref(&self) -> &str {
&self.message
}
}
impl Message for RpcMessage {
type Level = ();
}
struct LanguageServerState {
kind: LanguageServerKind,
log_messages: VecDeque<LogMessage>,
trace_messages: VecDeque<TraceMessage>,
log_messages: VecDeque<String>,
trace_messages: VecDeque<String>,
rpc_state: Option<LanguageServerRpcState>,
trace_level: TraceValue,
log_level: MessageType,
io_logs_subscription: Option<lsp::Subscription>,
_io_logs_subscription: Option<lsp::Subscription>,
_lsp_logs_subscription: Option<lsp::Subscription>,
_lsp_trace_subscription: Option<lsp::Subscription>,
}
enum LanguageServerKind {
@@ -123,7 +60,7 @@ impl LanguageServerKind {
}
struct LanguageServerRpcState {
rpc_messages: VecDeque<RpcMessage>,
rpc_messages: VecDeque<String>,
last_message_kind: Option<MessageKind>,
}
@@ -227,7 +164,6 @@ impl LogStore {
.update(&mut cx, |this, cx| {
this.add_language_server_log(
server_id,
MessageType::LOG,
&params.message,
cx,
);
@@ -239,8 +175,7 @@ impl LogStore {
LanguageServerKind::Global {
name: LanguageServerName(Arc::from("copilot")),
},
server.server_id(),
Some(server.clone()),
server.clone(),
cx,
);
}
@@ -291,8 +226,7 @@ impl LogStore {
LanguageServerKind::Local {
project: project.downgrade(),
},
server.server_id(),
Some(server),
server,
cx,
);
}
@@ -300,23 +234,8 @@ impl LogStore {
project::Event::LanguageServerRemoved(id) => {
this.remove_language_server(*id, cx);
}
project::Event::LanguageServerLog(id, typ, message) => {
this.add_language_server(
LanguageServerKind::Local {
project: project.downgrade(),
},
*id,
None,
cx,
);
match typ {
project::LanguageServerLogType::Log(typ) => {
this.add_language_server_log(*id, *typ, message, cx);
}
project::LanguageServerLogType::Trace(_) => {
this.add_language_server_trace(*id, message, cx);
}
}
project::Event::LanguageServerLog(id, message) => {
this.add_language_server_log(*id, message, cx);
}
_ => {}
}),
@@ -335,56 +254,77 @@ impl LogStore {
fn add_language_server(
&mut self,
kind: LanguageServerKind,
server_id: LanguageServerId,
server: Option<Arc<LanguageServer>>,
server: Arc<LanguageServer>,
cx: &mut ModelContext<Self>,
) -> Option<&mut LanguageServerState> {
let server_state = self.language_servers.entry(server_id).or_insert_with(|| {
cx.notify();
LanguageServerState {
kind,
rpc_state: None,
log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
trace_level: TraceValue::Off,
log_level: MessageType::LOG,
io_logs_subscription: None,
}
});
let server_state = self
.language_servers
.entry(server.server_id())
.or_insert_with(|| {
cx.notify();
LanguageServerState {
kind,
rpc_state: None,
log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
trace_level: TraceValue::Off,
_io_logs_subscription: None,
_lsp_logs_subscription: None,
_lsp_trace_subscription: None,
}
});
if let Some(server) = server.filter(|_| server_state.io_logs_subscription.is_none()) {
let io_tx = self.io_tx.clone();
let server_id = server.server_id();
server_state.io_logs_subscription = Some(server.on_io(move |io_kind, message| {
io_tx
.unbounded_send((server_id, io_kind, message.to_string()))
.ok();
}));
if server.has_notification_handler::<lsp::notification::LogMessage>()
|| server.has_notification_handler::<lsp::notification::LogTrace>()
{
// Another event wants to re-add the server that was already added and subscribed to, avoid doing it again.
return Some(server_state);
}
let io_tx = self.io_tx.clone();
let server_id = server.server_id();
server_state._io_logs_subscription = Some(server.on_io(move |io_kind, message| {
io_tx
.unbounded_send((server_id, io_kind, message.to_string()))
.ok();
}));
let this = cx.handle().downgrade();
server_state._lsp_logs_subscription =
Some(server.on_notification::<lsp::notification::LogMessage, _>({
let this = this.clone();
move |params, mut cx| {
if let Some(this) = this.upgrade() {
this.update(&mut cx, |this, cx| {
this.add_language_server_log(server_id, &params.message, cx);
})
.ok();
}
}
}));
server_state._lsp_trace_subscription =
Some(server.on_notification::<lsp::notification::LogTrace, _>({
move |params, mut cx| {
if let Some(this) = this.upgrade() {
this.update(&mut cx, |this, cx| {
this.add_language_server_trace(server_id, &params.message, cx);
})
.ok();
}
}
}));
Some(server_state)
}
fn add_language_server_log(
&mut self,
id: LanguageServerId,
typ: MessageType,
message: &str,
cx: &mut ModelContext<Self>,
) -> Option<()> {
let language_server_state = self.get_language_server_state(id)?;
let log_lines = &mut language_server_state.log_messages;
Self::add_language_server_message(
log_lines,
id,
LogMessage {
message: message.trim_end().to_string(),
typ,
},
language_server_state.log_level,
LogKind::Logs,
cx,
);
Self::add_language_server_message(log_lines, id, message, LogKind::Logs, cx);
Some(())
}
@@ -397,39 +337,28 @@ impl LogStore {
let language_server_state = self.get_language_server_state(id)?;
let log_lines = &mut language_server_state.trace_messages;
Self::add_language_server_message(
log_lines,
id,
TraceMessage {
message: message.trim_end().to_string(),
},
(),
LogKind::Trace,
cx,
);
Self::add_language_server_message(log_lines, id, message, LogKind::Trace, cx);
Some(())
}
fn add_language_server_message<T: Message>(
log_lines: &mut VecDeque<T>,
fn add_language_server_message(
log_lines: &mut VecDeque<String>,
id: LanguageServerId,
message: T,
current_severity: <T as Message>::Level,
message: &str,
kind: LogKind,
cx: &mut ModelContext<Self>,
) {
while log_lines.len() >= MAX_STORED_LOG_ENTRIES {
log_lines.pop_front();
}
let entry: &str = message.as_ref();
let entry = entry.to_string();
let visible = message.should_include(current_severity);
log_lines.push_back(message);
if visible {
cx.emit(Event::NewServerLogEntry { id, entry, kind });
cx.notify();
}
let message = message.trim();
log_lines.push_back(message.to_string());
cx.emit(Event::NewServerLogEntry {
id,
entry: message.to_string(),
kind,
});
cx.notify();
}
fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut ModelContext<Self>) {
@@ -437,14 +366,12 @@ impl LogStore {
cx.notify();
}
fn server_logs(&self, server_id: LanguageServerId) -> Option<&VecDeque<LogMessage>> {
fn server_logs(&self, server_id: LanguageServerId) -> Option<&VecDeque<String>> {
Some(&self.language_servers.get(&server_id)?.log_messages)
}
fn server_trace(&self, server_id: LanguageServerId) -> Option<&VecDeque<TraceMessage>> {
fn server_trace(&self, server_id: LanguageServerId) -> Option<&VecDeque<String>> {
Some(&self.language_servers.get(&server_id)?.trace_messages)
}
fn server_ids_for_project<'a>(
&'a self,
lookup_project: &'a WeakModel<Project>,
@@ -498,7 +425,7 @@ impl LogStore {
IoKind::StdIn => false,
IoKind::StdErr => {
let message = format!("stderr: {}", message.trim());
self.add_language_server_log(language_server_id, MessageType::LOG, &message, cx);
self.add_language_server_log(language_server_id, &message, cx);
return Some(());
}
};
@@ -519,9 +446,7 @@ impl LogStore {
MessageKind::Send => SEND_LINE,
MessageKind::Receive => RECEIVE_LINE,
};
rpc_log_lines.push_back(RpcMessage {
message: line_before_message.to_string(),
});
rpc_log_lines.push_back(line_before_message.to_string());
cx.emit(Event::NewServerLogEntry {
id: language_server_id,
entry: line_before_message.to_string(),
@@ -533,9 +458,7 @@ impl LogStore {
rpc_log_lines.pop_front();
}
let message = message.trim();
rpc_log_lines.push_back(RpcMessage {
message: message.to_string(),
});
rpc_log_lines.push_back(message.to_string());
cx.emit(Event::NewServerLogEntry {
id: language_server_id,
entry: message.to_string(),
@@ -723,17 +646,11 @@ impl LspLogView {
}
fn show_logs_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
let typ = self
.log_store
.read_with(cx, |v, _| {
v.language_servers.get(&server_id).map(|v| v.log_level)
})
.unwrap_or(MessageType::LOG);
let log_contents = self
.log_store
.read(cx)
.server_logs(server_id)
.map(|v| log_contents(v, typ));
.map(log_contents);
if let Some(log_contents) = log_contents {
self.current_server_id = Some(server_id);
self.active_entry_kind = LogKind::Logs;
@@ -744,38 +661,12 @@ impl LspLogView {
}
cx.focus(&self.focus_handle);
}
fn update_log_level(
&self,
server_id: LanguageServerId,
level: MessageType,
cx: &mut ViewContext<Self>,
) {
let log_contents = self.log_store.update(cx, |this, _| {
if let Some(state) = this.get_language_server_state(server_id) {
state.log_level = level;
}
this.server_logs(server_id).map(|v| log_contents(v, level))
});
if let Some(log_contents) = log_contents {
self.editor.update(cx, move |editor, cx| {
editor.set_text(log_contents, cx);
editor.move_to_end(&MoveToEnd, cx);
});
cx.notify();
}
cx.focus(&self.focus_handle);
}
fn show_trace_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
let log_contents = self
.log_store
.read(cx)
.server_trace(server_id)
.map(|v| log_contents(v, ()));
.map(log_contents);
if let Some(log_contents) = log_contents {
self.current_server_id = Some(server_id);
self.active_entry_kind = LogKind::Trace;
@@ -795,7 +686,7 @@ impl LspLogView {
let rpc_log = self.log_store.update(cx, |log_store, _| {
log_store
.enable_rpc_trace_for_language_server(server_id)
.map(|state| log_contents(&state.rpc_messages, ()))
.map(|state| log_contents(&state.rpc_messages))
});
if let Some(rpc_log) = rpc_log {
self.current_server_id = Some(server_id);
@@ -867,23 +758,14 @@ impl LspLogView {
}
}
fn log_filter<T: Message>(line: &T, cmp: <T as Message>::Level) -> Option<&str> {
if line.should_include(cmp) {
Some(line.as_ref())
} else {
None
}
}
fn log_contents<T: Message>(lines: &VecDeque<T>, cmp: <T as Message>::Level) -> String {
fn log_contents(lines: &VecDeque<String>) -> String {
let (a, b) = lines.as_slices();
let a = a.into_iter().filter_map(move |v| log_filter(v, cmp));
let b = b.into_iter().filter_map(move |v| log_filter(v, cmp));
a.chain(b).fold(String::new(), |mut acc, el| {
acc.push_str(el);
acc.push('\n');
acc
})
let log_contents = a.join("\n");
if b.is_empty() {
log_contents
} else {
log_contents + "\n" + &b.join("\n")
}
}
impl Render for LspLogView {
@@ -1162,8 +1044,8 @@ impl Render for LspLogToolbarItemView {
)
.ml_2(),
)
.child(log_view.update(cx, |this, _| match this.active_entry_kind {
LogKind::Trace => {
.child(log_view.update(cx, |this, _| {
if this.active_entry_kind == LogKind::Trace {
let log_view = log_view.clone();
div().child(
PopoverMenu::new("lsp-trace-level-menu")
@@ -1213,60 +1095,9 @@ impl Render for LspLogToolbarItemView {
}
}),
)
} else {
div()
}
LogKind::Logs => {
let log_view = log_view.clone();
div().child(
PopoverMenu::new("lsp-log-level-menu")
.anchor(AnchorCorner::TopLeft)
.trigger(Button::new(
"language_server_log_level_selector",
"Log level",
))
.menu({
let log_view = log_view.clone();
move |cx| {
let id = log_view.read(cx).current_server_id?;
let log_level = log_view.update(cx, |this, cx| {
this.log_store.update(cx, |this, _| {
Some(this.get_language_server_state(id)?.log_level)
})
})?;
ContextMenu::build(cx, |mut menu, _| {
let log_view = log_view.clone();
for (option, label) in [
(MessageType::LOG, "Log"),
(MessageType::INFO, "Info"),
(MessageType::WARNING, "Warning"),
(MessageType::ERROR, "Error"),
] {
menu = menu.entry(label, None, {
let log_view = log_view.clone();
move |cx| {
log_view.update(cx, |this, cx| {
if let Some(id) = this.current_server_id {
this.update_log_level(id, option, cx);
}
});
}
});
if option == log_level {
menu.select_last();
}
}
menu
})
.into()
}
}),
)
}
_ => div(),
}))
}
}

View File

@@ -135,97 +135,3 @@ impl LspCommand for ExpandMacro {
BufferId::new(message.buffer_id)
}
}
pub enum LspSwitchSourceHeader {}
impl lsp::request::Request for LspSwitchSourceHeader {
type Params = SwitchSourceHeaderParams;
type Result = Option<SwitchSourceHeaderResult>;
const METHOD: &'static str = "textDocument/switchSourceHeader";
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SwitchSourceHeaderParams(lsp::TextDocumentIdentifier);
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct SwitchSourceHeaderResult(pub String);
#[derive(Default, Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SwitchSourceHeader;
#[async_trait(?Send)]
impl LspCommand for SwitchSourceHeader {
type Response = SwitchSourceHeaderResult;
type LspRequest = LspSwitchSourceHeader;
type ProtoRequest = proto::LspExtSwitchSourceHeader;
fn to_lsp(
&self,
path: &Path,
_: &Buffer,
_: &Arc<LanguageServer>,
_: &AppContext,
) -> SwitchSourceHeaderParams {
SwitchSourceHeaderParams(lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(path).unwrap(),
})
}
async fn response_from_lsp(
self,
message: Option<SwitchSourceHeaderResult>,
_: Model<Project>,
_: Model<Buffer>,
_: LanguageServerId,
_: AsyncAppContext,
) -> anyhow::Result<SwitchSourceHeaderResult> {
Ok(message
.map(|message| SwitchSourceHeaderResult(message.0))
.unwrap_or_default())
}
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtSwitchSourceHeader {
proto::LspExtSwitchSourceHeader {
project_id,
buffer_id: buffer.remote_id().into(),
}
}
async fn from_proto(
_: Self::ProtoRequest,
_: Model<Project>,
_: Model<Buffer>,
_: AsyncAppContext,
) -> anyhow::Result<Self> {
Ok(Self {})
}
fn response_to_proto(
response: SwitchSourceHeaderResult,
_: &mut Project,
_: PeerId,
_: &clock::Global,
_: &mut AppContext,
) -> proto::LspExtSwitchSourceHeaderResponse {
proto::LspExtSwitchSourceHeaderResponse {
target_file: response.0,
}
}
async fn response_from_proto(
self,
message: proto::LspExtSwitchSourceHeaderResponse,
_: Model<Project>,
_: Model<Buffer>,
_: AsyncAppContext,
) -> anyhow::Result<SwitchSourceHeaderResult> {
Ok(SwitchSourceHeaderResult(message.target_file))
}
fn buffer_id_from_proto(message: &proto::LspExtSwitchSourceHeader) -> Result<BufferId> {
BufferId::new(message.buffer_id)
}
}

View File

@@ -62,8 +62,8 @@ use log::error;
use lsp::{
CompletionContext, DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
DocumentHighlightKind, Edit, FileSystemWatcher, InsertTextFormat, LanguageServer,
LanguageServerBinary, LanguageServerId, LspRequestFuture, MessageActionItem, MessageType,
OneOf, ServerHealthStatus, ServerStatus, TextEdit, WorkDoneProgressCancelParams,
LanguageServerBinary, LanguageServerId, LspRequestFuture, MessageActionItem, OneOf,
ServerHealthStatus, ServerStatus, TextEdit, WorkDoneProgressCancelParams,
};
use lsp_command::*;
use node_runtime::NodeRuntime;
@@ -308,17 +308,11 @@ impl PartialEq for LanguageServerPromptRequest {
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum LanguageServerLogType {
Log(MessageType),
Trace(Option<String>),
}
#[derive(Clone, Debug, PartialEq)]
pub enum Event {
LanguageServerAdded(LanguageServerId),
LanguageServerRemoved(LanguageServerId),
LanguageServerLog(LanguageServerId, LanguageServerLogType, String),
LanguageServerLog(LanguageServerId, String),
Notification(String),
LanguageServerPrompt(LanguageServerPromptRequest),
LanguageNotFound(Model<Buffer>),
@@ -655,17 +649,16 @@ impl DirectoryLister {
};
"~/".to_string()
}
pub fn list_directory(&self, path: String, cx: &mut AppContext) -> Task<Result<Vec<PathBuf>>> {
pub fn list_directory(&self, query: String, cx: &mut AppContext) -> Task<Result<Vec<PathBuf>>> {
match self {
DirectoryLister::Project(project) => {
project.update(cx, |project, cx| project.list_directory(path, cx))
project.update(cx, |project, cx| project.list_directory(query, cx))
}
DirectoryLister::Local(fs) => {
let fs = fs.clone();
cx.background_executor().spawn(async move {
let mut results = vec![];
let expanded = shellexpand::tilde(&path);
let expanded = shellexpand::tilde(&query);
let query = Path::new(expanded.as_ref());
let mut response = fs.read_dir(query).await?;
while let Some(path) = response.next().await {
@@ -2307,7 +2300,6 @@ impl Project {
buffer.update(cx, |buffer, cx| {
let worktree_id = old_file.worktree_id(cx);
let ids = &self.language_server_ids;
if let Some(language) = buffer.language().cloned() {
@@ -2628,7 +2620,7 @@ impl Project {
let worktree_id = file.worktree_id(cx);
let abs_path = file.as_local()?.abs_path(cx);
let text_document = lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(abs_path).log_err()?,
uri: lsp::Url::from_file_path(abs_path).unwrap(),
};
for (_, _, server) in self.language_servers_for_worktree(worktree_id) {
@@ -3659,56 +3651,17 @@ impl Project {
})
.detach();
language_server
.on_notification::<lsp::notification::Progress, _>({
let project = project.clone();
move |params, mut cx| {
if let Some(this) = project.upgrade() {
this.update(&mut cx, |this, cx| {
this.on_lsp_progress(
params,
server_id,
disk_based_diagnostics_progress_token.clone(),
cx,
);
})
.ok();
}
}
})
.detach();
language_server
.on_notification::<lsp::notification::LogMessage, _>({
let project = project.clone();
move |params, mut cx| {
if let Some(this) = project.upgrade() {
this.update(&mut cx, |_, cx| {
cx.emit(Event::LanguageServerLog(
server_id,
LanguageServerLogType::Log(params.typ),
params.message,
));
})
.ok();
}
}
})
.detach();
language_server
.on_notification::<lsp::notification::LogTrace, _>({
let project = project.clone();
move |params, mut cx| {
if let Some(this) = project.upgrade() {
this.update(&mut cx, |_, cx| {
cx.emit(Event::LanguageServerLog(
server_id,
LanguageServerLogType::Trace(params.verbose),
params.message,
));
})
.ok();
}
.on_notification::<lsp::notification::Progress, _>(move |params, mut cx| {
if let Some(this) = project.upgrade() {
this.update(&mut cx, |this, cx| {
this.on_lsp_progress(
params,
server_id,
disk_based_diagnostics_progress_token.clone(),
cx,
);
})
.ok();
}
})
.detach();
@@ -3720,16 +3673,9 @@ impl Project {
(None, override_options) => initialization_options = override_options,
_ => {}
}
let language_server = cx
.update(|cx| language_server.initialize(initialization_options, cx))?
.await
.inspect_err(|_| {
if let Some(this) = project.upgrade() {
this.update(cx, |_, cx| cx.emit(Event::LanguageServerRemoved(server_id)))
.ok();
}
})?;
.await?;
language_server
.notify::<lsp::notification::DidChangeConfiguration>(
@@ -7822,88 +7768,6 @@ impl Project {
}
}
// Returns the resolved version of `path`, that was found in `buffer`, if it exists.
pub fn resolve_existing_file_path(
&self,
path: &str,
buffer: &Model<Buffer>,
cx: &mut ModelContext<Self>,
) -> Task<Option<ResolvedPath>> {
// TODO: ssh based remoting.
if self.ssh_session.is_some() {
return Task::ready(None);
}
if self.is_local() {
let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
if expanded.is_absolute() {
let fs = self.fs.clone();
cx.background_executor().spawn(async move {
let path = expanded.as_path();
let exists = fs.is_file(path).await;
exists.then(|| ResolvedPath::AbsPath(expanded))
})
} else {
self.resolve_path_in_worktrees(expanded, buffer, cx)
}
} else {
let path = PathBuf::from(path);
if path.is_absolute() || path.starts_with("~") {
return Task::ready(None);
}
self.resolve_path_in_worktrees(path, buffer, cx)
}
}
fn resolve_path_in_worktrees(
&self,
path: PathBuf,
buffer: &Model<Buffer>,
cx: &mut ModelContext<Self>,
) -> Task<Option<ResolvedPath>> {
let mut candidates = vec![path.clone()];
if let Some(file) = buffer.read(cx).file() {
if let Some(dir) = file.path().parent() {
let joined = dir.to_path_buf().join(path);
candidates.push(joined);
}
}
let worktrees = self.worktrees(cx).collect::<Vec<_>>();
cx.spawn(|_, mut cx| async move {
for worktree in worktrees {
for candidate in candidates.iter() {
let path = worktree
.update(&mut cx, |worktree, _| {
let root_entry_path = &worktree.root_entry().unwrap().path;
let resolved = resolve_path(&root_entry_path, candidate);
let stripped =
resolved.strip_prefix(&root_entry_path).unwrap_or(&resolved);
worktree.entry_for_path(stripped).map(|entry| {
ResolvedPath::ProjectPath(ProjectPath {
worktree_id: worktree.id(),
path: entry.path.clone(),
})
})
})
.ok()?;
if path.is_some() {
return path;
}
}
}
None
})
}
pub fn list_directory(
&self,
query: String,
@@ -11365,14 +11229,6 @@ fn resolve_path(base: &Path, path: &Path) -> PathBuf {
result
}
/// ResolvedPath is a path that has been resolved to either a ProjectPath
/// or an AbsPath and that *exists*.
#[derive(Debug, Clone)]
pub enum ResolvedPath {
ProjectPath(ProjectPath),
AbsPath(PathBuf),
}
impl Item for Buffer {
fn try_open(
project: &Model<Project>,

View File

@@ -1356,47 +1356,22 @@ impl ProjectPanel {
}
fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext<Self>) {
let abs_file_paths = {
let project = self.project.read(cx);
self.marked_entries()
.into_iter()
.filter_map(|entry| {
let entry_path = project.path_for_entry(entry.entry_id, cx)?.path;
Some(
project
.worktree_for_id(entry.worktree_id, cx)?
.read(cx)
.abs_path()
.join(entry_path)
.to_string_lossy()
.to_string(),
)
})
.collect::<Vec<_>>()
};
if !abs_file_paths.is_empty() {
cx.write_to_clipboard(ClipboardItem::new_string(abs_file_paths.join("\n")));
if let Some((worktree, entry)) = self.selected_entry(cx) {
cx.write_to_clipboard(ClipboardItem::new_string(
worktree
.abs_path()
.join(&entry.path)
.to_string_lossy()
.to_string(),
));
}
}
fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext<Self>) {
let file_paths = {
let project = self.project.read(cx);
self.marked_entries()
.into_iter()
.filter_map(|entry| {
Some(
project
.path_for_entry(entry.entry_id, cx)?
.path
.to_string_lossy()
.to_string(),
)
})
.collect::<Vec<_>>()
};
if !file_paths.is_empty() {
cx.write_to_clipboard(ClipboardItem::new_string(file_paths.join("\n")));
if let Some((_, entry)) = self.selected_entry(cx) {
cx.write_to_clipboard(ClipboardItem::new_string(
entry.path.to_string_lossy().to_string(),
));
}
}

View File

@@ -131,7 +131,7 @@ message Envelope {
UpdateUserPlan update_user_plan = 234;
UpdateDiffBase update_diff_base = 104;
AcceptTermsOfService accept_terms_of_service = 239;
AcceptTermsOfServiceResponse accept_terms_of_service_response = 240;
AcceptTermsOfServiceResponse accept_terms_of_service_response = 240; // current max
OnTypeFormatting on_type_formatting = 105;
OnTypeFormattingResponse on_type_formatting_response = 106;
@@ -264,18 +264,15 @@ message Envelope {
GetSignatureHelp get_signature_help = 217;
GetSignatureHelpResponse get_signature_help_response = 218;
ListRemoteDirectory list_remote_directory = 219;
ListRemoteDirectoryResponse list_remote_directory_response = 220;
UpdateDevServerProject update_dev_server_project = 221;
AddWorktree add_worktree = 222;
AddWorktreeResponse add_worktree_response = 223;
GetLlmToken get_llm_token = 235;
GetLlmTokenResponse get_llm_token_response = 236;
LspExtSwitchSourceHeader lsp_ext_switch_source_header = 241;
LspExtSwitchSourceHeaderResponse lsp_ext_switch_source_header_response = 242; // current max
}
reserved 158 to 161;
@@ -2079,15 +2076,6 @@ message LspExtExpandMacroResponse {
string expansion = 2;
}
message LspExtSwitchSourceHeader {
uint64 project_id = 1;
uint64 buffer_id = 2;
}
message LspExtSwitchSourceHeaderResponse {
string target_file = 1;
}
message SetRoomParticipantRole {
uint64 room_id = 1;
uint64 user_id = 2;

View File

@@ -406,8 +406,6 @@ messages!(
(UpdateContext, Foreground),
(SynchronizeContexts, Foreground),
(SynchronizeContextsResponse, Foreground),
(LspExtSwitchSourceHeader, Background),
(LspExtSwitchSourceHeaderResponse, Background),
(AddWorktree, Foreground),
(AddWorktreeResponse, Foreground),
);
@@ -530,7 +528,6 @@ request_messages!(
(OpenContext, OpenContextResponse),
(CreateContext, CreateContextResponse),
(SynchronizeContexts, SynchronizeContextsResponse),
(LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
(AddWorktree, AddWorktreeResponse),
);
@@ -600,7 +597,6 @@ entity_messages!(
CreateContext,
UpdateContext,
SynchronizeContexts,
LspExtSwitchSourceHeader
);
entity_messages!(

View File

@@ -8,14 +8,13 @@ use editor::actions::{
use editor::{Editor, EditorSettings};
use gpui::{
Action, AnchorCorner, ClickEvent, ElementId, EventEmitter, InteractiveElement, ParentElement,
Render, Styled, Subscription, View, ViewContext, WeakView,
anchored, deferred, Action, AnchorCorner, ClickEvent, DismissEvent, ElementId, EventEmitter,
InteractiveElement, ParentElement, Render, Styled, Subscription, View, ViewContext, WeakView,
};
use search::{buffer_search, BufferSearchBar};
use settings::{Settings, SettingsStore};
use ui::{
prelude::*, ButtonStyle, ContextMenu, IconButton, IconButtonShape, IconName, IconSize,
PopoverMenu, PopoverMenuHandle, Tooltip,
prelude::*, ButtonStyle, ContextMenu, IconButton, IconButtonShape, IconName, IconSize, Tooltip,
};
use workspace::{
item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
@@ -28,9 +27,10 @@ pub struct QuickActionBar {
_inlay_hints_enabled_subscription: Option<Subscription>,
active_item: Option<Box<dyn ItemHandle>>,
buffer_search_bar: View<BufferSearchBar>,
repl_menu: Option<View<ContextMenu>>,
show: bool,
toggle_selections_handle: PopoverMenuHandle<ContextMenu>,
toggle_settings_handle: PopoverMenuHandle<ContextMenu>,
toggle_selections_menu: Option<View<ContextMenu>>,
toggle_settings_menu: Option<View<ContextMenu>>,
workspace: WeakView<Workspace>,
}
@@ -44,9 +44,10 @@ impl QuickActionBar {
_inlay_hints_enabled_subscription: None,
active_item: None,
buffer_search_bar,
repl_menu: None,
show: true,
toggle_selections_handle: Default::default(),
toggle_settings_handle: Default::default(),
toggle_selections_menu: None,
toggle_settings_menu: None,
workspace: workspace.weak_handle(),
};
this.apply_settings(cx);
@@ -78,6 +79,17 @@ impl QuickActionBar {
ToolbarItemLocation::Hidden
}
}
fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
div().absolute().bottom_0().right_0().size_0().child(
deferred(
anchored()
.anchor(AnchorCorner::TopRight)
.child(menu.clone()),
)
.with_priority(1),
)
}
}
impl Render for QuickActionBar {
@@ -146,155 +158,150 @@ impl Render for QuickActionBar {
);
let editor_selections_dropdown = selection_menu_enabled.then(|| {
let focus = editor.focus_handle(cx);
PopoverMenu::new("editor-selections-dropdown")
.trigger(
IconButton::new("toggle_editor_selections_icon", IconName::TextCursor)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.selected(self.toggle_selections_handle.is_deployed())
.when(!self.toggle_selections_handle.is_deployed(), |this| {
this.tooltip(|cx| Tooltip::text("Selection Controls", cx))
}),
)
.with_handle(self.toggle_selections_handle.clone())
.anchor(AnchorCorner::TopRight)
.menu(move |cx| {
let focus = focus.clone();
let menu = ContextMenu::build(cx, move |menu, _| {
menu.context(focus.clone())
.action("Select All", Box::new(SelectAll))
.action(
"Select Next Occurrence",
Box::new(SelectNext {
replace_newest: false,
}),
)
.action("Expand Selection", Box::new(SelectLargerSyntaxNode))
.action("Shrink Selection", Box::new(SelectSmallerSyntaxNode))
.action("Add Cursor Above", Box::new(AddSelectionAbove))
.action("Add Cursor Below", Box::new(AddSelectionBelow))
.separator()
.action("Go to Symbol", Box::new(ToggleOutline))
.action("Go to Line/Column", Box::new(ToggleGoToLine))
.separator()
.action("Next Problem", Box::new(GoToDiagnostic))
.action("Previous Problem", Box::new(GoToPrevDiagnostic))
.separator()
.action("Next Hunk", Box::new(GoToHunk))
.action("Previous Hunk", Box::new(GoToPrevHunk))
.separator()
.action("Move Line Up", Box::new(MoveLineUp))
.action("Move Line Down", Box::new(MoveLineDown))
.action("Duplicate Selection", Box::new(DuplicateLineDown))
});
Some(menu)
IconButton::new("toggle_editor_selections_icon", IconName::TextCursor)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.selected(self.toggle_selections_menu.is_some())
.on_click({
let focus = editor.focus_handle(cx);
cx.listener(move |quick_action_bar, _, cx| {
let focus = focus.clone();
let menu = ContextMenu::build(cx, move |menu, _| {
menu.context(focus.clone())
.action("Select All", Box::new(SelectAll))
.action(
"Select Next Occurrence",
Box::new(SelectNext {
replace_newest: false,
}),
)
.action("Expand Selection", Box::new(SelectLargerSyntaxNode))
.action("Shrink Selection", Box::new(SelectSmallerSyntaxNode))
.action("Add Cursor Above", Box::new(AddSelectionAbove))
.action("Add Cursor Below", Box::new(AddSelectionBelow))
.separator()
.action("Go to Symbol", Box::new(ToggleOutline))
.action("Go to Line/Column", Box::new(ToggleGoToLine))
.separator()
.action("Next Problem", Box::new(GoToDiagnostic))
.action("Previous Problem", Box::new(GoToPrevDiagnostic))
.separator()
.action("Next Hunk", Box::new(GoToHunk))
.action("Previous Hunk", Box::new(GoToPrevHunk))
.separator()
.action("Move Line Up", Box::new(MoveLineUp))
.action("Move Line Down", Box::new(MoveLineDown))
.action("Duplicate Selection", Box::new(DuplicateLineDown))
});
cx.subscribe(&menu, |quick_action_bar, _, _: &DismissEvent, _cx| {
quick_action_bar.toggle_selections_menu = None;
})
.detach();
quick_action_bar.toggle_selections_menu = Some(menu);
})
})
.when(self.toggle_selections_menu.is_none(), |this| {
this.tooltip(|cx| Tooltip::text("Selection Controls", cx))
})
});
let editor = editor.downgrade();
let editor_settings_dropdown = PopoverMenu::new("editor-settings")
.trigger(
IconButton::new("toggle_editor_settings_icon", IconName::Sliders)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.selected(self.toggle_settings_handle.is_deployed())
.when(!self.toggle_settings_handle.is_deployed(), |this| {
this.tooltip(|cx| Tooltip::text("Editor Controls", cx))
}),
)
.anchor(AnchorCorner::TopRight)
.with_handle(self.toggle_settings_handle.clone())
.menu(move |cx| {
let menu = ContextMenu::build(cx, |mut menu, _| {
if supports_inlay_hints {
menu = menu.toggleable_entry(
"Inlay Hints",
inlay_hints_enabled,
IconPosition::Start,
Some(editor::actions::ToggleInlayHints.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor
.update(cx, |editor, cx| {
editor.toggle_inlay_hints(
&editor::actions::ToggleInlayHints,
let editor_settings_dropdown =
IconButton::new("toggle_editor_settings_icon", IconName::Sliders)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.selected(self.toggle_settings_menu.is_some())
.on_click({
let editor = editor.clone();
cx.listener(move |quick_action_bar, _, cx| {
let menu = ContextMenu::build(cx, |mut menu, _| {
if supports_inlay_hints {
menu = menu.toggleable_entry(
"Inlay Hints",
inlay_hints_enabled,
IconPosition::Start,
Some(editor::actions::ToggleInlayHints.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor.update(cx, |editor, cx| {
editor.toggle_inlay_hints(
&editor::actions::ToggleInlayHints,
cx,
);
});
}
},
);
}
menu = menu.toggleable_entry(
"Inline Git Blame",
git_blame_inline_enabled,
IconPosition::Start,
Some(editor::actions::ToggleGitBlameInline.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor.update(cx, |editor, cx| {
editor.toggle_git_blame_inline(
&editor::actions::ToggleGitBlameInline,
cx,
)
});
}
},
);
menu = menu.toggleable_entry(
"Selection Menu",
selection_menu_enabled,
IconPosition::Start,
Some(editor::actions::ToggleSelectionMenu.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor.update(cx, |editor, cx| {
editor.toggle_selection_menu(
&editor::actions::ToggleSelectionMenu,
cx,
)
});
}
},
);
menu = menu.toggleable_entry(
"Auto Signature Help",
auto_signature_help_enabled,
IconPosition::Start,
Some(editor::actions::ToggleAutoSignatureHelp.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor.update(cx, |editor, cx| {
editor.toggle_auto_signature_help_menu(
&editor::actions::ToggleAutoSignatureHelp,
cx,
);
})
.ok();
}
},
);
}
});
}
},
);
menu = menu.toggleable_entry(
"Inline Git Blame",
git_blame_inline_enabled,
IconPosition::Start,
Some(editor::actions::ToggleGitBlameInline.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor
.update(cx, |editor, cx| {
editor.toggle_git_blame_inline(
&editor::actions::ToggleGitBlameInline,
cx,
)
})
.ok();
}
},
);
menu = menu.toggleable_entry(
"Selection Menu",
selection_menu_enabled,
IconPosition::Start,
Some(editor::actions::ToggleSelectionMenu.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor
.update(cx, |editor, cx| {
editor.toggle_selection_menu(
&editor::actions::ToggleSelectionMenu,
cx,
)
})
.ok();
}
},
);
menu = menu.toggleable_entry(
"Auto Signature Help",
auto_signature_help_enabled,
IconPosition::Start,
Some(editor::actions::ToggleAutoSignatureHelp.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor
.update(cx, |editor, cx| {
editor.toggle_auto_signature_help_menu(
&editor::actions::ToggleAutoSignatureHelp,
cx,
);
})
.ok();
}
},
);
menu
menu
});
cx.subscribe(&menu, |quick_action_bar, _, _: &DismissEvent, _cx| {
quick_action_bar.toggle_settings_menu = None;
})
.detach();
quick_action_bar.toggle_settings_menu = Some(menu);
})
})
.when(self.toggle_settings_menu.is_none(), |this| {
this.tooltip(|cx| Tooltip::text("Editor Controls", cx))
});
Some(menu)
});
h_flex()
.id("quick action bar")
@@ -309,6 +316,21 @@ impl Render for QuickActionBar {
)
.children(editor_selections_dropdown)
.child(editor_settings_dropdown)
.when_some(self.repl_menu.as_ref(), |el, repl_menu| {
el.child(Self::render_menu_overlay(repl_menu))
})
.when_some(
self.toggle_settings_menu.as_ref(),
|el, toggle_settings_menu| {
el.child(Self::render_menu_overlay(toggle_settings_menu))
},
)
.when_some(
self.toggle_selections_menu.as_ref(),
|el, toggle_selections_menu| {
el.child(Self::render_menu_overlay(toggle_selections_menu))
},
)
}
}

View File

@@ -173,6 +173,8 @@ impl QuickActionBar {
url: format!("{}#change-kernel", ZED_REPL_DOCUMENTATION),
}),
)
// TODO: Add Restart action
// .action("Restart", Box::new(gpui::NoAction))
.custom_entry(
move |_cx| {
Label::new("Shut Down Kernel")
@@ -187,20 +189,6 @@ impl QuickActionBar {
}
},
)
.custom_entry(
move |_cx| {
Label::new("Restart Kernel")
.size(LabelSize::Small)
.color(Color::Error)
.into_any_element()
},
{
let editor = editor.clone();
move |cx| {
repl::restart(editor.clone(), cx);
}
},
)
.separator()
.action("View Sessions", Box::new(repl::Sessions))
// TODO: Add shut down all kernels action
@@ -317,15 +305,6 @@ fn session_state(session: View<Session>, cx: &WindowContext) -> ReplMenuState {
};
let menu_state = match &session.kernel {
Kernel::Restarting => ReplMenuState {
tooltip: format!("Restarting {}", kernel_name).into(),
icon_is_animating: true,
popover_disabled: true,
icon_color: Color::Muted,
indicator: Some(Indicator::dot().color(Color::Muted)),
status: session.kernel.status(),
..fill_fields()
},
Kernel::RunningKernel(kernel) => match &kernel.execution_state {
ExecutionState::Idle => ReplMenuState {
tooltip: format!("Run code on {} ({})", kernel_name, kernel_language).into(),

View File

@@ -87,7 +87,6 @@ pub enum KernelStatus {
Error,
ShuttingDown,
Shutdown,
Restarting,
}
impl KernelStatus {
@@ -108,7 +107,6 @@ impl ToString for KernelStatus {
KernelStatus::Error => "Error".to_string(),
KernelStatus::ShuttingDown => "Shutting Down".to_string(),
KernelStatus::Shutdown => "Shutdown".to_string(),
KernelStatus::Restarting => "Restarting".to_string(),
}
}
}
@@ -124,7 +122,6 @@ impl From<&Kernel> for KernelStatus {
Kernel::ErroredLaunch(_) => KernelStatus::Error,
Kernel::ShuttingDown => KernelStatus::ShuttingDown,
Kernel::Shutdown => KernelStatus::Shutdown,
Kernel::Restarting => KernelStatus::Restarting,
}
}
}
@@ -136,7 +133,6 @@ pub enum Kernel {
ErroredLaunch(String),
ShuttingDown,
Shutdown,
Restarting,
}
impl Kernel {
@@ -164,7 +160,7 @@ impl Kernel {
pub fn is_shutting_down(&self) -> bool {
match self {
Kernel::Restarting | Kernel::ShuttingDown => true,
Kernel::ShuttingDown => true,
Kernel::RunningKernel(_)
| Kernel::StartingKernel(_)
| Kernel::ErroredLaunch(_)
@@ -328,7 +324,7 @@ impl RunningKernel {
_control_task: control_task,
_routing_task: routing_task,
connection_path,
execution_state: ExecutionState::Idle,
execution_state: ExecutionState::Busy,
kernel_info: None,
},
messages_rx,

View File

@@ -420,7 +420,6 @@ pub enum ExecutionStatus {
ShuttingDown,
Shutdown,
KernelErrored(String),
Restarting,
}
pub struct ExecutionView {
@@ -614,9 +613,6 @@ impl Render for ExecutionView {
ExecutionStatus::ShuttingDown => Label::new("Kernel shutting down...")
.color(Color::Muted)
.into_any_element(),
ExecutionStatus::Restarting => Label::new("Kernel restarting...")
.color(Color::Muted)
.into_any_element(),
ExecutionStatus::Shutdown => Label::new("Kernel shutdown")
.color(Color::Muted)
.into_any_element(),

View File

@@ -20,7 +20,7 @@ pub use crate::jupyter_settings::JupyterSettings;
pub use crate::kernels::{Kernel, KernelSpecification, KernelStatus};
pub use crate::repl_editor::*;
pub use crate::repl_sessions_ui::{
ClearOutputs, Interrupt, ReplSessionsPage, Restart, Run, Sessions, Shutdown,
ClearOutputs, Interrupt, ReplSessionsPage, Run, Sessions, Shutdown,
};
use crate::repl_store::ReplStore;
pub use crate::session::Session;

View File

@@ -168,27 +168,6 @@ pub fn shutdown(editor: WeakView<Editor>, cx: &mut WindowContext) {
});
}
pub fn restart(editor: WeakView<Editor>, cx: &mut WindowContext) {
let Some(editor) = editor.upgrade() else {
return;
};
let entity_id = editor.entity_id();
let Some(session) = ReplStore::global(cx)
.read(cx)
.get_session(entity_id)
.cloned()
else {
return;
};
session.update(cx, |session, cx| {
session.restart(cx);
cx.notify();
});
}
fn cell_range(buffer: &BufferSnapshot, start_row: u32, end_row: u32) -> Range<Point> {
let mut snippet_end_row = end_row;
while buffer.is_line_blank(snippet_end_row) && snippet_end_row > start_row {

View File

@@ -23,7 +23,6 @@ actions!(
Sessions,
Interrupt,
Shutdown,
Restart,
RefreshKernelspecs
]
);
@@ -127,19 +126,6 @@ pub fn init(cx: &mut AppContext) {
}
})
.detach();
editor
.register_action({
let editor_handle = editor_handle.clone();
move |_: &Restart, cx| {
if !JupyterSettings::enabled(cx) {
return;
}
crate::restart(editor_handle.clone(), cx);
}
})
.detach();
})
.detach();
}

View File

@@ -31,12 +31,10 @@ use theme::ActiveTheme;
use ui::{prelude::*, IconButtonShape, Tooltip};
pub struct Session {
fs: Arc<dyn Fs>,
editor: WeakView<Editor>,
pub kernel: Kernel,
blocks: HashMap<String, EditorBlock>,
messaging_task: Option<Task<()>>,
process_status_task: Option<Task<()>>,
messaging_task: Task<()>,
pub kernel_specification: KernelSpecification,
telemetry: Arc<Telemetry>,
_buffer_subscription: Subscription,
@@ -194,50 +192,24 @@ impl Session {
kernel_specification: KernelSpecification,
cx: &mut ViewContext<Self>,
) -> Self {
let subscription = match editor.upgrade() {
Some(editor) => {
let buffer = editor.read(cx).buffer().clone();
cx.subscribe(&buffer, Self::on_buffer_event)
}
None => Subscription::new(|| {}),
};
let kernel_language = kernel_specification.kernelspec.language.clone();
let mut session = Self {
fs,
editor,
kernel: Kernel::StartingKernel(Task::ready(()).shared()),
messaging_task: None,
process_status_task: None,
blocks: HashMap::default(),
kernel_specification,
_buffer_subscription: subscription,
telemetry,
};
session.start_kernel(cx);
session
}
fn start_kernel(&mut self, cx: &mut ViewContext<Self>) {
let kernel_language = self.kernel_specification.kernelspec.language.clone();
let entity_id = self.editor.entity_id();
let working_directory = self
.editor
.upgrade()
.and_then(|editor| editor.read(cx).working_directory(cx))
.unwrap_or_else(temp_dir);
self.telemetry.report_repl_event(
telemetry.report_repl_event(
kernel_language.clone(),
KernelStatus::Starting.to_string(),
cx.entity_id().to_string(),
);
let entity_id = editor.entity_id();
let working_directory = editor
.upgrade()
.and_then(|editor| editor.read(cx).working_directory(cx))
.unwrap_or_else(temp_dir);
let kernel = RunningKernel::new(
self.kernel_specification.clone(),
kernel_specification.clone(),
entity_id,
working_directory,
self.fs.clone(),
fs.clone(),
cx,
);
@@ -257,7 +229,6 @@ impl Session {
let reader = BufReader::new(stderr.unwrap());
let mut lines = reader.lines();
while let Some(Ok(line)) = lines.next().await {
// todo!(): Log stdout and stderr to something the session can show
log::error!("kernel: {}", line);
}
})
@@ -280,7 +251,7 @@ impl Session {
let status = kernel.process.status();
session.kernel(Kernel::RunningKernel(kernel), cx);
let process_status_task = cx.spawn(|session, mut cx| async move {
cx.spawn(|session, mut cx| async move {
let error_message = match status.await {
Ok(status) => {
if status.success() {
@@ -328,11 +299,10 @@ impl Session {
cx.notify();
})
.ok();
});
})
.detach();
session.process_status_task = Some(process_status_task);
session.messaging_task = Some(cx.spawn(|session, mut cx| async move {
session.messaging_task = cx.spawn(|session, mut cx| async move {
while let Some(message) = messages_rx.next().await {
session
.update(&mut cx, |session, cx| {
@@ -340,9 +310,9 @@ impl Session {
})
.ok();
}
}));
});
// todo!(@rgbkrk): send KernelInfoRequest once our shell channel read/writes are split
// todo!(@rgbkrk): send kernelinforequest once our shell channel read/writes are split
// cx.spawn(|this, mut cx| async move {
// cx.background_executor()
// .timer(Duration::from_millis(120))
@@ -366,8 +336,23 @@ impl Session {
})
.shared();
self.kernel(Kernel::StartingKernel(pending_kernel), cx);
cx.notify();
let subscription = match editor.upgrade() {
Some(editor) => {
let buffer = editor.read(cx).buffer().clone();
cx.subscribe(&buffer, Self::on_buffer_event)
}
None => Subscription::new(|| {}),
};
return Self {
editor,
kernel: Kernel::StartingKernel(pending_kernel),
messaging_task: Task::ready(()),
blocks: HashMap::default(),
kernel_specification,
_buffer_subscription: subscription,
telemetry,
};
}
fn on_buffer_event(
@@ -468,7 +453,6 @@ impl Session {
.ok();
let status = match &self.kernel {
Kernel::Restarting => ExecutionStatus::Restarting,
Kernel::RunningKernel(_) => ExecutionStatus::Queued,
Kernel::StartingKernel(_) => ExecutionStatus::ConnectingToKernel,
Kernel::ErroredLaunch(error) => ExecutionStatus::KernelErrored(error.clone()),
@@ -631,12 +615,6 @@ impl Session {
// Give the kernel a bit of time to clean up
cx.background_executor().timer(Duration::from_secs(3)).await;
this.update(&mut cx, |session, _cx| {
session.messaging_task.take();
session.process_status_task.take();
})
.ok();
kernel.process.kill().ok();
this.update(&mut cx, |session, cx| {
@@ -648,59 +626,11 @@ impl Session {
})
.detach();
}
_ => {
self.messaging_task.take();
self.process_status_task.take();
self.kernel(Kernel::Shutdown, cx);
}
}
cx.notify();
}
pub fn restart(&mut self, cx: &mut ViewContext<Self>) {
let kernel = std::mem::replace(&mut self.kernel, Kernel::Restarting);
match kernel {
Kernel::Restarting => {
// Do nothing if already restarting
}
Kernel::RunningKernel(mut kernel) => {
let mut request_tx = kernel.request_tx.clone();
cx.spawn(|this, mut cx| async move {
// Send shutdown request with restart flag
log::debug!("restarting kernel");
let message: JupyterMessage = ShutdownRequest { restart: true }.into();
request_tx.try_send(message).ok();
this.update(&mut cx, |session, _cx| {
session.messaging_task.take();
session.process_status_task.take();
})
.ok();
// Wait for kernel to shutdown
cx.background_executor().timer(Duration::from_secs(1)).await;
// Force kill the kernel if it hasn't shut down
kernel.process.kill().ok();
// Start a new kernel
this.update(&mut cx, |session, cx| {
// todo!(): Differentiate between restart and restart+clear-outputs
session.clear_outputs(cx);
session.start_kernel(cx);
})
.ok();
})
.detach();
Kernel::StartingKernel(_kernel) => {
self.kernel = Kernel::Shutdown;
}
_ => {
// If it's not already running, we can just clean up and start a new kernel
self.messaging_task.take();
self.process_status_task.take();
self.clear_outputs(cx);
self.start_kernel(cx);
self.kernel = Kernel::Shutdown;
}
}
cx.notify();
@@ -733,7 +663,6 @@ impl Render for Session {
Kernel::ErroredLaunch(err) => (Some(format!("Error: {err}")), None),
Kernel::ShuttingDown => (Some("Shutting Down".into()), None),
Kernel::Shutdown => (Some("Shutdown".into()), None),
Kernel::Restarting => (Some("Restarting".into()), None),
};
KernelListItem::new(self.kernel_specification.clone())
@@ -746,7 +675,6 @@ impl Render for Session {
Kernel::ErroredLaunch(_) => Color::Error,
Kernel::ShuttingDown => Color::Modified,
Kernel::Shutdown => Color::Disabled,
Kernel::Restarting => Color::Modified,
})
.child(Label::new(self.kernel_specification.name.clone()))
.children(status_text.map(|status_text| Label::new(format!("({status_text})"))))

View File

@@ -1,24 +1,17 @@
/// Please see: [Telemetry in Zed](https://zed.dev/docs/telemetry) for additional documentation.
use semantic_version::SemanticVersion;
use serde::{Deserialize, Serialize};
use std::{fmt::Display, sync::Arc, time::Duration};
#[derive(Serialize, Deserialize, Debug)]
pub struct EventRequestBody {
/// Identifier unique to each Zed installation (differs for stable, preview, dev)
pub installation_id: Option<String>,
/// Identifier unique to each logged in Zed user (randomly generated on first sign in)
pub metrics_id: Option<String>,
/// Identifier unique to each Zed session (differs for each time you open Zed)
pub session_id: Option<String>,
/// True for Zed staff, otherwise false
pub is_staff: Option<bool>,
/// Zed version number
pub app_version: String,
pub os_name: String,
pub os_version: Option<String>,
pub architecture: String,
/// Zed release channel (stable, preview, dev)
pub release_channel: Option<String>,
pub events: Vec<EventWrapper>,
}
@@ -32,7 +25,6 @@ impl EventRequestBody {
#[derive(Serialize, Deserialize, Debug)]
pub struct EventWrapper {
pub signed_in: bool,
/// Duration between this event's timestamp and the timestamp of the first event in the current batch
pub milliseconds_since_first_event: i64,
#[serde(flatten)]
pub event: Event,
@@ -78,19 +70,13 @@ pub enum Event {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct EditorEvent {
/// The editor operation performed (open, save)
pub operation: String,
/// The extension of the file that was opened or saved
pub file_extension: Option<String>,
/// Whether the user is in vim mode or not
pub vim_mode: bool,
/// Whether the user has copilot enabled or not
pub copilot_enabled: bool,
/// Whether the user has copilot enabled for the language of the file opened or saved
pub copilot_enabled_for_language: bool,
}
/// Deprecated since Zed v0.137.0 (2024-05-29). Replaced by InlineCompletionEvent.
// Needed for clients sending old copilot_event types
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct CopilotEvent {
@@ -101,7 +87,6 @@ pub struct CopilotEvent {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct InlineCompletionEvent {
/// Provider of the completion suggestion (e.g. copilot, supermaven)
pub provider: String,
pub suggestion_accepted: bool,
pub file_extension: Option<String>,
@@ -109,7 +94,6 @@ pub struct InlineCompletionEvent {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct CallEvent {
/// Operation performed: invite/join call; begin/end screenshare; share/unshare project; etc
pub operation: String,
pub room_id: Option<u64>,
pub channel_id: Option<u64>,
@@ -117,11 +101,8 @@ pub struct CallEvent {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct AssistantEvent {
/// Unique random identifier for each assistant tab (None for inline assist)
pub conversation_id: Option<String>,
/// The kind of assistant (Panel, Inline)
pub kind: AssistantKind,
/// Name of the AI model used (gpt-4o, claude-3-5-sonnet, etc)
pub model: String,
pub response_latency: Option<Duration>,
pub error_message: Option<String>,
@@ -190,7 +171,6 @@ pub struct HangReport {
pub os_name: String,
pub os_version: Option<String>,
pub architecture: String,
/// Identifier unique to each Zed installation (differs for stable, preview, dev)
pub installation_id: Option<String>,
}
@@ -202,27 +182,19 @@ pub struct LocationData {
#[derive(Serialize, Deserialize)]
pub struct Panic {
/// The name of the thread that panicked
pub thread: String,
/// The panic message
pub payload: String,
/// The location of the panic (file, line number)
#[serde(skip_serializing_if = "Option::is_none")]
pub location_data: Option<LocationData>,
pub backtrace: Vec<String>,
/// Zed version number
pub app_version: String,
/// Zed release channel (stable, preview, dev)
pub release_channel: String,
pub os_name: String,
pub os_version: Option<String>,
pub architecture: String,
/// The time the panic occurred (UNIX millisecond timestamp)
pub panicked_on: i64,
#[serde(skip_serializing_if = "Option::is_none")]
/// Identifier unique to each Zed installation (differs for stable, preview, dev)
pub installation_id: Option<String>,
/// Identifier unique to each Zed session (differs for each time you open Zed)
pub session_id: String,
}

View File

@@ -18,7 +18,7 @@ use alacritty_terminal::{
Config, RenderableCursor, TermMode,
},
tty::{self, setup_env},
vte::ansi::{ClearMode, Handler, NamedPrivateMode, PrivateMode},
vte::ansi::{ClearMode, Handler, NamedPrivateMode, PrivateMode, Rgb},
Term,
};
use anyhow::{bail, Result};
@@ -127,6 +127,7 @@ pub enum MaybeNavigationTarget {
#[derive(Clone)]
enum InternalEvent {
ColorRequest(usize, Arc<dyn Fn(Rgb) -> String + Sync + Send + 'static>),
Resize(TerminalSize),
Clear,
// FocusNextMatch,
@@ -687,19 +688,9 @@ impl Terminal {
cx.emit(Event::TitleChanged);
}
}
AlacTermEvent::ColorRequest(index, format) => {
// It's important that the color request is processed here to retain relative order
// with other PTY writes. Otherwise applications might witness out-of-order
// responses to requests. For example: An application sending `OSC 11 ; ? ST`
// (color request) followed by `CSI c` (request device attributes) would receive
// the response to `CSI c` first.
// Instead of locking, we could store the colors in `self.last_content`. But then
// we might respond with out of date value if a "set color" sequence is immediately
// followed by a color request sequence.
let color = self.term.lock().colors()[*index].unwrap_or_else(|| {
to_alac_rgb(get_color_at_index(*index, cx.theme().as_ref()))
});
self.write_to_pty(format(color));
AlacTermEvent::ColorRequest(idx, fun_ptr) => {
self.events
.push_back(InternalEvent::ColorRequest(*idx, fun_ptr.clone()));
}
AlacTermEvent::ChildExit(error_code) => {
self.register_task_finished(Some(*error_code), cx);
@@ -723,6 +714,12 @@ impl Terminal {
cx: &mut ModelContext<Self>,
) {
match event {
InternalEvent::ColorRequest(index, format) => {
let color = term.colors()[*index].unwrap_or_else(|| {
to_alac_rgb(get_color_at_index(*index, cx.theme().as_ref()))
});
self.write_to_pty(format(color))
}
InternalEvent::Resize(mut new_size) => {
new_size.size.height = cmp::max(new_size.line_height, new_size.height());
new_size.size.width = cmp::max(new_size.cell_width, new_size.width());

View File

@@ -5,7 +5,7 @@ use collections::{HashMap, HashSet};
use db::kvp::KEY_VALUE_STORE;
use futures::future::join_all;
use gpui::{
actions, Action, AnchorCorner, AnyView, AppContext, AsyncWindowContext, Entity, EventEmitter,
actions, Action, AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EventEmitter,
ExternalPaths, FocusHandle, FocusableView, IntoElement, Model, ParentElement, Pixels, Render,
Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
};
@@ -20,7 +20,7 @@ use terminal::{
Terminal,
};
use ui::{
h_flex, ButtonCommon, Clickable, ContextMenu, IconButton, IconSize, PopoverMenu, Selectable,
h_flex, ButtonCommon, Clickable, ContextMenu, FluentBuilder, IconButton, IconSize, Selectable,
Tooltip,
};
use util::{ResultExt, TryFutureExt};
@@ -173,42 +173,47 @@ impl TerminalPanel {
let additional_buttons = self.additional_tab_bar_buttons.clone();
self.pane.update(cx, |pane, cx| {
pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
if !pane.has_focus(cx) && !pane.context_menu_focused(cx) {
if !pane.has_focus(cx) {
return (None, None);
}
let focus_handle = pane.focus_handle(cx);
let right_children = h_flex()
.gap_2()
.children(additional_buttons.clone())
.child(
PopoverMenu::new("terminal-tab-bar-popover-menu")
.trigger(
IconButton::new("plus", IconName::Plus)
.icon_size(IconSize::Small)
.tooltip(|cx| Tooltip::text("New...", cx)),
)
.anchor(AnchorCorner::TopRight)
.with_handle(pane.new_item_context_menu_handle.clone())
.menu(move |cx| {
let focus_handle = focus_handle.clone();
IconButton::new("plus", IconName::Plus)
.icon_size(IconSize::Small)
.on_click(cx.listener(|pane, _, cx| {
let focus_handle = pane.focus_handle(cx);
let menu = ContextMenu::build(cx, |menu, _| {
menu.context(focus_handle.clone())
.action(
"New Terminal",
workspace::NewTerminal.boxed_clone(),
)
// We want the focus to go back to terminal panel once task modal is dismissed,
// hence we focus that first. Otherwise, we'd end up without a focused element, as
// context menu will be gone the moment we spawn the modal.
.action(
"Spawn task",
tasks_ui::Spawn::modal().boxed_clone(),
)
menu.action(
"New Terminal",
workspace::NewTerminal.boxed_clone(),
)
.entry(
"Spawn task",
Some(tasks_ui::Spawn::modal().boxed_clone()),
move |cx| {
// We want the focus to go back to terminal panel once task modal is dismissed,
// hence we focus that first. Otherwise, we'd end up without a focused element, as
// context menu will be gone the moment we spawn the modal.
cx.focus(&focus_handle);
cx.dispatch_action(
tasks_ui::Spawn::modal().boxed_clone(),
);
},
)
});
Some(menu)
}),
cx.subscribe(&menu, |pane, _, _: &DismissEvent, _| {
pane.new_item_menu = None;
})
.detach();
pane.new_item_menu = Some(menu);
}))
.tooltip(|cx| Tooltip::text("New...", cx)),
)
.when_some(pane.new_item_menu.as_ref(), |el, new_item_menu| {
el.child(Pane::render_menu_overlay(new_item_menu))
})
.child({
let zoomed = pane.is_zoomed();
IconButton::new("toggle_zoom", IconName::Maximize)

View File

@@ -56,23 +56,6 @@ impl<M: ManagedView> PopoverMenuHandle<M> {
}
}
}
pub fn is_deployed(&self) -> bool {
self.0
.borrow()
.as_ref()
.map_or(false, |state| state.menu.borrow().as_ref().is_some())
}
pub fn is_focused(&self, cx: &mut WindowContext) -> bool {
self.0.borrow().as_ref().map_or(false, |state| {
state
.menu
.borrow()
.as_ref()
.map_or(false, |view| view.focus_handle(cx).is_focused(cx))
})
}
}
pub struct PopoverMenu<M: ManagedView> {
@@ -357,12 +340,9 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
// want a click on the toggle to re-open it.
cx.on_mouse_event(move |_: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble && child_hitbox.is_hovered(cx) {
if let Some(menu) = menu_handle.borrow().as_ref() {
menu.update(cx, |_, cx| {
cx.emit(DismissEvent);
});
}
menu_handle.borrow_mut().take();
cx.stop_propagation();
cx.refresh();
}
})
}

View File

@@ -529,8 +529,8 @@ fn generate_commands(_: &AppContext) -> Vec<VimCommand> {
save_intent: Some(SaveIntent::Overwrite),
}),
VimCommand::new(("cq", "uit"), zed_actions::Quit),
VimCommand::new(("sp", "lit"), workspace::SplitHorizontal),
VimCommand::new(("vs", "plit"), workspace::SplitVertical),
VimCommand::new(("sp", "lit"), workspace::SplitUp),
VimCommand::new(("vs", "plit"), workspace::SplitLeft),
VimCommand::new(
("bd", "elete"),
workspace::CloseActiveItem {
@@ -546,8 +546,14 @@ fn generate_commands(_: &AppContext) -> Vec<VimCommand> {
VimCommand::new(("bf", "irst"), workspace::ActivateItem(0)),
VimCommand::new(("br", "ewind"), workspace::ActivateItem(0)),
VimCommand::new(("bl", "ast"), workspace::ActivateLastItem),
VimCommand::new(("new", ""), workspace::NewFileSplitHorizontal),
VimCommand::new(("vne", "w"), workspace::NewFileSplitVertical),
VimCommand::new(
("new", ""),
workspace::NewFileInDirection(workspace::SplitDirection::Up),
),
VimCommand::new(
("vne", "w"),
workspace::NewFileInDirection(workspace::SplitDirection::Left),
),
VimCommand::new(("tabe", "dit"), workspace::NewFile),
VimCommand::new(("tabnew", ""), workspace::NewFile),
VimCommand::new(("tabn", "ext"), workspace::ActivateNextItem).count(),
@@ -736,15 +742,9 @@ fn generate_positions(string: &str, query: &str) -> Vec<usize> {
mod test {
use std::path::Path;
use crate::{
state::Mode,
test::{NeovimBackedTestContext, VimTestContext},
};
use editor::Editor;
use crate::test::{NeovimBackedTestContext, VimTestContext};
use gpui::TestAppContext;
use indoc::indoc;
use ui::ViewContext;
use workspace::Workspace;
#[gpui::test]
async fn test_command_basics(cx: &mut TestAppContext) {
@@ -923,55 +923,4 @@ mod test {
.await;
cx.shared_state().await.assert_eq("k\nk\nˇk\n4\n4\n3\n2\n1");
}
fn assert_active_item(
workspace: &mut Workspace,
expected_path: &str,
expected_text: &str,
cx: &mut ViewContext<Workspace>,
) {
let active_editor = workspace.active_item_as::<Editor>(cx).unwrap();
let buffer = active_editor
.read(cx)
.buffer()
.read(cx)
.as_singleton()
.unwrap();
let text = buffer.read(cx).text();
let file = buffer.read(cx).file().unwrap();
let file_path = file.as_local().unwrap().abs_path(cx);
assert_eq!(text, expected_text);
assert_eq!(file_path.to_str().unwrap(), expected_path);
}
#[gpui::test]
async fn test_command_gf(cx: &mut TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
// Assert base state, that we're in /root/dir/file.rs
cx.workspace(|workspace, cx| {
assert_active_item(workspace, "/root/dir/file.rs", "", cx);
});
// Insert a new file
let fs = cx.workspace(|workspace, cx| workspace.project().read(cx).fs().clone());
fs.as_fake()
.insert_file("/root/dir/file2.rs", "This is file2.rs".as_bytes().to_vec())
.await;
// Put the path to the second file into the currently open buffer
cx.set_state(indoc! {"go to fiˇle2.rs"}, Mode::Normal);
// Go to file2.rs
cx.simulate_keystrokes("g f");
// We now have two items
cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
cx.workspace(|workspace, cx| {
assert_active_item(workspace, "/root/dir/file2.rs", "This is file2.rs", cx);
});
}
}

View File

@@ -14,9 +14,9 @@ use collections::{BTreeSet, HashMap, HashSet, VecDeque};
use futures::{stream::FuturesUnordered, StreamExt};
use gpui::{
actions, anchored, deferred, impl_actions, prelude::*, Action, AnchorCorner, AnyElement,
AppContext, AsyncWindowContext, ClickEvent, ClipboardItem, Div, DragMoveEvent, EntityId,
EventEmitter, ExternalPaths, FocusHandle, FocusOutEvent, FocusableView, KeyContext, Model,
MouseButton, MouseDownEvent, NavigationDirection, Pixels, Point, PromptLevel, Render,
AppContext, AsyncWindowContext, ClickEvent, ClipboardItem, DismissEvent, Div, DragMoveEvent,
EntityId, EventEmitter, ExternalPaths, FocusHandle, FocusOutEvent, FocusableView, KeyContext,
Model, MouseButton, MouseDownEvent, NavigationDirection, Pixels, Point, PromptLevel, Render,
ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakFocusHandle, WeakView,
WindowContext,
};
@@ -40,7 +40,7 @@ use theme::ThemeSettings;
use ui::{
prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconButtonShape, IconName,
IconSize, Indicator, Label, PopoverMenu, PopoverMenuHandle, Tab, TabBar, TabPosition, Tooltip,
IconSize, Indicator, Label, Tab, TabBar, TabPosition, Tooltip,
};
use ui::{v_flex, ContextMenu};
use util::{debug_panic, maybe, truncate_and_remove_front, ResultExt};
@@ -152,8 +152,6 @@ actions!(
SplitUp,
SplitRight,
SplitDown,
SplitHorizontal,
SplitVertical,
TogglePreviewTab,
]
);
@@ -247,6 +245,8 @@ pub struct Pane {
last_focus_handle_by_item: HashMap<EntityId, WeakFocusHandle>,
nav_history: NavHistory,
toolbar: View<Toolbar>,
pub new_item_menu: Option<View<ContextMenu>>,
split_item_menu: Option<View<ContextMenu>>,
pub(crate) workspace: WeakView<Workspace>,
project: Model<Project>,
drag_split_direction: Option<SplitDirection>,
@@ -264,8 +264,6 @@ pub struct Pane {
display_nav_history_buttons: Option<bool>,
double_click_dispatch_action: Box<dyn Action>,
save_modals_spawned: HashSet<EntityId>,
pub new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,
split_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,
}
pub struct ActivationHistoryEntry {
@@ -366,6 +364,8 @@ impl Pane {
next_timestamp,
}))),
toolbar: cx.new_view(|_| Toolbar::new()),
new_item_menu: None,
split_item_menu: None,
tab_bar_scroll_handle: ScrollHandle::new(),
drag_split_direction: None,
workspace,
@@ -375,7 +375,7 @@ impl Pane {
can_split: true,
should_display_tab_bar: Rc::new(|cx| TabBarSettings::get_global(cx).show),
render_tab_bar_buttons: Rc::new(move |pane, cx| {
if !pane.has_focus(cx) && !pane.context_menu_focused(cx) {
if !pane.has_focus(cx) {
return (None, None);
}
// Ideally we would return a vec of elements here to pass directly to the [TabBar]'s
@@ -384,16 +384,10 @@ impl Pane {
// Instead we need to replicate the spacing from the [TabBar]'s `end_slot` here.
.gap(Spacing::Small.rems(cx))
.child(
PopoverMenu::new("pane-tab-bar-popover-menu")
.trigger(
IconButton::new("plus", IconName::Plus)
.icon_size(IconSize::Small)
.tooltip(|cx| Tooltip::text("New...", cx)),
)
.anchor(AnchorCorner::TopRight)
.with_handle(pane.new_item_context_menu_handle.clone())
.menu(move |cx| {
Some(ContextMenu::build(cx, |menu, _| {
IconButton::new("plus", IconName::Plus)
.icon_size(IconSize::Small)
.on_click(cx.listener(|pane, _, cx| {
let menu = ContextMenu::build(cx, |menu, _| {
menu.action("New File", NewFile.boxed_clone())
.action(
"Open File",
@@ -413,27 +407,37 @@ impl Pane {
)
.separator()
.action("New Terminal", NewTerminal.boxed_clone())
}))
}),
});
cx.subscribe(&menu, |pane, _, _: &DismissEvent, cx| {
pane.focus(cx);
pane.new_item_menu = None;
})
.detach();
pane.new_item_menu = Some(menu);
}))
.tooltip(|cx| Tooltip::text("New...", cx)),
)
.when_some(pane.new_item_menu.as_ref(), |el, new_item_menu| {
el.child(Self::render_menu_overlay(new_item_menu))
})
.child(
PopoverMenu::new("pane-tab-bar-split")
.trigger(
IconButton::new("split", IconName::Split)
.icon_size(IconSize::Small)
.tooltip(|cx| Tooltip::text("Split Pane", cx)),
)
.anchor(AnchorCorner::TopRight)
.with_handle(pane.split_item_context_menu_handle.clone())
.menu(move |cx| {
ContextMenu::build(cx, |menu, _| {
IconButton::new("split", IconName::Split)
.icon_size(IconSize::Small)
.on_click(cx.listener(|pane, _, cx| {
let menu = ContextMenu::build(cx, |menu, _| {
menu.action("Split Right", SplitRight.boxed_clone())
.action("Split Left", SplitLeft.boxed_clone())
.action("Split Up", SplitUp.boxed_clone())
.action("Split Down", SplitDown.boxed_clone())
});
cx.subscribe(&menu, |pane, _, _: &DismissEvent, cx| {
pane.focus(cx);
pane.split_item_menu = None;
})
.into()
}),
.detach();
pane.split_item_menu = Some(menu);
}))
.tooltip(|cx| Tooltip::text("Split Pane", cx)),
)
.child({
let zoomed = pane.is_zoomed();
@@ -452,6 +456,9 @@ impl Pane {
)
})
})
.when_some(pane.split_item_menu.as_ref(), |el, split_item_menu| {
el.child(Self::render_menu_overlay(split_item_menu))
})
.into_any_element()
.into();
(None, right_children)
@@ -462,8 +469,6 @@ impl Pane {
_subscriptions: subscriptions,
double_click_dispatch_action,
save_modals_spawned: HashSet::default(),
split_item_context_menu_handle: Default::default(),
new_item_context_menu_handle: Default::default(),
}
}
@@ -547,9 +552,11 @@ impl Pane {
}
}
pub fn context_menu_focused(&self, cx: &mut ViewContext<Self>) -> bool {
self.new_item_context_menu_handle.is_focused(cx)
|| self.split_item_context_menu_handle.is_focused(cx)
fn context_menu_focused(&self, cx: &mut ViewContext<Self>) -> bool {
self.new_item_menu
.as_ref()
.or(self.split_item_menu.as_ref())
.map_or(false, |menu| menu.focus_handle(cx).is_focused(cx))
}
fn focus_out(&mut self, _event: FocusOutEvent, cx: &mut ViewContext<Self>) {
@@ -2247,12 +2254,6 @@ impl Render for Pane {
}))
.on_action(cx.listener(|pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)))
.on_action(cx.listener(|pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)))
.on_action(cx.listener(|pane, _: &SplitHorizontal, cx| {
pane.split(SplitDirection::horizontal(cx), cx)
}))
.on_action(cx.listener(|pane, _: &SplitVertical, cx| {
pane.split(SplitDirection::vertical(cx), cx)
}))
.on_action(
cx.listener(|pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx)),
)

View File

@@ -1,8 +1,4 @@
use crate::{
pane_group::element::pane_axis,
workspace_settings::{PaneSplitDirectionHorizontal, PaneSplitDirectionVertical},
AppState, FollowerState, Pane, Workspace, WorkspaceSettings,
};
use crate::{pane_group::element::pane_axis, AppState, FollowerState, Pane, Workspace};
use anyhow::{anyhow, Result};
use call::{ActiveCall, ParticipantLocation};
use client::proto::PeerId;
@@ -14,7 +10,6 @@ use gpui::{
use parking_lot::Mutex;
use project::Project;
use serde::Deserialize;
use settings::Settings;
use std::sync::Arc;
use ui::prelude::*;
@@ -566,20 +561,6 @@ impl SplitDirection {
[Self::Up, Self::Down, Self::Left, Self::Right]
}
pub fn vertical(cx: &WindowContext) -> Self {
match WorkspaceSettings::get_global(cx).pane_split_direction_vertical {
PaneSplitDirectionVertical::Left => SplitDirection::Left,
PaneSplitDirectionVertical::Right => SplitDirection::Right,
}
}
pub fn horizontal(cx: &WindowContext) -> Self {
match WorkspaceSettings::get_global(cx).pane_split_direction_horizontal {
PaneSplitDirectionHorizontal::Down => SplitDirection::Down,
PaneSplitDirectionHorizontal::Up => SplitDirection::Up,
}
}
pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
match self {
Self::Up => rect.origin.y,

View File

@@ -55,9 +55,7 @@ pub use persistence::{
WorkspaceDb, DB as WORKSPACE_DB,
};
use postage::stream::Stream;
use project::{
DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree, WorktreeId,
};
use project::{DirectoryLister, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
use serde::Deserialize;
use session::AppSession;
use settings::Settings;
@@ -136,8 +134,6 @@ actions!(
FollowNextCollaborator,
NewCenterTerminal,
NewFile,
NewFileSplitVertical,
NewFileSplitHorizontal,
NewSearch,
NewTerminal,
NewWindow,
@@ -170,6 +166,9 @@ pub struct ActivatePaneInDirection(pub SplitDirection);
#[derive(Clone, Deserialize, PartialEq)]
pub struct SwapPaneInDirection(pub SplitDirection);
#[derive(Clone, Deserialize, PartialEq)]
pub struct NewFileInDirection(pub SplitDirection);
#[derive(Clone, PartialEq, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SaveAll {
@@ -219,6 +218,7 @@ impl_actions!(
ActivatePaneInDirection,
CloseAllItemsAndPanes,
CloseInactiveTabsAndPanes,
NewFileInDirection,
OpenTerminal,
Reload,
Save,
@@ -2015,17 +2015,6 @@ impl Workspace {
})
}
pub fn open_resolved_path(
&mut self,
path: ResolvedPath,
cx: &mut ViewContext<Self>,
) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
match path {
ResolvedPath::ProjectPath(project_path) => self.open_path(project_path, None, true, cx),
ResolvedPath::AbsPath(path) => self.open_abs_path(path, false, cx),
}
}
fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
let project = self.project.read(cx);
if project.is_remote() && project.dev_server_project_id().is_none() {

View File

@@ -8,8 +8,6 @@ use settings::{Settings, SettingsSources};
#[derive(Deserialize)]
pub struct WorkspaceSettings {
pub active_pane_magnification: f32,
pub pane_split_direction_horizontal: PaneSplitDirectionHorizontal,
pub pane_split_direction_vertical: PaneSplitDirectionVertical,
pub centered_layout: CenteredLayoutSettings,
pub confirm_quit: bool,
pub show_call_status_icon: bool,
@@ -63,14 +61,6 @@ pub struct WorkspaceSettingsContent {
///
/// Default: `1.0`
pub active_pane_magnification: Option<f32>,
// Direction to split horizontally.
//
// Default: "up"
pub pane_split_direction_horizontal: Option<PaneSplitDirectionHorizontal>,
// Direction to split vertically.
//
// Default: "left"
pub pane_split_direction_vertical: Option<PaneSplitDirectionVertical>,
// Centered layout related settings.
pub centered_layout: Option<CenteredLayoutSettings>,
/// Whether or not to prompt the user to confirm before closing the application.
@@ -141,20 +131,6 @@ pub enum AutosaveSetting {
OnWindowChange,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum PaneSplitDirectionHorizontal {
Up,
Down,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum PaneSplitDirectionVertical {
Left,
Right,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct CenteredLayoutSettings {

View File

@@ -21,5 +21,3 @@ enable = false
"/ruby.html" = "/docs/languages/ruby.html"
"/python.html" = "/docs/languages/python.html"
"/adding-new-languages.html" = "/docs/extensions/languages.html"
"/language-model-integration.html" = "/docs/assistant/assistant.html"
"/assistant.html" = "/docs/assistant/assistant.html"

View File

@@ -3,11 +3,11 @@
# General
- [Getting Started](./getting-started.md)
- [System Requirements](./system-requirements.md)
- [Linux](./linux.md)
- [Windows](./windows.md)
- [Telemetry](./telemetry.md)
- [Additional Learning Materials](./additional-learning-materials.md)
- [System Requirements](./system-requirements.md)
- [Linux](./linux.md)
- [Windows](./windows.md)
- [Telemetry](./telemetry.md)
- [Additional Learning Materials](./additional-learning-materials.md)
# Configuration
@@ -33,12 +33,12 @@
# Assistant
- [Assistant](./assistant/assistant.md)
- [Configuration](./assistant/configuration.md)
- [Assistant Panel](./assistant/assistant-panel.md)
- [Contexts](./assistant/contexts.md)
- [Inline Assistant](./assistant/inline-assistant.md)
- [Commands](./assistant/commands.md)
- [Prompts](./assistant/prompting.md)
- [Configuration](./assistant/configuration.md)
- [Assistant Panel](./assistant/assistant-panel.md)
- [Contexts](./assistant/contexts.md)
- [Inline Assistant](./assistant/inline-assistant.md)
- [Commands](./assistant/commands.md)
- [Prompts](./assistant/prompting.md)
# Extensions

View File

@@ -6,10 +6,10 @@ Slash commands enhance the assistant's capabilities. Begin by typing a `/` at th
- `/default`: Inserts the default prompt into the context
- `/diagnostics`: Injects errors reported by the project's language server into the context
- `/fetch`: Fetches the content of a webpage and inserts it into the context
- `/fetch`: Inserts the content of a webpage and inserts it into the context
- `/file`: Inserts a single file or a directory of files into the context
- `/now`: Inserts the current date and time into the context
- `/prompt`: Adds a custom-configured prompt to the context ([see Prompt Library](/docs/assistant/prompting#prompt-library))
- `/prompt`: Adds a custom-configured prompt to the context (see Prompt Library)
- `/symbols`: Inserts the current tab's active symbols into the context
- `/tab`: Inserts the content of the active tab or all open tabs into the context
- `/terminal`: Inserts a select number of lines of output from the terminal

View File

@@ -13,7 +13,7 @@ The following providers are supported:
To configure different providers, run `assistant: show configuration` in the command palette, or click on the hamburger menu at the top-right of the assistant panel and select "Configure".
[^1]: This provider does not support the [`/workflow`](./commands#workflow-not-generally-available) command.
[^1]: This provider does not support [`/workflow`](./commands#workflow-not-generally-available) command.
To further customize providers, you can use `settings.json` to do that as follows:
@@ -29,7 +29,7 @@ A hosted service providing convenient and performant support for AI-enabled codi
You can use Claude 3.5 Sonnet via [Zed AI](#zed-ai) for free. To use other Anthropic models you will need to configure it by providing your own API key.
1. Sign up for Anthropic and [create an API key](https://console.anthropic.com/settings/keys)
1. You can obtain an API key [here](https://console.anthropic.com/settings/keys).
2. Make sure that your Anthropic account has credits
3. Open the configuration view (`assistant: show configuration`) and navigate to the Anthropic section
4. Enter your Anthropic API key
@@ -40,7 +40,7 @@ Zed will also use the `ANTHROPIC_API_KEY` environment variable if it's defined.
#### Anthropic Custom Models {#anthropic-custom-models}
You can add custom models to the Anthropic provider by adding the following to your Zed `settings.json`:
You can add custom models to the Anthropic provider, by adding the following to your Zed `settings.json`:
```json
{
@@ -75,8 +75,8 @@ You can use GitHub Copilot chat with the Zed assistant by choosing it via the mo
You can use Gemini 1.5 Pro/Flash with the Zed assistant by choosing it via the model dropdown in the assistant panel.
1. Go the Google AI Studio site and [create an API key](https://aistudio.google.com/app/apikey).
2. Open the configuration view (`assistant: show configuration`) and navigate to the Google AI section
1. Create an API key [here](https://aistudio.google.com/app/apikey).
2. Open the configuration view (`assistant: show configuration`) and navigate to the OpenAI section
3. Enter your Google AI API key
The Google AI API key will be saved in your keychain.
@@ -85,7 +85,7 @@ Zed will also use the `GOOGLE_AI_API_KEY` environment variable if it's defined.
#### Google AI custom models {#google-ai-custom-models}
You can add custom models to the Google AI provider by adding the following to your Zed `settings.json`:
You can add custom models to the GoogleAI provider, by adding the following to your Zed `settings.json`:
```json
{
@@ -137,7 +137,7 @@ You can use Ollama with the Zed assistant by making Ollama appear as an OpenAPI
### OpenAI {#openai}
1. Visit the OpenAI platform and [create an API key](https://platform.openai.com/account/api-keys)
1. Create an [OpenAI API key](https://platform.openai.com/account/api-keys)
2. Make sure that your OpenAI account has credits
3. Open the configuration view (`assistant: show configuration`) and navigate to the OpenAI section
4. Enter your OpenAI API key
@@ -244,7 +244,7 @@ You can also manually edit the `default_model` object in your settings:
| key | type | default | description |
| -------------- | ------- | ------- | ------------------------------------------------------------------------------------- |
| enabled | boolean | true | Setting this to `false` will completely disable the assistant |
| button | boolean | true | Show the assistant icon in the status bar |
| button | boolean | true | Show the assistant icon |
| dock | string | "right" | The default dock position for the assistant panel. Can be ["left", "right", "bottom"] |
| default_height | string | null | The pixel height of the assistant panel when docked to the bottom |
| default_width | string | null | The pixel width of the assistant panel when docked to the left or right |

View File

@@ -1264,7 +1264,6 @@ List of `integer` column numbers
"font_family": null,
"font_features": null,
"font_size": null,
"line_height": "comfortable",
"option_as_meta": true,
"button": false,
"shell": {},
@@ -1448,46 +1447,6 @@ See Buffer Font Features
}
```
### Terminal: Line Height
- Description: Set the terminal's line height.
- Setting: `line_height`
- Default: `comfortable`
**Options**
1. Use a line height that's `comfortable` for reading, 1.618. (default)
```jsonc
{
"terminal": {
"line_height": "comfortable",
},
}
```
2. Use a `standard` line height, 1.3. This option is useful for TUIs, particularly if they use box characters
```jsonc
{
"terminal": {
"line_height": "standard",
},
}
```
3. Use a custom line height.
```jsonc
{
"terminal": {
"line_height": {
"custom": 2,
},
},
}
```
### Terminal: Option As Meta
- Description: Re-interprets the option keys to act like a 'meta' key, like in Emacs.
@@ -1830,7 +1789,7 @@ Run the `theme selector: toggle` action in the command palette to see a current
- Description: Whether to fold directories automatically when directory has only one directory inside.
- Setting: `auto_fold_dirs`
- Default: `true`
- Default: `false`
**Options**

View File

@@ -15,7 +15,7 @@ For example, if you have Prettier installed and on your `PATH`, you can use it t
{
"languages": {
"JavaScript": {
"formatter": {
"format_on_save": {
"external": {
"command": "prettier",
"arguments": ["--stdin-filepath", "{buffer_path}"]

View File

@@ -1,5 +1,7 @@
# Telemetry in Zed
**Up to date with v0.112.0**
Zed collects anonymous telemetry data to help the team understand how people are using the application and to see what sort of issues they are experiencing.
## Configuring Telemetry Settings
@@ -29,25 +31,118 @@ Telemetry is sent from the application to our servers. Data is proxied through o
Diagnostic events include debug information (stack traces) from crash reports. Reports are sent on the first application launch after the crash occurred. We've built dashboards that allow us to visualize the frequency and severity of issues experienced by users. Having these reports sent automatically allows us to begin implementing fixes without the user needing to file a report in our issue tracker. The plots in the dashboards also give us an informal measurement of the stability of Zed.
You can see what data is sent when a panic occurs by inspecting the `Panic` struct in [crates/telemetry_events/src/telemetry_events.rs](https://github.com/zed-industries/zed/blob/main/crates/telemetry_events/src/telemetry_events.rs#L184) in the zed repo. You can find additional information in the [Debugging Crashes](https://zed.dev/docs/development/debugging-crashes) documentation.
When a panic occurs, the following data is sent:
### Usage Data (Metrics) {#metrics}
#### PanicRequest
To improve Zed and understand how it is being used in the wild, Zed optionally collects usage data like the following:
- `panic`: The panic data
- `token`: An identifier that is used to authenticate the request on zed.dev
- (a) file extensions of opened files;
- (b) features and tools You use within the Editor;
- (c) project statistics (e.g., number of files); and
- (d) frameworks detected in Your projects
#### Panic
Usage Data does not include any of Your software code or sensitive project details. Metric events are reported over HTTPS, and requests are rate-limited to avoid using significant network bandwidth.
- `thread`: The name of the thread that panicked
- `payload`: The panic message
- `location_data`: The location of the panic
- `file`
- `line`
- `backtrace`: The backtrace of the panic
- `app_version`: Zed's app version
- `release_channel`: Zed's release channel
- `stable`
- `preview`
- `dev`
- `os_name`: The name of your operating system
- `os_version`: The version of your operating system
- `architecture`: The architecture of your CPU
- `panicked_on`: The time that the panic occurred
- `installation_id`: An identifier that is unique to each installation of Zed (this differs for stable, preview, and dev builds)
- `session_id`: An identifier that is unique to each Zed session (this differs for each time you open Zed)
Usage Data is associated with a secure random telemetry ID which may be linked to Your email address. This linkage currently serves two purposes: (1) it allows Zed to analyze usage patterns over time while maintaining Your privacy; and (2) it enables Zed to reach out to specific user groups for feedback and improvement suggestions.
### Metrics
Zed also collects metric information based on user actions. Metric events are reported over HTTPS, and requests are rate-limited to avoid using significant network bandwidth. All data remains anonymous, and can't be related to specific Zed users.
The following data is sent:
#### ClickhouseEventRequestBody
- `token`: An identifier that is used to authenticate the request on zed.dev
- `installation_id`: An identifier that is unique to each installation of Zed (this differs for stable, preview, and dev builds)
- `session_id`: An identifier that is unique to each Zed session (this differs for each time you open Zed)
- `is_staff`: A boolean that indicates whether the user is a member of the Zed team or not
- `app_version`: Zed's app version
- `os_name`: The name of your operating system
- `os_version`: The version of your operating system
- `architecture`: The architecture of your CPU
- `release_channel`: Zed's release channel
- `stable`
- `preview`
- `dev`
- `events`: A vector of `ClickhouseEventWrapper`s
#### ClickhouseEventWrapper
- `signed_in`: A boolean that indicates whether the user is signed in or not
- `event`: An enum, where each variant can be one of the following `ClickhouseEvent` variants:
#### ClickhouseEvent
- `editor`
- `operation`: The editor operation that was performed
- `open`
- `save`
- `file_extension`: The extension of the file that was opened or saved
- `vim_mode`: A boolean that indicates whether the user is in vim mode or not
- `copilot_enabled`: A boolean that indicates whether the user has copilot enabled or not
- `copilot_enabled_for_language`: A boolean that indicates whether the user has copilot enabled for the language of the file that was opened or saved
- `milliseconds_since_first_event`: Duration of time between this event's timestamp and the timestamp of the first event in the current batch
- `copilot`
- `suggestion_accepted`: A boolean that indicates whether the suggestion was accepted or not
- `file_extension`: The file extension of the file that was opened or saved
- `milliseconds_since_first_event`: Same as above
- `call`
- `operation`: The call operation that was performed
- `accept incoming`
- `decline incoming`
- `disable microphone`
- `disable screen share`
- `enable microphone`
- `enable screen share`
- `hang up`
- `invite`
- `join channel`
- `open channel notes`
- `share project`
- `unshare project`
- `room_id`: The ID of the room
- `channel_id`: The ID of the channel
- `milliseconds_since_first_event`: Same as above
- `assistant`
- `conversation_id`: The ID of the conversation (for panel events only)
- `kind`: An enum with the following variants:
- `panel`
- `inline`
- `model`: The model that was used
- `milliseconds_since_first_event`: Same as above
- `cpu`
- `usage_as_percentage`: The CPU usage
- `core_count`: The number of cores on the CPU
- `milliseconds_since_first_event`: Same as above
- `memory`
- `memory_in_bytes`: The amount of memory used in bytes
- `virtual_memory_in_bytes`: The amount of virtual memory used in bytes
- `milliseconds_since_first_event`: Same as above
- `app`
- `operation`: The app operation that was performed
- `first open`
- `open`
- `close`
- `milliseconds_since_first_event`: Same as above
You can audit the metrics data that Zed has reported by running the command `zed: open telemetry log` from the command palette, or clicking `Help > View Telemetry Log` in the application menu.
You can see the full list of the event types and exactly the data sent for each by inspecting the `Event` enum and the associated structs in [crates/telemetry_events/src/telemetry_events.rs](https://github.com/zed-industries/zed/blob/main/crates/telemetry_events/src/telemetry_events.rs#L63] in the zed repo.
The telemetry settings can also be configured via the `welcome` screen, which can be invoked via the `workspace: welcome` action in the command palette.
## Concerns and Questions
### Concerns and Questions
If you have concerns about telemetry, please feel free to open issues in our [Zed repository](https://github.com/zed-industries/zed/issues/new/choose).

View File

@@ -1,6 +1,6 @@
[package]
name = "zed_zig"
version = "0.3.0"
version = "0.2.0"
edition = "2021"
publish = false
license = "Apache-2.0"

View File

@@ -1,7 +1,7 @@
id = "zig"
name = "Zig"
description = "Zig support."
version = "0.3.0"
version = "0.2.0"
schema_version = 1
authors = ["Allan Calix <contact@acx.dev>"]
repository = "https://github.com/zed-industries/zed"

View File

@@ -61,40 +61,39 @@ impl ZigExtension {
&language_server_id,
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
);
// We're pinning ZLS to a release that has `.tar.gz` assets, since the latest release does not have
// them, at time of writing.
//
// ZLS tracking issue: https://github.com/zigtools/zls/issues/1879
let release = zed::github_release_by_tag_name("zigtools/zls", "0.11.0")?;
// Note that in github releases and on zlstools.org the tar.gz asset is not shown
// but is available at https://builds.zigtools.org/zls-{os}-{arch}-{version}.tar.gz
let release = zed::latest_github_release(
"zigtools/zls",
zed::GithubReleaseOptions {
require_assets: true,
pre_release: false,
let asset_name = format!(
"zls-{arch}-{os}.{extension}",
arch = match arch {
zed::Architecture::Aarch64 => "aarch64",
zed::Architecture::X86 => "x86",
zed::Architecture::X8664 => "x86_64",
},
)?;
os = match platform {
zed::Os::Mac => "macos",
zed::Os::Linux => "linux",
zed::Os::Windows => "windows",
},
extension = match platform {
zed::Os::Mac | zed::Os::Linux => "tar.gz",
zed::Os::Windows => "zip",
}
);
let arch: &str = match arch {
zed::Architecture::Aarch64 => "aarch64",
zed::Architecture::X86 => "x86",
zed::Architecture::X8664 => "x86_64",
};
let os: &str = match platform {
zed::Os::Mac => "macos",
zed::Os::Linux => "linux",
zed::Os::Windows => "windows",
};
let extension: &str = match platform {
zed::Os::Mac | zed::Os::Linux => "tar.gz",
zed::Os::Windows => "zip",
};
let asset_name: String = format!("zls-{}-{}-{}.{}", os, arch, release.version, extension);
let download_url = format!("https://builds.zigtools.org/{}", asset_name);
let asset = release
.assets
.iter()
.find(|asset| asset.name == asset_name)
.ok_or_else(|| format!("no asset found matching {:?}", asset_name))?;
let version_dir = format!("zls-{}", release.version);
let binary_path = match platform {
zed::Os::Mac | zed::Os::Linux => format!("{version_dir}/zls"),
zed::Os::Mac | zed::Os::Linux => format!("{version_dir}/bin/zls"),
zed::Os::Windows => format!("{version_dir}/zls.exe"),
};
@@ -105,7 +104,7 @@ impl ZigExtension {
);
zed::download_file(
&download_url,
&asset.download_url,
&version_dir,
match platform {
zed::Os::Mac | zed::Os::Linux => zed::DownloadedFileType::GzipTar,

View File

@@ -0,0 +1,39 @@
# SWE-Bench eval
## Zed "agent" flow
- Spin up Zed
- Open project with repo at `base_commit`
- Open assistant panel
- Add /workflow to context (e.g. in System message)
- (Out of band) LLM call to rephrase SWE-bench `problem_statement` into a Zed Assistant user query/prompt
- Trying to simulate user prompt here
- `user_query = rephrase(problem_statement)`
- Add `/auto {user_query}` to populate context
- Store benchmark outputs:
- `/file` calls + `/search` output
- Overlap of these files/snippets with `patch`
- [Stretch]: Overlap of these files/snippets within one-hop of `patch` (hop resolved via LSP go-to-impl call)
- Add `user_query` at end of assistant context
- Run assistant on context
- Apply workflow step resolution
- Apply inline-edits
- Store benchmark outputs:
- Success/failure of step resolution
- Success/failure of "proper" indentation of inline edit
- Success/failure of "overgeneration" of inline edit
- Finally, run tests from test_patch, observe results of `PASS_TO_PASS` + `FAIL_TO_PASS` tests
- Store benchmark outputs:
- Number of patch files modified: all/any/none
- Success/failure of `PASS_TO_PASS` + `FAIL_TO_PASS` tests
## Things to Report
- Rephrased user query (for test case validity)
### /workflow
- Step resolution: OK/fail
- Proper indents in inline edits: OK/fail per edit
- Overgeneration in inline edits: OK/fail per edit
- Number of patch files modified: all/any/none
- Success/failure of `PASS_TO_PASS` + `FAIL_TO_PASS` tests: OK/fail
### /auto {problem_statement}:
- Overlap of `/file` + `/search` outputs with `patch` snippets
- Overlap of these files/snippets within one-hop of `patch`

View File

@@ -0,0 +1,30 @@
# %%
import polars as pl
df = pl.read_parquet('hf://datasets/princeton-nlp/SWE-bench_Verified/data/test-00000-of-00001.parquet')
print(df.head())
print(df.columns)
print(len(df))
# Inspect the head of specific columns
df.select(['repo', 'problem_statement', 'test_patch', 'hints_text']).head()
full_row = df.head(1).to_dict(as_series=False)
import pprint
pp = pprint.PrettyPrinter(indent=4)
print("Repo:")
pp.pprint(full_row['repo'])
print("\nPatch:")
pp.pprint(full_row['patch'])
print("\nTest Patch:")
pp.pprint(full_row['test_patch'])
print("\nProblem Statement:")
pp.pprint(full_row['problem_statement'])
print("\nHints Text:")
pp.pprint(full_row['hints_text'])
print("\nPASS_TO_PASS:")
pp.pprint(full_row['PASS_TO_PASS'])
print("\nFAIL_TO_PASS:")
pp.pprint(full_row['FAIL_TO_PASS'])