Compare commits
17 Commits
cole/git-p
...
v0.161.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d664f56fac | ||
|
|
4f51496e09 | ||
|
|
71921c7a6f | ||
|
|
395a23618c | ||
|
|
fb6774cfff | ||
|
|
06533d5a68 | ||
|
|
cd7416bfb4 | ||
|
|
e5fc5ea366 | ||
|
|
ca006fbeea | ||
|
|
c0099afc2e | ||
|
|
65532a5f10 | ||
|
|
bce4938350 | ||
|
|
1b1a8bad51 | ||
|
|
544b1e94c1 | ||
|
|
25901eace8 | ||
|
|
8db3ea9eda | ||
|
|
15a0b94222 |
15
Cargo.lock
generated
15
Cargo.lock
generated
@@ -6178,12 +6178,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jupyter-serde"
|
||||
version = "0.2.1"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77b96de099fc23d5c21e05de32cc087c8326983895b7f6c242562af01f7d4c81"
|
||||
checksum = "dd71aa17c4fa65e6d7536ab2728881a41f8feb2ee5841c2240516c3c3d65d8b3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
@@ -7178,9 +7177,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nbformat"
|
||||
version = "0.3.2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84f8a9ab08b34237c2c1d0504b794c2ff01c08dfc46a060d160f004a7f479c31"
|
||||
checksum = "c9ffb2ca556072f114bcaf2ca01dde7f1bc8a4946097dd804cb5a22d8af7d6df"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -9995,9 +9994,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "runtimelib"
|
||||
version = "0.16.1"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc7fe3c17675445fe89de68d130be00b7115104924fbcf53a9b0a84b0283fc81"
|
||||
checksum = "fe23ba9967355bbb1be2fb9a8e51bd239ffdf9c791fad5a9b765122ee2bde2e4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-dispatcher",
|
||||
@@ -15063,7 +15062,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.161.0"
|
||||
version = "0.161.2"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
|
||||
@@ -372,7 +372,7 @@ linkify = "0.10.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
nanoid = "0.4"
|
||||
nbformat = "0.3.2"
|
||||
nbformat = "0.5.0"
|
||||
nix = "0.29"
|
||||
num-format = "0.4.4"
|
||||
once_cell = "1.19.0"
|
||||
@@ -406,7 +406,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
|
||||
"stream",
|
||||
] }
|
||||
rsa = "0.9.6"
|
||||
runtimelib = { version = "0.16.1", default-features = false, features = [
|
||||
runtimelib = { version = "0.19.0", default-features = false, features = [
|
||||
"async-dispatcher-runtime",
|
||||
] }
|
||||
rustc-demangle = "0.1.23"
|
||||
|
||||
@@ -564,9 +564,11 @@
|
||||
"ctrl-alt-c": "outline_panel::CopyPath",
|
||||
"alt-ctrl-shift-c": "outline_panel::CopyRelativePath",
|
||||
"alt-ctrl-r": "outline_panel::RevealInFileManager",
|
||||
"space": ["outline_panel::Open", { "change_selection": false }],
|
||||
"space": "outline_panel::Open",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev"
|
||||
"shift-up": "menu::SelectPrev",
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"ctrl-k enter": "editor::OpenExcerptsSplit"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -577,9 +577,11 @@
|
||||
"cmd-alt-c": "outline_panel::CopyPath",
|
||||
"alt-cmd-shift-c": "outline_panel::CopyRelativePath",
|
||||
"alt-cmd-r": "outline_panel::RevealInFileManager",
|
||||
"space": ["outline_panel::Open", { "change_selection": false }],
|
||||
"space": "outline_panel::Open",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev"
|
||||
"shift-up": "menu::SelectPrev",
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"cmd-k enter": "editor::OpenExcerptsSplit"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1080,7 +1080,21 @@ impl AssistantPanel {
|
||||
self.show_updated_summary(&context_editor, cx);
|
||||
cx.notify()
|
||||
}
|
||||
EditorEvent::Edited { .. } => cx.emit(AssistantPanelEvent::ContextEdited),
|
||||
EditorEvent::Edited { .. } => {
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let is_via_ssh = workspace
|
||||
.project()
|
||||
.update(cx, |project, _| project.is_via_ssh());
|
||||
|
||||
workspace
|
||||
.client()
|
||||
.telemetry()
|
||||
.log_edit_event("assistant panel", is_via_ssh);
|
||||
})
|
||||
.log_err();
|
||||
cx.emit(AssistantPanelEvent::ContextEdited)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ pub struct InlineAssistant {
|
||||
confirmed_assists: HashMap<InlineAssistId, Model<CodegenAlternative>>,
|
||||
prompt_history: VecDeque<String>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ impl InlineAssistant {
|
||||
confirmed_assists: HashMap::default(),
|
||||
prompt_history: VecDeque::default(),
|
||||
prompt_builder,
|
||||
telemetry: Some(telemetry),
|
||||
telemetry,
|
||||
fs,
|
||||
}
|
||||
}
|
||||
@@ -241,19 +241,17 @@ impl InlineAssistant {
|
||||
codegen_ranges.push(start..end);
|
||||
|
||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||
telemetry.report_assistant_event(AssistantEvent {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::Inline,
|
||||
phase: AssistantPhase::Invoked,
|
||||
message_id: None,
|
||||
model: model.telemetry_id(),
|
||||
model_provider: model.provider_id().to_string(),
|
||||
response_latency: None,
|
||||
error_message: None,
|
||||
language_name: buffer.language().map(|language| language.name().to_proto()),
|
||||
});
|
||||
}
|
||||
self.telemetry.report_assistant_event(AssistantEvent {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::Inline,
|
||||
phase: AssistantPhase::Invoked,
|
||||
message_id: None,
|
||||
model: model.telemetry_id(),
|
||||
model_provider: model.provider_id().to_string(),
|
||||
response_latency: None,
|
||||
error_message: None,
|
||||
language_name: buffer.language().map(|language| language.name().to_proto()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,7 +814,7 @@ impl InlineAssistant {
|
||||
error_message: None,
|
||||
language_name: language_name.map(|name| name.to_proto()),
|
||||
},
|
||||
self.telemetry.clone(),
|
||||
Some(self.telemetry.clone()),
|
||||
cx.http_client(),
|
||||
model.api_key(cx),
|
||||
cx.background_executor(),
|
||||
@@ -1757,6 +1755,20 @@ impl PromptEditor {
|
||||
) {
|
||||
match event {
|
||||
EditorEvent::Edited { .. } => {
|
||||
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let is_via_ssh = workspace
|
||||
.project()
|
||||
.update(cx, |project, _| project.is_via_ssh());
|
||||
|
||||
workspace
|
||||
.client()
|
||||
.telemetry()
|
||||
.log_edit_event("inline assist", is_via_ssh);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
let prompt = self.editor.read(cx).text(cx);
|
||||
if self
|
||||
.prompt_history_ix
|
||||
@@ -2290,7 +2302,7 @@ pub struct Codegen {
|
||||
buffer: Model<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
is_insertion: bool,
|
||||
}
|
||||
@@ -2300,7 +2312,7 @@ impl Codegen {
|
||||
buffer: Model<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
@@ -2309,7 +2321,7 @@ impl Codegen {
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
false,
|
||||
telemetry.clone(),
|
||||
Some(telemetry.clone()),
|
||||
builder.clone(),
|
||||
cx,
|
||||
)
|
||||
@@ -2400,7 +2412,7 @@ impl Codegen {
|
||||
self.buffer.clone(),
|
||||
self.range.clone(),
|
||||
false,
|
||||
self.telemetry.clone(),
|
||||
Some(self.telemetry.clone()),
|
||||
self.builder.clone(),
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -87,11 +87,11 @@ impl Model {
|
||||
|
||||
pub fn max_token_count(&self) -> usize {
|
||||
match self {
|
||||
Self::Gpt4o => 128000,
|
||||
Self::Gpt4 => 8192,
|
||||
Self::Gpt3_5Turbo => 16385,
|
||||
Self::O1Mini => 128000,
|
||||
Self::O1Preview => 128000,
|
||||
Self::Gpt4o => 64000,
|
||||
Self::Gpt4 => 32768,
|
||||
Self::Gpt3_5Turbo => 12288,
|
||||
Self::O1Mini => 20000,
|
||||
Self::O1Preview => 20000,
|
||||
Self::Claude3_5Sonnet => 200_000,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ pub mod test;
|
||||
|
||||
use ::git::diff::DiffHunkStatus;
|
||||
pub(crate) use actions::*;
|
||||
pub use actions::{OpenExcerpts, OpenExcerptsSplit};
|
||||
use aho_corasick::AhoCorasick;
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use blink_manager::BlinkManager;
|
||||
@@ -12539,11 +12540,11 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
fn open_excerpts_in_split(&mut self, _: &OpenExcerptsSplit, cx: &mut ViewContext<Self>) {
|
||||
pub fn open_excerpts_in_split(&mut self, _: &OpenExcerptsSplit, cx: &mut ViewContext<Self>) {
|
||||
self.open_excerpts_common(true, cx)
|
||||
}
|
||||
|
||||
fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
|
||||
pub fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
|
||||
self.open_excerpts_common(false, cx)
|
||||
}
|
||||
|
||||
|
||||
@@ -78,13 +78,18 @@ impl Extension {
|
||||
version: SemanticVersion,
|
||||
component: &Component,
|
||||
) -> Result<Self> {
|
||||
// Note: The release channel can be used to stage a new version of the extension API.
|
||||
let allow_latest_version = match release_channel {
|
||||
ReleaseChannel::Dev | ReleaseChannel::Nightly => true,
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview => false,
|
||||
};
|
||||
|
||||
if allow_latest_version && version >= latest::MIN_VERSION {
|
||||
if version >= latest::MIN_VERSION {
|
||||
// Note: The release channel can be used to stage a new version of the extension API.
|
||||
// We always allow the latest in tests so that the extension tests pass on release branches.
|
||||
let allow_latest_version = match release_channel {
|
||||
ReleaseChannel::Dev | ReleaseChannel::Nightly => true,
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview => cfg!(test),
|
||||
};
|
||||
if !allow_latest_version {
|
||||
Err(anyhow!(
|
||||
"unreleased versions of the extension API can only be used on development builds of Zed"
|
||||
))?;
|
||||
}
|
||||
let extension =
|
||||
latest::Extension::instantiate_async(store, component, latest::linker())
|
||||
.await
|
||||
|
||||
@@ -68,7 +68,7 @@ pub(crate) struct DispatchNodeId(usize);
|
||||
|
||||
pub(crate) struct DispatchTree {
|
||||
node_stack: Vec<DispatchNodeId>,
|
||||
context_stack: Vec<KeyContext>,
|
||||
pub(crate) context_stack: Vec<KeyContext>,
|
||||
view_stack: Vec<EntityId>,
|
||||
nodes: Vec<DispatchNode>,
|
||||
focusable_node_ids: FxHashMap<FocusId, DispatchNodeId>,
|
||||
@@ -396,13 +396,13 @@ impl DispatchTree {
|
||||
pub fn bindings_for_action(
|
||||
&self,
|
||||
action: &dyn Action,
|
||||
context_path: &[KeyContext],
|
||||
context_stack: &[KeyContext],
|
||||
) -> Vec<KeyBinding> {
|
||||
let keymap = self.keymap.borrow();
|
||||
keymap
|
||||
.bindings_for_action(action)
|
||||
.filter(|binding| {
|
||||
let (bindings, _) = keymap.bindings_for_input(&binding.keystrokes, context_path);
|
||||
let (bindings, _) = keymap.bindings_for_input(&binding.keystrokes, context_stack);
|
||||
bindings
|
||||
.iter()
|
||||
.next()
|
||||
@@ -519,13 +519,6 @@ impl DispatchTree {
|
||||
dispatch_path
|
||||
}
|
||||
|
||||
pub fn context_path(&self, node_id: DispatchNodeId) -> SmallVec<[KeyContext; 32]> {
|
||||
self.dispatch_path(node_id)
|
||||
.into_iter()
|
||||
.filter_map(|node_id| self.node(node_id).context.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn focus_path(&self, focus_id: FocusId) -> SmallVec<[FocusId; 8]> {
|
||||
let mut focus_path: SmallVec<[FocusId; 8]> = SmallVec::new();
|
||||
let mut current_node_id = self.focusable_node_ids.get(&focus_id).copied();
|
||||
|
||||
@@ -139,7 +139,7 @@ impl Keymap {
|
||||
pub fn bindings_for_input(
|
||||
&self,
|
||||
input: &[Keystroke],
|
||||
context_path: &[KeyContext],
|
||||
context_stack: &[KeyContext],
|
||||
) -> (SmallVec<[KeyBinding; 1]>, bool) {
|
||||
let possibilities = self.bindings().rev().filter_map(|binding| {
|
||||
binding
|
||||
@@ -151,8 +151,8 @@ impl Keymap {
|
||||
let mut is_pending = None;
|
||||
|
||||
'outer: for (binding, pending) in possibilities {
|
||||
for depth in (0..=context_path.len()).rev() {
|
||||
if self.binding_enabled(binding, &context_path[0..depth]) {
|
||||
for depth in (0..=context_stack.len()).rev() {
|
||||
if self.binding_enabled(binding, &context_stack[0..depth]) {
|
||||
if is_pending.is_none() {
|
||||
is_pending = Some(pending);
|
||||
}
|
||||
|
||||
@@ -3683,11 +3683,22 @@ impl<'a> WindowContext<'a> {
|
||||
|
||||
/// Returns all available actions for the focused element.
|
||||
pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
|
||||
let node_id = self
|
||||
.window
|
||||
.focus
|
||||
.and_then(|focus_id| {
|
||||
self.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.focusable_node_id(focus_id)
|
||||
})
|
||||
.unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id());
|
||||
|
||||
let mut actions = self
|
||||
.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.available_actions(self.focused_node_id());
|
||||
.available_actions(node_id);
|
||||
for action_type in self.global_action_listeners.keys() {
|
||||
if let Err(ix) = actions.binary_search_by_key(action_type, |a| a.as_any().type_id()) {
|
||||
let action = self.actions.build_action_type(action_type).ok();
|
||||
@@ -3701,9 +3712,13 @@ impl<'a> WindowContext<'a> {
|
||||
|
||||
/// Returns key bindings that invoke the given action on the currently focused element.
|
||||
pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
|
||||
let dispatch_tree = &self.window.rendered_frame.dispatch_tree;
|
||||
dispatch_tree
|
||||
.bindings_for_action(action, &dispatch_tree.context_path(self.focused_node_id()))
|
||||
self.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.bindings_for_action(
|
||||
action,
|
||||
&self.window.rendered_frame.dispatch_tree.context_stack,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns key bindings that invoke the given action on the currently focused element.
|
||||
@@ -3719,23 +3734,15 @@ impl<'a> WindowContext<'a> {
|
||||
) -> Vec<KeyBinding> {
|
||||
let dispatch_tree = &self.window.rendered_frame.dispatch_tree;
|
||||
|
||||
if let Some(node_id) = dispatch_tree.focusable_node_id(focus_handle.id) {
|
||||
dispatch_tree.bindings_for_action(action, &dispatch_tree.context_path(node_id))
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn focused_node_id(&self) -> DispatchNodeId {
|
||||
self.window
|
||||
.focus
|
||||
.and_then(|focus_id| {
|
||||
self.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.focusable_node_id(focus_id)
|
||||
})
|
||||
.unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id())
|
||||
let Some(node_id) = dispatch_tree.focusable_node_id(focus_handle.id) else {
|
||||
return vec![];
|
||||
};
|
||||
let context_stack: Vec<_> = dispatch_tree
|
||||
.dispatch_path(node_id)
|
||||
.into_iter()
|
||||
.filter_map(|node_id| dispatch_tree.node(node_id).context.clone())
|
||||
.collect();
|
||||
dispatch_tree.bindings_for_action(action, &context_stack)
|
||||
}
|
||||
|
||||
/// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle.
|
||||
|
||||
@@ -23,9 +23,9 @@ use editor::{
|
||||
use file_icons::FileIcons;
|
||||
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, anchored, deferred, div, impl_actions, point, px, size, uniform_list, Action,
|
||||
AnyElement, AppContext, AssetSource, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent,
|
||||
Div, ElementId, EventEmitter, FocusHandle, FocusableView, HighlightStyle, InteractiveElement,
|
||||
actions, anchored, deferred, div, point, px, size, uniform_list, Action, AnyElement,
|
||||
AppContext, AssetSource, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div,
|
||||
ElementId, EventEmitter, FocusHandle, FocusableView, HighlightStyle, InteractiveElement,
|
||||
IntoElement, KeyContext, ListHorizontalSizingBehavior, ListSizingBehavior, Model, MouseButton,
|
||||
MouseDownEvent, ParentElement, Pixels, Point, Render, SharedString, Stateful,
|
||||
StatefulInteractiveElement as _, Styled, Subscription, Task, UniformListScrollHandle, View,
|
||||
@@ -58,13 +58,6 @@ use workspace::{
|
||||
};
|
||||
use worktree::{Entry, ProjectEntryId, WorktreeId};
|
||||
|
||||
#[derive(Clone, Default, Deserialize, PartialEq)]
|
||||
pub struct Open {
|
||||
change_selection: bool,
|
||||
}
|
||||
|
||||
impl_actions!(outline_panel, [Open]);
|
||||
|
||||
actions!(
|
||||
outline_panel,
|
||||
[
|
||||
@@ -75,9 +68,10 @@ actions!(
|
||||
ExpandAllEntries,
|
||||
ExpandSelectedEntry,
|
||||
FoldDirectory,
|
||||
ToggleActiveEditorPin,
|
||||
Open,
|
||||
RevealInFileManager,
|
||||
SelectParent,
|
||||
ToggleActiveEditorPin,
|
||||
ToggleFocus,
|
||||
UnfoldDirectory,
|
||||
]
|
||||
@@ -813,11 +807,11 @@ impl OutlinePanel {
|
||||
self.update_cached_entries(None, cx);
|
||||
}
|
||||
|
||||
fn open(&mut self, open: &Open, cx: &mut ViewContext<Self>) {
|
||||
fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
|
||||
if self.filter_editor.focus_handle(cx).is_focused(cx) {
|
||||
cx.propagate()
|
||||
} else if let Some(selected_entry) = self.selected_entry().cloned() {
|
||||
self.open_entry(&selected_entry, open.change_selection, cx);
|
||||
self.open_entry(&selected_entry, true, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -834,6 +828,32 @@ impl OutlinePanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn open_excerpts(&mut self, action: &editor::OpenExcerpts, cx: &mut ViewContext<Self>) {
|
||||
if self.filter_editor.focus_handle(cx).is_focused(cx) {
|
||||
cx.propagate()
|
||||
} else if let Some((active_editor, selected_entry)) =
|
||||
self.active_editor().zip(self.selected_entry().cloned())
|
||||
{
|
||||
self.open_entry(&selected_entry, true, cx);
|
||||
active_editor.update(cx, |editor, cx| editor.open_excerpts(action, cx));
|
||||
}
|
||||
}
|
||||
|
||||
fn open_excerpts_split(
|
||||
&mut self,
|
||||
action: &editor::OpenExcerptsSplit,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if self.filter_editor.focus_handle(cx).is_focused(cx) {
|
||||
cx.propagate()
|
||||
} else if let Some((active_editor, selected_entry)) =
|
||||
self.active_editor().zip(self.selected_entry().cloned())
|
||||
{
|
||||
self.open_entry(&selected_entry, true, cx);
|
||||
active_editor.update(cx, |editor, cx| editor.open_excerpts_in_split(action, cx));
|
||||
}
|
||||
}
|
||||
|
||||
fn open_entry(
|
||||
&mut self,
|
||||
entry: &PanelEntry,
|
||||
@@ -851,7 +871,6 @@ impl OutlinePanel {
|
||||
Point::new(0.0, -(active_editor.read(cx).file_header_size() as f32))
|
||||
};
|
||||
|
||||
self.toggle_expanded(entry, cx);
|
||||
let scroll_target = match entry {
|
||||
PanelEntry::FoldedDirs(..) | PanelEntry::Fs(FsEntry::Directory(..)) => None,
|
||||
PanelEntry::Fs(FsEntry::ExternalFile(buffer_id, _)) => {
|
||||
@@ -949,6 +968,9 @@ impl OutlinePanel {
|
||||
} else {
|
||||
self.select_first(&SelectFirst {}, cx)
|
||||
}
|
||||
if let Some(selected_entry) = self.selected_entry().cloned() {
|
||||
self.open_entry(&selected_entry, false, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
|
||||
@@ -965,6 +987,9 @@ impl OutlinePanel {
|
||||
} else {
|
||||
self.select_last(&SelectLast, cx)
|
||||
}
|
||||
if let Some(selected_entry) = self.selected_entry().cloned() {
|
||||
self.open_entry(&selected_entry, false, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn select_parent(&mut self, _: &SelectParent, cx: &mut ViewContext<Self>) {
|
||||
@@ -1449,10 +1474,7 @@ impl OutlinePanel {
|
||||
}
|
||||
|
||||
fn reveal_entry_for_selection(&mut self, editor: View<Editor>, cx: &mut ViewContext<'_, Self>) {
|
||||
if !self.active {
|
||||
return;
|
||||
}
|
||||
if !OutlinePanelSettings::get_global(cx).auto_reveal_entries {
|
||||
if !self.active || !OutlinePanelSettings::get_global(cx).auto_reveal_entries {
|
||||
return;
|
||||
}
|
||||
let project = self.project.clone();
|
||||
@@ -2005,6 +2027,7 @@ impl OutlinePanel {
|
||||
return;
|
||||
}
|
||||
let change_selection = event.down.click_count > 1;
|
||||
outline_panel.toggle_expanded(&clicked_entry, cx);
|
||||
outline_panel.open_entry(&clicked_entry, change_selection, cx);
|
||||
})
|
||||
})
|
||||
@@ -2447,19 +2470,20 @@ impl OutlinePanel {
|
||||
}
|
||||
|
||||
fn clear_previous(&mut self, cx: &mut WindowContext<'_>) {
|
||||
self.fs_entries_update_task = Task::ready(());
|
||||
self.outline_fetch_tasks.clear();
|
||||
self.cached_entries_update_task = Task::ready(());
|
||||
self.reveal_selection_task = Task::ready(Ok(()));
|
||||
self.filter_editor.update(cx, |editor, cx| editor.clear(cx));
|
||||
self.collapsed_entries.clear();
|
||||
self.unfolded_dirs.clear();
|
||||
self.selected_entry = SelectedEntry::None;
|
||||
self.fs_entries_update_task = Task::ready(());
|
||||
self.cached_entries_update_task = Task::ready(());
|
||||
self.active_item = None;
|
||||
self.fs_entries.clear();
|
||||
self.fs_entries_depth.clear();
|
||||
self.fs_children_count.clear();
|
||||
self.outline_fetch_tasks.clear();
|
||||
self.excerpts.clear();
|
||||
self.cached_entries = Vec::new();
|
||||
self.selected_entry = SelectedEntry::None;
|
||||
self.pinned = false;
|
||||
self.mode = ItemsDisplayMode::Outline;
|
||||
}
|
||||
@@ -2488,10 +2512,6 @@ impl OutlinePanel {
|
||||
.matches
|
||||
.iter()
|
||||
.rev()
|
||||
.filter(|(match_range, _)| {
|
||||
match_range.start.excerpt_id == excerpt_id
|
||||
|| match_range.end.excerpt_id == excerpt_id
|
||||
})
|
||||
.min_by_key(|&(match_range, _)| {
|
||||
let match_display_range =
|
||||
match_range.clone().to_display_points(&editor_snapshot);
|
||||
@@ -4244,6 +4264,8 @@ impl Render for OutlinePanel {
|
||||
.on_action(cx.listener(Self::toggle_active_editor_pin))
|
||||
.on_action(cx.listener(Self::unfold_directory))
|
||||
.on_action(cx.listener(Self::fold_directory))
|
||||
.on_action(cx.listener(Self::open_excerpts))
|
||||
.on_action(cx.listener(Self::open_excerpts_split))
|
||||
.when(is_local, |el| {
|
||||
el.on_action(cx.listener(Self::reveal_in_finder))
|
||||
})
|
||||
@@ -4725,6 +4747,189 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_item_opening(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
populate_with_test_ra_project(&fs, "/rust-analyzer").await;
|
||||
let project = Project::test(fs.clone(), ["/rust-analyzer".as_ref()], cx).await;
|
||||
project.read_with(cx, |project, _| {
|
||||
project.languages().add(Arc::new(rust_lang()))
|
||||
});
|
||||
let workspace = add_outline_panel(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let outline_panel = outline_panel(&workspace, cx);
|
||||
outline_panel.update(cx, |outline_panel, cx| outline_panel.set_active(true, cx));
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch::default(), cx)
|
||||
})
|
||||
.unwrap();
|
||||
let search_view = workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view expected to appear after new search event trigger")
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let query = "param_names_for_lifetime_elision_hints";
|
||||
perform_project_search(&search_view, query, cx);
|
||||
search_view.update(cx, |search_view, cx| {
|
||||
search_view
|
||||
.results_editor()
|
||||
.update(cx, |results_editor, cx| {
|
||||
assert_eq!(
|
||||
results_editor.display_text(cx).match_indices(query).count(),
|
||||
9
|
||||
);
|
||||
});
|
||||
});
|
||||
let all_matches = r#"/
|
||||
crates/
|
||||
ide/src/
|
||||
inlay_hints/
|
||||
fn_lifetime_fn.rs
|
||||
search: match config.param_names_for_lifetime_elision_hints {
|
||||
search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {
|
||||
search: Some(it) if config.param_names_for_lifetime_elision_hints => {
|
||||
search: InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG },
|
||||
inlay_hints.rs
|
||||
search: pub param_names_for_lifetime_elision_hints: bool,
|
||||
search: param_names_for_lifetime_elision_hints: self
|
||||
static_index.rs
|
||||
search: param_names_for_lifetime_elision_hints: false,
|
||||
rust-analyzer/src/
|
||||
cli/
|
||||
analysis_stats.rs
|
||||
search: param_names_for_lifetime_elision_hints: true,
|
||||
config.rs
|
||||
search: param_names_for_lifetime_elision_hints: self"#;
|
||||
let select_first_in_all_matches = |line_to_select: &str| {
|
||||
assert!(all_matches.contains(line_to_select));
|
||||
all_matches.replacen(
|
||||
line_to_select,
|
||||
&format!("{line_to_select}{SELECTED_MARKER}"),
|
||||
1,
|
||||
)
|
||||
};
|
||||
cx.executor()
|
||||
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));
|
||||
cx.run_until_parked();
|
||||
|
||||
let active_editor = outline_panel.update(cx, |outline_panel, _| {
|
||||
outline_panel
|
||||
.active_editor()
|
||||
.expect("should have an active editor open")
|
||||
});
|
||||
let initial_outline_selection =
|
||||
"search: match config.param_names_for_lifetime_elision_hints {";
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
),
|
||||
select_first_in_all_matches(initial_outline_selection)
|
||||
);
|
||||
assert_eq!(
|
||||
selected_row_text(&active_editor, cx),
|
||||
initial_outline_selection.replace("search: ", ""), // Clear outline metadata prefixes
|
||||
"Should place the initial editor selection on the corresponding search result"
|
||||
);
|
||||
|
||||
outline_panel.select_next(&SelectNext, cx);
|
||||
outline_panel.select_next(&SelectNext, cx);
|
||||
});
|
||||
|
||||
let navigated_outline_selection =
|
||||
"search: Some(it) if config.param_names_for_lifetime_elision_hints => {";
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
),
|
||||
select_first_in_all_matches(navigated_outline_selection)
|
||||
);
|
||||
assert_eq!(
|
||||
selected_row_text(&active_editor, cx),
|
||||
initial_outline_selection.replace("search: ", ""), // Clear outline metadata prefixes
|
||||
"Should still have the initial caret position after SelectNext calls"
|
||||
);
|
||||
});
|
||||
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
outline_panel.open(&Open, cx);
|
||||
});
|
||||
outline_panel.update(cx, |_, cx| {
|
||||
assert_eq!(
|
||||
selected_row_text(&active_editor, cx),
|
||||
navigated_outline_selection.replace("search: ", ""), // Clear outline metadata prefixes
|
||||
"After opening, should move the caret to the opened outline entry's position"
|
||||
);
|
||||
});
|
||||
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
outline_panel.select_next(&SelectNext, cx);
|
||||
});
|
||||
let next_navigated_outline_selection =
|
||||
"search: InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG },";
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
),
|
||||
select_first_in_all_matches(next_navigated_outline_selection)
|
||||
);
|
||||
assert_eq!(
|
||||
selected_row_text(&active_editor, cx),
|
||||
navigated_outline_selection.replace("search: ", ""), // Clear outline metadata prefixes
|
||||
"Should again preserve the selection after another SelectNext call"
|
||||
);
|
||||
});
|
||||
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
outline_panel.open_excerpts(&editor::OpenExcerpts, cx);
|
||||
});
|
||||
cx.executor()
|
||||
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));
|
||||
cx.run_until_parked();
|
||||
let new_active_editor = outline_panel.update(cx, |outline_panel, _| {
|
||||
outline_panel
|
||||
.active_editor()
|
||||
.expect("should have an active editor open")
|
||||
});
|
||||
outline_panel.update(cx, |outline_panel, cx| {
|
||||
assert_ne!(
|
||||
active_editor, new_active_editor,
|
||||
"After opening an excerpt, new editor should be open"
|
||||
);
|
||||
assert_eq!(
|
||||
display_entries(
|
||||
&snapshot(&outline_panel, cx),
|
||||
&outline_panel.cached_entries,
|
||||
outline_panel.selected_entry(),
|
||||
),
|
||||
"fn_lifetime_fn.rs <==== selected"
|
||||
);
|
||||
assert_eq!(
|
||||
selected_row_text(&new_active_editor, cx),
|
||||
next_navigated_outline_selection.replace("search: ", ""), // Clear outline metadata prefixes
|
||||
"When opening the excerpt, should navigate to the place corresponding the outline entry"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_frontend_repo_structure(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
@@ -5194,4 +5399,16 @@ mod tests {
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
}
|
||||
|
||||
fn selected_row_text(editor: &View<Editor>, cx: &mut WindowContext) -> String {
|
||||
editor.update(cx, |editor, cx| {
|
||||
let selections = editor.selections.all::<language::Point>(cx);
|
||||
assert_eq!(selections.len(), 1, "Active editor should have exactly one selection after any outline panel interactions");
|
||||
let selection = selections.first().unwrap();
|
||||
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let line_start = language::Point::new(selection.start.row, 0);
|
||||
let line_end = multi_buffer_snapshot.clip_point(language::Point::new(selection.end.row, u32::MAX), language::Bias::Right);
|
||||
multi_buffer_snapshot.text_for_range(line_start..line_end).collect::<String>().trim().to_owned()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2643,7 +2643,7 @@ impl ProjectPanel {
|
||||
entry_id,
|
||||
cx.modifiers().secondary(),
|
||||
!preview_tabs_enabled || click_count > 1,
|
||||
!preview_tabs_enabled && click_count == 1,
|
||||
preview_tabs_enabled && click_count == 1,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,16 @@ impl QuickActionBar {
|
||||
|
||||
let editor = self.active_editor()?;
|
||||
|
||||
let is_local_project = editor
|
||||
.read(cx)
|
||||
.workspace()
|
||||
.map(|workspace| workspace.read(cx).project().read(cx).is_local())
|
||||
.unwrap_or(false);
|
||||
|
||||
if !is_local_project {
|
||||
return None;
|
||||
}
|
||||
|
||||
let has_nonempty_selection = {
|
||||
editor.update(cx, |this, cx| {
|
||||
this.selections
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
use anyhow::Result;
|
||||
use base64::prelude::*;
|
||||
use base64::{
|
||||
alphabet,
|
||||
engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig},
|
||||
Engine as _,
|
||||
};
|
||||
use gpui::{img, ClipboardItem, Image, ImageFormat, Pixels, RenderImage, WindowContext};
|
||||
use std::sync::Arc;
|
||||
use ui::{div, prelude::*, IntoElement, Styled};
|
||||
@@ -14,11 +18,21 @@ pub struct ImageView {
|
||||
image: Arc<RenderImage>,
|
||||
}
|
||||
|
||||
pub const STANDARD_INDIFFERENT: GeneralPurpose = GeneralPurpose::new(
|
||||
&alphabet::STANDARD,
|
||||
GeneralPurposeConfig::new()
|
||||
.with_encode_padding(false)
|
||||
.with_decode_padding_mode(DecodePaddingMode::Indifferent),
|
||||
);
|
||||
|
||||
impl ImageView {
|
||||
pub fn from(base64_encoded_data: &str) -> Result<Self> {
|
||||
let bytes = BASE64_STANDARD.decode(base64_encoded_data.trim())?;
|
||||
let filtered =
|
||||
base64_encoded_data.replace(&[' ', '\n', '\t', '\r', '\x0b', '\x0c'][..], "");
|
||||
let bytes = STANDARD_INDIFFERENT.decode(filtered)?;
|
||||
|
||||
let format = image::guess_format(&bytes)?;
|
||||
|
||||
let mut data = image::load_from_memory_with_format(&bytes, format)?.into_rgba8();
|
||||
|
||||
// Convert from RGBA to BGRA.
|
||||
|
||||
@@ -61,85 +61,97 @@ pub fn init(cx: &mut AppContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
let editor_handle = cx.view().downgrade();
|
||||
cx.defer(|editor, cx| {
|
||||
let workspace = Workspace::for_window(cx);
|
||||
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &Run, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
let is_local_project = workspace
|
||||
.map(|workspace| workspace.read(cx).project().read(cx).is_local())
|
||||
.unwrap_or(false);
|
||||
|
||||
if !is_local_project {
|
||||
return;
|
||||
}
|
||||
|
||||
let editor_handle = cx.view().downgrade();
|
||||
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &Run, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
crate::run(editor_handle.clone(), true, cx).log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
crate::run(editor_handle.clone(), true, cx).log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &RunInPlace, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &RunInPlace, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
crate::run(editor_handle.clone(), false, cx).log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
crate::run(editor_handle.clone(), false, cx).log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &ClearOutputs, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &ClearOutputs, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
crate::clear_outputs(editor_handle.clone(), cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
crate::clear_outputs(editor_handle.clone(), cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &Interrupt, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &Interrupt, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
crate::interrupt(editor_handle.clone(), cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
crate::interrupt(editor_handle.clone(), cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &Shutdown, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &Shutdown, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
crate::shutdown(editor_handle.clone(), cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
crate::shutdown(editor_handle.clone(), cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &Restart, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &Restart, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
crate::restart(editor_handle.clone(), cx);
|
||||
}
|
||||
|
||||
crate::restart(editor_handle.clone(), cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
@@ -4540,6 +4540,11 @@ impl Workspace {
|
||||
.children(leader_border),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn for_window(cx: &mut WindowContext) -> Option<View<Workspace>> {
|
||||
let window = cx.window_handle().downcast::<Workspace>()?;
|
||||
cx.read_window(&window, |workspace, _| workspace).ok()
|
||||
}
|
||||
}
|
||||
|
||||
fn leader_border_for_pane(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition = "2021"
|
||||
name = "zed"
|
||||
version = "0.161.0"
|
||||
version = "0.161.2"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Zed Team <hi@zed.dev>"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
stable
|
||||
@@ -15,7 +15,7 @@ version=$(script/get-crate-version zed)
|
||||
channel=$(cat crates/zed/RELEASE_CHANNEL)
|
||||
echo "Publishing version: ${version} on release channel ${channel}"
|
||||
echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
|
||||
echo "RELEASE_VERSION="${version}" >> $GITHUB_ENV
|
||||
echo "RELEASE_VERSION=${version}" >> $GITHUB_ENV
|
||||
|
||||
expected_tag_name=""
|
||||
case ${channel} in
|
||||
|
||||
Reference in New Issue
Block a user