Add slash commands for adding context into the assistant (#12102)
Tasks * [x] remove old flaps and output when editing a slash command * [x] the completing a command name that takes args, insert a space to prepare for typing an arg * [x] always trigger completions when typing in a slash command * [x] don't show line numbers * [x] implement `prompt` command * [x] `current-file` command * [x] state gets corrupted on `duplicate line up` on a slash command * [x] exclude slash command source from completion request Next steps: * show output token count in flap trailer * add `/project` command that matches project ambient context * delete ambient context Release Notes: - N/A --------- Co-authored-by: Marshall <marshall@zed.dev> Co-authored-by: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
parent
d6e59bfae1
commit
a73a3ef243
18 changed files with 1649 additions and 147 deletions
|
@ -449,6 +449,9 @@ pub struct Editor {
|
|||
mode: EditorMode,
|
||||
show_breadcrumbs: bool,
|
||||
show_gutter: bool,
|
||||
show_line_numbers: Option<bool>,
|
||||
show_git_diff_gutter: Option<bool>,
|
||||
show_code_actions: Option<bool>,
|
||||
show_wrap_guides: Option<bool>,
|
||||
placeholder_text: Option<Arc<str>>,
|
||||
highlight_order: usize,
|
||||
|
@ -517,6 +520,9 @@ pub struct Editor {
|
|||
pub struct EditorSnapshot {
|
||||
pub mode: EditorMode,
|
||||
show_gutter: bool,
|
||||
show_line_numbers: Option<bool>,
|
||||
show_git_diff_gutter: Option<bool>,
|
||||
show_code_actions: Option<bool>,
|
||||
render_git_blame_gutter: bool,
|
||||
pub display_snapshot: DisplaySnapshot,
|
||||
pub placeholder_text: Option<Arc<str>>,
|
||||
|
@ -1646,6 +1652,9 @@ impl Editor {
|
|||
mode,
|
||||
show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
|
||||
show_gutter: mode == EditorMode::Full,
|
||||
show_line_numbers: None,
|
||||
show_git_diff_gutter: None,
|
||||
show_code_actions: None,
|
||||
show_wrap_guides: None,
|
||||
placeholder_text: None,
|
||||
highlight_order: 0,
|
||||
|
@ -1881,6 +1890,9 @@ impl Editor {
|
|||
EditorSnapshot {
|
||||
mode: self.mode,
|
||||
show_gutter: self.show_gutter,
|
||||
show_line_numbers: self.show_line_numbers,
|
||||
show_git_diff_gutter: self.show_git_diff_gutter,
|
||||
show_code_actions: self.show_code_actions,
|
||||
render_git_blame_gutter: self.render_git_blame_gutter(cx),
|
||||
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
|
||||
scroll_anchor: self.scroll_manager.anchor(),
|
||||
|
@ -1933,8 +1945,8 @@ impl Editor {
|
|||
self.custom_context_menu = Some(Box::new(f))
|
||||
}
|
||||
|
||||
pub fn set_completion_provider(&mut self, hub: Box<dyn CompletionProvider>) {
|
||||
self.completion_provider = Some(hub);
|
||||
pub fn set_completion_provider(&mut self, provider: Box<dyn CompletionProvider>) {
|
||||
self.completion_provider = Some(provider);
|
||||
}
|
||||
|
||||
pub fn set_inline_completion_provider<T>(
|
||||
|
@ -3280,22 +3292,41 @@ impl Editor {
|
|||
trigger_in_words: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if !EditorSettings::get_global(cx).show_completions_on_input {
|
||||
return;
|
||||
}
|
||||
|
||||
let selection = self.selections.newest_anchor();
|
||||
if self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.is_completion_trigger(selection.head(), text, trigger_in_words, cx)
|
||||
{
|
||||
if self.is_completion_trigger(text, trigger_in_words, cx) {
|
||||
self.show_completions(&ShowCompletions, cx);
|
||||
} else {
|
||||
self.hide_context_menu(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_completion_trigger(
|
||||
&self,
|
||||
text: &str,
|
||||
trigger_in_words: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> bool {
|
||||
let position = self.selections.newest_anchor().head();
|
||||
let multibuffer = self.buffer.read(cx);
|
||||
let Some(buffer) = position
|
||||
.buffer_id
|
||||
.and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if let Some(completion_provider) = &self.completion_provider {
|
||||
completion_provider.is_completion_trigger(
|
||||
&buffer,
|
||||
position.text_anchor,
|
||||
text,
|
||||
trigger_in_words,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// If any empty selections is touching the start of its innermost containing autoclose
|
||||
/// region, expand it to select the brackets.
|
||||
fn select_autoclose_pair(&mut self, cx: &mut ViewContext<Self>) {
|
||||
|
@ -9613,8 +9644,27 @@ impl Editor {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_show_wrap_guides(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
|
||||
self.show_wrap_guides = Some(show_gutter);
|
||||
pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut ViewContext<Self>) {
|
||||
self.show_line_numbers = Some(show_line_numbers);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_show_git_diff_gutter(
|
||||
&mut self,
|
||||
show_git_diff_gutter: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.show_git_diff_gutter = Some(show_git_diff_gutter);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut ViewContext<Self>) {
|
||||
self.show_code_actions = Some(show_code_actions);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut ViewContext<Self>) {
|
||||
self.show_wrap_guides = Some(show_wrap_guides);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
|
@ -10888,6 +10938,15 @@ pub trait CompletionProvider {
|
|||
push_to_history: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Task<Result<Option<language::Transaction>>>;
|
||||
|
||||
fn is_completion_trigger(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
position: language::Anchor,
|
||||
text: &str,
|
||||
trigger_in_words: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
impl CompletionProvider for Model<Project> {
|
||||
|
@ -10925,6 +10984,40 @@ impl CompletionProvider for Model<Project> {
|
|||
project.apply_additional_edits_for_completion(buffer, completion, push_to_history, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn is_completion_trigger(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
position: language::Anchor,
|
||||
text: &str,
|
||||
trigger_in_words: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
if !EditorSettings::get_global(cx).show_completions_on_input {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut chars = text.chars();
|
||||
let char = if let Some(char) = chars.next() {
|
||||
char
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
if chars.next().is_some() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let buffer = buffer.read(cx);
|
||||
let scope = buffer.snapshot().language_scope_at(position);
|
||||
if trigger_in_words && char_kind(&scope, char) == CharKind::Word {
|
||||
return true;
|
||||
}
|
||||
|
||||
buffer
|
||||
.completion_triggers()
|
||||
.iter()
|
||||
.any(|string| string == text)
|
||||
}
|
||||
}
|
||||
|
||||
fn inlay_hint_settings(
|
||||
|
@ -11030,13 +11123,17 @@ impl EditorSnapshot {
|
|||
}
|
||||
let descent = cx.text_system().descent(font_id, font_size);
|
||||
|
||||
let show_git_gutter = matches!(
|
||||
ProjectSettings::get_global(cx).git.git_gutter,
|
||||
Some(GitGutterSetting::TrackedFiles)
|
||||
);
|
||||
let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
|
||||
matches!(
|
||||
ProjectSettings::get_global(cx).git.git_gutter,
|
||||
Some(GitGutterSetting::TrackedFiles)
|
||||
)
|
||||
});
|
||||
let gutter_settings = EditorSettings::get_global(cx).gutter;
|
||||
let gutter_lines_enabled = gutter_settings.line_numbers;
|
||||
let line_gutter_width = if gutter_lines_enabled {
|
||||
let show_line_numbers = self
|
||||
.show_line_numbers
|
||||
.unwrap_or_else(|| gutter_settings.line_numbers);
|
||||
let line_gutter_width = if show_line_numbers {
|
||||
// Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
|
||||
let min_width_for_number_on_gutter = em_width * 4.0;
|
||||
max_line_number_width.max(min_width_for_number_on_gutter)
|
||||
|
@ -11044,26 +11141,30 @@ impl EditorSnapshot {
|
|||
0.0.into()
|
||||
};
|
||||
|
||||
let show_code_actions = self
|
||||
.show_code_actions
|
||||
.unwrap_or_else(|| gutter_settings.code_actions);
|
||||
|
||||
let git_blame_entries_width = self
|
||||
.render_git_blame_gutter
|
||||
.then_some(em_width * GIT_BLAME_GUTTER_WIDTH_CHARS);
|
||||
|
||||
let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
|
||||
left_padding += if gutter_settings.code_actions {
|
||||
left_padding += if show_code_actions {
|
||||
em_width * 3.0
|
||||
} else if show_git_gutter && gutter_lines_enabled {
|
||||
} else if show_git_gutter && show_line_numbers {
|
||||
em_width * 2.0
|
||||
} else if show_git_gutter || gutter_lines_enabled {
|
||||
} else if show_git_gutter || show_line_numbers {
|
||||
em_width
|
||||
} else {
|
||||
px(0.)
|
||||
};
|
||||
|
||||
let right_padding = if gutter_settings.folds && gutter_lines_enabled {
|
||||
let right_padding = if gutter_settings.folds && show_line_numbers {
|
||||
em_width * 4.0
|
||||
} else if gutter_settings.folds {
|
||||
em_width * 3.0
|
||||
} else if gutter_lines_enabled {
|
||||
} else if show_line_numbers {
|
||||
em_width
|
||||
} else {
|
||||
px(0.)
|
||||
|
|
|
@ -1623,6 +1623,13 @@ impl EditorElement {
|
|||
snapshot: &EditorSnapshot,
|
||||
cx: &mut WindowContext,
|
||||
) -> Vec<Option<ShapedLine>> {
|
||||
let include_line_numbers = snapshot.show_line_numbers.unwrap_or_else(|| {
|
||||
EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode == EditorMode::Full
|
||||
});
|
||||
if !include_line_numbers {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let editor = self.editor.read(cx);
|
||||
let newest_selection_head = newest_selection_head.unwrap_or_else(|| {
|
||||
let newest = editor.selections.newest::<Point>(cx);
|
||||
|
@ -1638,54 +1645,47 @@ impl EditorElement {
|
|||
.head
|
||||
});
|
||||
let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
|
||||
let include_line_numbers =
|
||||
EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode == EditorMode::Full;
|
||||
let mut shaped_line_numbers = Vec::with_capacity(rows.len());
|
||||
let mut line_number = String::new();
|
||||
|
||||
let is_relative = EditorSettings::get_global(cx).relative_line_numbers;
|
||||
let relative_to = if is_relative {
|
||||
Some(newest_selection_head.row())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let relative_rows = self.calculate_relative_line_numbers(snapshot, &rows, relative_to);
|
||||
|
||||
for (ix, row) in buffer_rows.into_iter().enumerate() {
|
||||
let display_row = DisplayRow(rows.start.0 + ix as u32);
|
||||
let color = if active_rows.contains_key(&display_row) {
|
||||
cx.theme().colors().editor_active_line_number
|
||||
} else {
|
||||
cx.theme().colors().editor_line_number
|
||||
};
|
||||
if let Some(multibuffer_row) = row {
|
||||
if include_line_numbers {
|
||||
line_number.clear();
|
||||
let default_number = multibuffer_row.0 + 1;
|
||||
let number = relative_rows
|
||||
.get(&DisplayRow(ix as u32 + rows.start.0))
|
||||
.unwrap_or(&default_number);
|
||||
write!(&mut line_number, "{number}").unwrap();
|
||||
let run = TextRun {
|
||||
len: line_number.len(),
|
||||
font: self.style.text.font(),
|
||||
color,
|
||||
background_color: None,
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
};
|
||||
let shaped_line = cx
|
||||
.text_system()
|
||||
.shape_line(line_number.clone().into(), font_size, &[run])
|
||||
.unwrap();
|
||||
shaped_line_numbers.push(Some(shaped_line));
|
||||
}
|
||||
} else {
|
||||
shaped_line_numbers.push(None);
|
||||
}
|
||||
}
|
||||
|
||||
shaped_line_numbers
|
||||
let mut line_number = String::new();
|
||||
buffer_rows
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, multibuffer_row)| {
|
||||
let multibuffer_row = multibuffer_row?;
|
||||
let display_row = DisplayRow(rows.start.0 + ix as u32);
|
||||
let color = if active_rows.contains_key(&display_row) {
|
||||
cx.theme().colors().editor_active_line_number
|
||||
} else {
|
||||
cx.theme().colors().editor_line_number
|
||||
};
|
||||
line_number.clear();
|
||||
let default_number = multibuffer_row.0 + 1;
|
||||
let number = relative_rows
|
||||
.get(&DisplayRow(ix as u32 + rows.start.0))
|
||||
.unwrap_or(&default_number);
|
||||
write!(&mut line_number, "{number}").unwrap();
|
||||
let run = TextRun {
|
||||
len: line_number.len(),
|
||||
font: self.style.text.font(),
|
||||
color,
|
||||
background_color: None,
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
};
|
||||
let shaped_line = cx
|
||||
.text_system()
|
||||
.shape_line(line_number.clone().into(), font_size, &[run])
|
||||
.unwrap();
|
||||
Some(shaped_line)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn layout_gutter_fold_toggles(
|
||||
|
@ -2513,10 +2513,16 @@ impl EditorElement {
|
|||
}
|
||||
}
|
||||
|
||||
let show_git_gutter = matches!(
|
||||
ProjectSettings::get_global(cx).git.git_gutter,
|
||||
Some(GitGutterSetting::TrackedFiles)
|
||||
);
|
||||
let show_git_gutter = layout
|
||||
.position_map
|
||||
.snapshot
|
||||
.show_git_diff_gutter
|
||||
.unwrap_or_else(|| {
|
||||
matches!(
|
||||
ProjectSettings::get_global(cx).git.git_gutter,
|
||||
Some(GitGutterSetting::TrackedFiles)
|
||||
)
|
||||
});
|
||||
if show_git_gutter {
|
||||
Self::paint_diff_hunks(layout.gutter_hitbox.bounds, layout, cx)
|
||||
}
|
||||
|
@ -4281,7 +4287,11 @@ impl Element for EditorElement {
|
|||
gutter_dimensions.width - gutter_dimensions.left_padding,
|
||||
cx,
|
||||
);
|
||||
if gutter_settings.code_actions {
|
||||
|
||||
let show_code_actions = snapshot
|
||||
.show_code_actions
|
||||
.unwrap_or_else(|| gutter_settings.code_actions);
|
||||
if show_code_actions {
|
||||
let newest_selection_point =
|
||||
newest_selection_head.to_point(&snapshot.display_snapshot);
|
||||
let buffer = snapshot.buffer_snapshot.buffer_line_for_row(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue