Merge branch 'main' into selection-history
This commit is contained in:
commit
5ef6337b09
9 changed files with 459 additions and 275 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1617,6 +1617,7 @@ dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"ctor",
|
"ctor",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"futures",
|
||||||
"fuzzy",
|
"fuzzy",
|
||||||
"gpui",
|
"gpui",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
|
|
@ -35,6 +35,7 @@ util = { path = "../util" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
aho-corasick = "0.7"
|
aho-corasick = "0.7"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
futures = "0.3"
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
@ -69,7 +69,8 @@ action!(Backspace);
|
||||||
action!(Delete);
|
action!(Delete);
|
||||||
action!(Input, String);
|
action!(Input, String);
|
||||||
action!(Newline);
|
action!(Newline);
|
||||||
action!(Tab);
|
action!(Tab, Direction);
|
||||||
|
action!(Indent);
|
||||||
action!(Outdent);
|
action!(Outdent);
|
||||||
action!(DeleteLine);
|
action!(DeleteLine);
|
||||||
action!(DeleteToPreviousWordStart);
|
action!(DeleteToPreviousWordStart);
|
||||||
|
@ -174,13 +175,15 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
Some("Editor && showing_code_actions"),
|
Some("Editor && showing_code_actions"),
|
||||||
),
|
),
|
||||||
Binding::new("enter", ConfirmRename, Some("Editor && renaming")),
|
Binding::new("enter", ConfirmRename, Some("Editor && renaming")),
|
||||||
Binding::new("tab", Tab, Some("Editor")),
|
Binding::new("tab", Tab(Direction::Next), Some("Editor")),
|
||||||
|
Binding::new("shift-tab", Tab(Direction::Prev), Some("Editor")),
|
||||||
Binding::new(
|
Binding::new(
|
||||||
"tab",
|
"tab",
|
||||||
ConfirmCompletion(None),
|
ConfirmCompletion(None),
|
||||||
Some("Editor && showing_completions"),
|
Some("Editor && showing_completions"),
|
||||||
),
|
),
|
||||||
Binding::new("shift-tab", Outdent, Some("Editor")),
|
Binding::new("cmd-[", Outdent, Some("Editor")),
|
||||||
|
Binding::new("cmd-]", Indent, Some("Editor")),
|
||||||
Binding::new("ctrl-shift-K", DeleteLine, Some("Editor")),
|
Binding::new("ctrl-shift-K", DeleteLine, Some("Editor")),
|
||||||
Binding::new("alt-backspace", DeleteToPreviousWordStart, Some("Editor")),
|
Binding::new("alt-backspace", DeleteToPreviousWordStart, Some("Editor")),
|
||||||
Binding::new("alt-h", DeleteToPreviousWordStart, Some("Editor")),
|
Binding::new("alt-h", DeleteToPreviousWordStart, Some("Editor")),
|
||||||
|
@ -310,6 +313,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(Editor::backspace);
|
cx.add_action(Editor::backspace);
|
||||||
cx.add_action(Editor::delete);
|
cx.add_action(Editor::delete);
|
||||||
cx.add_action(Editor::tab);
|
cx.add_action(Editor::tab);
|
||||||
|
cx.add_action(Editor::indent);
|
||||||
cx.add_action(Editor::outdent);
|
cx.add_action(Editor::outdent);
|
||||||
cx.add_action(Editor::delete_line);
|
cx.add_action(Editor::delete_line);
|
||||||
cx.add_action(Editor::delete_to_previous_word_start);
|
cx.add_action(Editor::delete_to_previous_word_start);
|
||||||
|
@ -2961,75 +2965,101 @@ impl Editor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
|
pub fn tab(&mut self, &Tab(direction): &Tab, cx: &mut ViewContext<Self>) {
|
||||||
if self.move_to_next_snippet_tabstop(cx) {
|
match direction {
|
||||||
return;
|
Direction::Prev => {
|
||||||
}
|
if !self.snippet_stack.is_empty() {
|
||||||
|
self.move_to_prev_snippet_tabstop(cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.outdent(&Outdent, cx);
|
||||||
|
}
|
||||||
|
Direction::Next => {
|
||||||
|
if self.move_to_next_snippet_tabstop(cx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tab_size = cx.global::<Settings>().tab_size;
|
||||||
|
let mut selections = self.local_selections::<Point>(cx);
|
||||||
|
if selections.iter().all(|s| s.is_empty()) {
|
||||||
|
self.transact(cx, |this, cx| {
|
||||||
|
this.buffer.update(cx, |buffer, cx| {
|
||||||
|
for selection in &mut selections {
|
||||||
|
let char_column = buffer
|
||||||
|
.read(cx)
|
||||||
|
.text_for_range(
|
||||||
|
Point::new(selection.start.row, 0)..selection.start,
|
||||||
|
)
|
||||||
|
.flat_map(str::chars)
|
||||||
|
.count();
|
||||||
|
let chars_to_next_tab_stop = tab_size - (char_column % tab_size);
|
||||||
|
buffer.edit(
|
||||||
|
[selection.start..selection.start],
|
||||||
|
" ".repeat(chars_to_next_tab_stop),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
selection.start.column += chars_to_next_tab_stop as u32;
|
||||||
|
selection.end = selection.start;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.indent(&Indent, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext<Self>) {
|
||||||
let tab_size = cx.global::<Settings>().tab_size;
|
let tab_size = cx.global::<Settings>().tab_size;
|
||||||
let mut selections = self.local_selections::<Point>(cx);
|
let mut selections = self.local_selections::<Point>(cx);
|
||||||
self.transact(cx, |this, cx| {
|
self.transact(cx, |this, cx| {
|
||||||
let mut last_indent = None;
|
let mut last_indent = None;
|
||||||
this.buffer.update(cx, |buffer, cx| {
|
this.buffer.update(cx, |buffer, cx| {
|
||||||
for selection in &mut selections {
|
for selection in &mut selections {
|
||||||
if selection.is_empty() {
|
let mut start_row = selection.start.row;
|
||||||
let char_column = buffer
|
let mut end_row = selection.end.row + 1;
|
||||||
.read(cx)
|
|
||||||
.text_for_range(Point::new(selection.start.row, 0)..selection.start)
|
// If a selection ends at the beginning of a line, don't indent
|
||||||
.flat_map(str::chars)
|
// that last line.
|
||||||
.count();
|
if selection.end.column == 0 {
|
||||||
let chars_to_next_tab_stop = tab_size - (char_column % tab_size);
|
end_row -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid re-indenting a row that has already been indented by a
|
||||||
|
// previous selection, but still update this selection's column
|
||||||
|
// to reflect that indentation.
|
||||||
|
if let Some((last_indent_row, last_indent_len)) = last_indent {
|
||||||
|
if last_indent_row == selection.start.row {
|
||||||
|
selection.start.column += last_indent_len;
|
||||||
|
start_row += 1;
|
||||||
|
}
|
||||||
|
if last_indent_row == selection.end.row {
|
||||||
|
selection.end.column += last_indent_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for row in start_row..end_row {
|
||||||
|
let indent_column = buffer.read(cx).indent_column_for_line(row) as usize;
|
||||||
|
let columns_to_next_tab_stop = tab_size - (indent_column % tab_size);
|
||||||
|
let row_start = Point::new(row, 0);
|
||||||
buffer.edit(
|
buffer.edit(
|
||||||
[selection.start..selection.start],
|
[row_start..row_start],
|
||||||
" ".repeat(chars_to_next_tab_stop),
|
" ".repeat(columns_to_next_tab_stop),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
selection.start.column += chars_to_next_tab_stop as u32;
|
|
||||||
selection.end = selection.start;
|
|
||||||
} else {
|
|
||||||
let mut start_row = selection.start.row;
|
|
||||||
let mut end_row = selection.end.row + 1;
|
|
||||||
|
|
||||||
// If a selection ends at the beginning of a line, don't indent
|
// Update this selection's endpoints to reflect the indentation.
|
||||||
// that last line.
|
if row == selection.start.row {
|
||||||
if selection.end.column == 0 {
|
selection.start.column += columns_to_next_tab_stop as u32;
|
||||||
end_row -= 1;
|
}
|
||||||
|
if row == selection.end.row {
|
||||||
|
selection.end.column += columns_to_next_tab_stop as u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid re-indenting a row that has already been indented by a
|
last_indent = Some((row, columns_to_next_tab_stop as u32));
|
||||||
// previous selection, but still update this selection's column
|
|
||||||
// to reflect that indentation.
|
|
||||||
if let Some((last_indent_row, last_indent_len)) = last_indent {
|
|
||||||
if last_indent_row == selection.start.row {
|
|
||||||
selection.start.column += last_indent_len;
|
|
||||||
start_row += 1;
|
|
||||||
}
|
|
||||||
if last_indent_row == selection.end.row {
|
|
||||||
selection.end.column += last_indent_len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for row in start_row..end_row {
|
|
||||||
let indent_column =
|
|
||||||
buffer.read(cx).indent_column_for_line(row) as usize;
|
|
||||||
let columns_to_next_tab_stop = tab_size - (indent_column % tab_size);
|
|
||||||
let row_start = Point::new(row, 0);
|
|
||||||
buffer.edit(
|
|
||||||
[row_start..row_start],
|
|
||||||
" ".repeat(columns_to_next_tab_stop),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update this selection's endpoints to reflect the indentation.
|
|
||||||
if row == selection.start.row {
|
|
||||||
selection.start.column += columns_to_next_tab_stop as u32;
|
|
||||||
}
|
|
||||||
if row == selection.end.row {
|
|
||||||
selection.end.column += columns_to_next_tab_stop as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
last_indent = Some((row, columns_to_next_tab_stop as u32));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -3039,11 +3069,6 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext<Self>) {
|
pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext<Self>) {
|
||||||
if !self.snippet_stack.is_empty() {
|
|
||||||
self.move_to_prev_snippet_tabstop(cx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let tab_size = cx.global::<Settings>().tab_size;
|
let tab_size = cx.global::<Settings>().tab_size;
|
||||||
let selections = self.local_selections::<Point>(cx);
|
let selections = self.local_selections::<Point>(cx);
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
|
@ -6427,7 +6452,7 @@ mod tests {
|
||||||
use text::Point;
|
use text::Point;
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
use util::test::{marked_text_by, marked_text_ranges, sample_text};
|
use util::test::{marked_text_by, marked_text_ranges, sample_text};
|
||||||
use workspace::FollowableItem;
|
use workspace::{FollowableItem, ItemHandle};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_edit_events(cx: &mut MutableAppContext) {
|
fn test_edit_events(cx: &mut MutableAppContext) {
|
||||||
|
@ -7594,7 +7619,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// indent from mid-tabstop to full tabstop
|
// indent from mid-tabstop to full tabstop
|
||||||
view.tab(&Tab, cx);
|
view.tab(&Tab(Direction::Next), cx);
|
||||||
assert_eq!(view.text(cx), " one two\nthree\n four");
|
assert_eq!(view.text(cx), " one two\nthree\n four");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.selected_display_ranges(cx),
|
view.selected_display_ranges(cx),
|
||||||
|
@ -7605,7 +7630,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// outdent from 1 tabstop to 0 tabstops
|
// outdent from 1 tabstop to 0 tabstops
|
||||||
view.outdent(&Outdent, cx);
|
view.tab(&Tab(Direction::Prev), cx);
|
||||||
assert_eq!(view.text(cx), "one two\nthree\n four");
|
assert_eq!(view.text(cx), "one two\nthree\n four");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.selected_display_ranges(cx),
|
view.selected_display_ranges(cx),
|
||||||
|
@ -7619,13 +7644,13 @@ mod tests {
|
||||||
view.select_display_ranges(&[DisplayPoint::new(1, 1)..DisplayPoint::new(2, 0)], cx);
|
view.select_display_ranges(&[DisplayPoint::new(1, 1)..DisplayPoint::new(2, 0)], cx);
|
||||||
|
|
||||||
// indent and outdent affect only the preceding line
|
// indent and outdent affect only the preceding line
|
||||||
view.tab(&Tab, cx);
|
view.tab(&Tab(Direction::Next), cx);
|
||||||
assert_eq!(view.text(cx), "one two\n three\n four");
|
assert_eq!(view.text(cx), "one two\n three\n four");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.selected_display_ranges(cx),
|
view.selected_display_ranges(cx),
|
||||||
&[DisplayPoint::new(1, 5)..DisplayPoint::new(2, 0)]
|
&[DisplayPoint::new(1, 5)..DisplayPoint::new(2, 0)]
|
||||||
);
|
);
|
||||||
view.outdent(&Outdent, cx);
|
view.tab(&Tab(Direction::Prev), cx);
|
||||||
assert_eq!(view.text(cx), "one two\nthree\n four");
|
assert_eq!(view.text(cx), "one two\nthree\n four");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.selected_display_ranges(cx),
|
view.selected_display_ranges(cx),
|
||||||
|
@ -7634,7 +7659,7 @@ mod tests {
|
||||||
|
|
||||||
// Ensure that indenting/outdenting works when the cursor is at column 0.
|
// Ensure that indenting/outdenting works when the cursor is at column 0.
|
||||||
view.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx);
|
view.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx);
|
||||||
view.tab(&Tab, cx);
|
view.tab(&Tab(Direction::Next), cx);
|
||||||
assert_eq!(view.text(cx), "one two\n three\n four");
|
assert_eq!(view.text(cx), "one two\n three\n four");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.selected_display_ranges(cx),
|
view.selected_display_ranges(cx),
|
||||||
|
@ -7642,7 +7667,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
view.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx);
|
view.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx);
|
||||||
view.outdent(&Outdent, cx);
|
view.tab(&Tab(Direction::Prev), cx);
|
||||||
assert_eq!(view.text(cx), "one two\nthree\n four");
|
assert_eq!(view.text(cx), "one two\nthree\n four");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.selected_display_ranges(cx),
|
view.selected_display_ranges(cx),
|
||||||
|
@ -8850,6 +8875,94 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||||
|
cx.foreground().forbid_parking();
|
||||||
|
cx.update(populate_settings);
|
||||||
|
|
||||||
|
let (mut language_server_config, mut fake_servers) = LanguageServerConfig::fake();
|
||||||
|
language_server_config.set_fake_capabilities(lsp::ServerCapabilities {
|
||||||
|
document_formatting_provider: Some(lsp::OneOf::Left(true)),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let language = Arc::new(Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
name: "Rust".into(),
|
||||||
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
language_server: Some(language_server_config),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Some(tree_sitter_rust::language()),
|
||||||
|
));
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.background().clone());
|
||||||
|
fs.insert_file("/file.rs", Default::default()).await;
|
||||||
|
|
||||||
|
let project = Project::test(fs, cx);
|
||||||
|
project.update(cx, |project, _| project.languages().add(language));
|
||||||
|
|
||||||
|
let worktree_id = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.find_or_create_local_worktree("/file.rs", true, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.read_with(cx, |tree, _| tree.id());
|
||||||
|
let buffer = project
|
||||||
|
.update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let mut fake_server = fake_servers.next().await.unwrap();
|
||||||
|
|
||||||
|
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
|
let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
|
||||||
|
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
|
||||||
|
assert!(cx.read(|cx| editor.is_dirty(cx)));
|
||||||
|
|
||||||
|
let save = cx.update(|cx| editor.save(project.clone(), cx));
|
||||||
|
fake_server
|
||||||
|
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||||
|
assert_eq!(
|
||||||
|
params.text_document.uri,
|
||||||
|
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||||
|
);
|
||||||
|
Some(vec![lsp::TextEdit::new(
|
||||||
|
lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
|
||||||
|
", ".to_string(),
|
||||||
|
)])
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
.await;
|
||||||
|
save.await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
editor.read_with(cx, |editor, cx| editor.text(cx)),
|
||||||
|
"one, two\nthree\n"
|
||||||
|
);
|
||||||
|
assert!(!cx.read(|cx| editor.is_dirty(cx)));
|
||||||
|
|
||||||
|
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
|
||||||
|
assert!(cx.read(|cx| editor.is_dirty(cx)));
|
||||||
|
|
||||||
|
// Ensure we can still save even if formatting hangs.
|
||||||
|
fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||||
|
assert_eq!(
|
||||||
|
params.text_document.uri,
|
||||||
|
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||||
|
);
|
||||||
|
futures::future::pending::<()>().await;
|
||||||
|
unreachable!()
|
||||||
|
});
|
||||||
|
let save = cx.update(|cx| editor.save(project.clone(), cx));
|
||||||
|
cx.foreground().advance_clock(items::FORMAT_TIMEOUT);
|
||||||
|
save.await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
editor.read_with(cx, |editor, cx| editor.text(cx)),
|
||||||
|
"one\ntwo\nthree\n"
|
||||||
|
);
|
||||||
|
assert!(!cx.read(|cx| editor.is_dirty(cx)));
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_completion(cx: &mut gpui::TestAppContext) {
|
async fn test_completion(cx: &mut gpui::TestAppContext) {
|
||||||
cx.update(populate_settings);
|
cx.update(populate_settings);
|
||||||
|
@ -9031,31 +9144,34 @@ mod tests {
|
||||||
position: Point,
|
position: Point,
|
||||||
completions: Vec<(Range<Point>, &'static str)>,
|
completions: Vec<(Range<Point>, &'static str)>,
|
||||||
) {
|
) {
|
||||||
fake.handle_request::<lsp::request::Completion, _>(move |params, _| {
|
fake.handle_request::<lsp::request::Completion, _, _>(move |params, _| {
|
||||||
assert_eq!(
|
let completions = completions.clone();
|
||||||
params.text_document_position.text_document.uri,
|
async move {
|
||||||
lsp::Url::from_file_path(path).unwrap()
|
assert_eq!(
|
||||||
);
|
params.text_document_position.text_document.uri,
|
||||||
assert_eq!(
|
lsp::Url::from_file_path(path).unwrap()
|
||||||
params.text_document_position.position,
|
);
|
||||||
lsp::Position::new(position.row, position.column)
|
assert_eq!(
|
||||||
);
|
params.text_document_position.position,
|
||||||
Some(lsp::CompletionResponse::Array(
|
lsp::Position::new(position.row, position.column)
|
||||||
completions
|
);
|
||||||
.iter()
|
Some(lsp::CompletionResponse::Array(
|
||||||
.map(|(range, new_text)| lsp::CompletionItem {
|
completions
|
||||||
label: new_text.to_string(),
|
.iter()
|
||||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
.map(|(range, new_text)| lsp::CompletionItem {
|
||||||
range: lsp::Range::new(
|
label: new_text.to_string(),
|
||||||
lsp::Position::new(range.start.row, range.start.column),
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
lsp::Position::new(range.start.row, range.start.column),
|
range: lsp::Range::new(
|
||||||
),
|
lsp::Position::new(range.start.row, range.start.column),
|
||||||
new_text: new_text.to_string(),
|
lsp::Position::new(range.start.row, range.start.column),
|
||||||
})),
|
),
|
||||||
..Default::default()
|
new_text: new_text.to_string(),
|
||||||
})
|
})),
|
||||||
.collect(),
|
..Default::default()
|
||||||
))
|
})
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
.await;
|
.await;
|
||||||
|
@ -9065,18 +9181,21 @@ mod tests {
|
||||||
fake: &mut FakeLanguageServer,
|
fake: &mut FakeLanguageServer,
|
||||||
edit: Option<(Range<Point>, &'static str)>,
|
edit: Option<(Range<Point>, &'static str)>,
|
||||||
) {
|
) {
|
||||||
fake.handle_request::<lsp::request::ResolveCompletionItem, _>(move |_, _| {
|
fake.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _| {
|
||||||
lsp::CompletionItem {
|
let edit = edit.clone();
|
||||||
additional_text_edits: edit.clone().map(|(range, new_text)| {
|
async move {
|
||||||
vec![lsp::TextEdit::new(
|
lsp::CompletionItem {
|
||||||
lsp::Range::new(
|
additional_text_edits: edit.map(|(range, new_text)| {
|
||||||
lsp::Position::new(range.start.row, range.start.column),
|
vec![lsp::TextEdit::new(
|
||||||
lsp::Position::new(range.end.row, range.end.column),
|
lsp::Range::new(
|
||||||
),
|
lsp::Position::new(range.start.row, range.start.column),
|
||||||
new_text.to_string(),
|
lsp::Position::new(range.end.row, range.end.column),
|
||||||
)]
|
),
|
||||||
}),
|
new_text.to_string(),
|
||||||
..Default::default()
|
)]
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{Anchor, Autoscroll, Editor, Event, ExcerptId, NavigationData, ToOffset, ToPoint as _};
|
use crate::{Anchor, Autoscroll, Editor, Event, ExcerptId, NavigationData, ToOffset, ToPoint as _};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use futures::FutureExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext,
|
elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext,
|
||||||
RenderContext, Subscription, Task, View, ViewContext, ViewHandle,
|
RenderContext, Subscription, Task, View, ViewContext, ViewHandle,
|
||||||
|
@ -7,13 +8,15 @@ use gpui::{
|
||||||
use language::{Bias, Buffer, Diagnostic, File as _, SelectionGoal};
|
use language::{Bias, Buffer, Diagnostic, File as _, SelectionGoal};
|
||||||
use project::{File, Project, ProjectEntryId, ProjectPath};
|
use project::{File, Project, ProjectEntryId, ProjectPath};
|
||||||
use rpc::proto::{self, update_view};
|
use rpc::proto::{self, update_view};
|
||||||
use std::{fmt::Write, path::PathBuf};
|
use std::{fmt::Write, path::PathBuf, time::Duration};
|
||||||
use text::{Point, Selection};
|
use text::{Point, Selection};
|
||||||
use util::ResultExt;
|
use util::TryFutureExt;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
FollowableItem, Item, ItemHandle, ItemNavHistory, ProjectItem, Settings, StatusItemView,
|
FollowableItem, Item, ItemHandle, ItemNavHistory, ProjectItem, Settings, StatusItemView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
|
||||||
|
|
||||||
impl FollowableItem for Editor {
|
impl FollowableItem for Editor {
|
||||||
fn from_state_proto(
|
fn from_state_proto(
|
||||||
pane: ViewHandle<workspace::Pane>,
|
pane: ViewHandle<workspace::Pane>,
|
||||||
|
@ -317,9 +320,17 @@ impl Item for Editor {
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let buffer = self.buffer().clone();
|
let buffer = self.buffer().clone();
|
||||||
let buffers = buffer.read(cx).all_buffers();
|
let buffers = buffer.read(cx).all_buffers();
|
||||||
let transaction = project.update(cx, |project, cx| project.format(buffers, true, cx));
|
let mut timeout = cx.background().timer(FORMAT_TIMEOUT).fuse();
|
||||||
|
let format = project.update(cx, |project, cx| project.format(buffers, true, cx));
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let transaction = transaction.await.log_err();
|
let transaction = futures::select_biased! {
|
||||||
|
_ = timeout => {
|
||||||
|
log::warn!("timed out waiting for formatting");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
transaction = format.log_err().fuse() => transaction,
|
||||||
|
};
|
||||||
|
|
||||||
this.update(&mut cx, |editor, cx| {
|
this.update(&mut cx, |editor, cx| {
|
||||||
editor.request_autoscroll(Autoscroll::Fit, cx)
|
editor.request_autoscroll(Autoscroll::Fit, cx)
|
||||||
});
|
});
|
||||||
|
|
|
@ -337,7 +337,7 @@ impl Deterministic {
|
||||||
|
|
||||||
if let Some((_, wakeup_time, _)) = state.pending_timers.first() {
|
if let Some((_, wakeup_time, _)) = state.pending_timers.first() {
|
||||||
let wakeup_time = *wakeup_time;
|
let wakeup_time = *wakeup_time;
|
||||||
if wakeup_time < new_now {
|
if wakeup_time <= new_now {
|
||||||
let timer_count = state
|
let timer_count = state
|
||||||
.pending_timers
|
.pending_timers
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -561,9 +561,10 @@ impl Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
for icon in icons {
|
for icon in icons {
|
||||||
let origin = icon.bounds.origin() * scale_factor;
|
// Snap sprite to pixel grid.
|
||||||
let target_size = icon.bounds.size() * scale_factor;
|
let origin = (icon.bounds.origin() * scale_factor).floor();
|
||||||
let source_size = (target_size * 2.).ceil().to_i32();
|
let target_size = (icon.bounds.size() * scale_factor).ceil();
|
||||||
|
let source_size = (target_size * 2.).to_i32();
|
||||||
|
|
||||||
let sprite =
|
let sprite =
|
||||||
self.sprite_cache
|
self.sprite_cache
|
||||||
|
|
|
@ -556,7 +556,14 @@ type FakeLanguageServerHandlers = Arc<
|
||||||
Mutex<
|
Mutex<
|
||||||
HashMap<
|
HashMap<
|
||||||
&'static str,
|
&'static str,
|
||||||
Box<dyn Send + FnMut(usize, &[u8], gpui::AsyncAppContext) -> Vec<u8>>,
|
Box<
|
||||||
|
dyn Send
|
||||||
|
+ FnMut(
|
||||||
|
usize,
|
||||||
|
&[u8],
|
||||||
|
gpui::AsyncAppContext,
|
||||||
|
) -> futures::future::BoxFuture<'static, Vec<u8>>,
|
||||||
|
>,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
>;
|
>;
|
||||||
|
@ -585,11 +592,16 @@ impl LanguageServer {
|
||||||
let (stdout_writer, stdout_reader) = async_pipe::pipe();
|
let (stdout_writer, stdout_reader) = async_pipe::pipe();
|
||||||
|
|
||||||
let mut fake = FakeLanguageServer::new(stdin_reader, stdout_writer, cx);
|
let mut fake = FakeLanguageServer::new(stdin_reader, stdout_writer, cx);
|
||||||
fake.handle_request::<request::Initialize, _>({
|
fake.handle_request::<request::Initialize, _, _>({
|
||||||
let capabilities = capabilities.clone();
|
let capabilities = capabilities.clone();
|
||||||
move |_, _| InitializeResult {
|
move |_, _| {
|
||||||
capabilities: capabilities.clone(),
|
let capabilities = capabilities.clone();
|
||||||
..Default::default()
|
async move {
|
||||||
|
InitializeResult {
|
||||||
|
capabilities,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -628,7 +640,8 @@ impl FakeLanguageServer {
|
||||||
let response;
|
let response;
|
||||||
if let Some(handler) = handlers.lock().get_mut(request.method) {
|
if let Some(handler) = handlers.lock().get_mut(request.method) {
|
||||||
response =
|
response =
|
||||||
handler(request.id, request.params.get().as_bytes(), cx.clone());
|
handler(request.id, request.params.get().as_bytes(), cx.clone())
|
||||||
|
.await;
|
||||||
log::debug!("handled lsp request. method:{}", request.method);
|
log::debug!("handled lsp request. method:{}", request.method);
|
||||||
} else {
|
} else {
|
||||||
response = serde_json::to_vec(&AnyResponse {
|
response = serde_json::to_vec(&AnyResponse {
|
||||||
|
@ -704,28 +717,36 @@ impl FakeLanguageServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_request<T, F>(
|
pub fn handle_request<T, F, Fut>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut handler: F,
|
mut handler: F,
|
||||||
) -> futures::channel::mpsc::UnboundedReceiver<()>
|
) -> futures::channel::mpsc::UnboundedReceiver<()>
|
||||||
where
|
where
|
||||||
T: 'static + request::Request,
|
T: 'static + request::Request,
|
||||||
F: 'static + Send + FnMut(T::Params, gpui::AsyncAppContext) -> T::Result,
|
F: 'static + Send + FnMut(T::Params, gpui::AsyncAppContext) -> Fut,
|
||||||
|
Fut: 'static + Send + Future<Output = T::Result>,
|
||||||
{
|
{
|
||||||
|
use futures::FutureExt as _;
|
||||||
|
|
||||||
let (responded_tx, responded_rx) = futures::channel::mpsc::unbounded();
|
let (responded_tx, responded_rx) = futures::channel::mpsc::unbounded();
|
||||||
self.handlers.lock().insert(
|
self.handlers.lock().insert(
|
||||||
T::METHOD,
|
T::METHOD,
|
||||||
Box::new(move |id, params, cx| {
|
Box::new(move |id, params, cx| {
|
||||||
let result = handler(serde_json::from_slice::<T::Params>(params).unwrap(), cx);
|
let result = handler(serde_json::from_slice::<T::Params>(params).unwrap(), cx);
|
||||||
let result = serde_json::to_string(&result).unwrap();
|
let responded_tx = responded_tx.clone();
|
||||||
let result = serde_json::from_str::<&RawValue>(&result).unwrap();
|
async move {
|
||||||
let response = AnyResponse {
|
let result = result.await;
|
||||||
id,
|
let result = serde_json::to_string(&result).unwrap();
|
||||||
error: None,
|
let result = serde_json::from_str::<&RawValue>(&result).unwrap();
|
||||||
result: Some(result),
|
let response = AnyResponse {
|
||||||
};
|
id,
|
||||||
responded_tx.unbounded_send(()).ok();
|
error: None,
|
||||||
serde_json::to_vec(&response).unwrap()
|
result: Some(result),
|
||||||
|
};
|
||||||
|
responded_tx.unbounded_send(()).ok();
|
||||||
|
serde_json::to_vec(&response).unwrap()
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
responded_rx
|
responded_rx
|
||||||
|
@ -844,7 +865,7 @@ mod tests {
|
||||||
"file://b/c"
|
"file://b/c"
|
||||||
);
|
);
|
||||||
|
|
||||||
fake.handle_request::<request::Shutdown, _>(|_, _| ());
|
fake.handle_request::<request::Shutdown, _, _>(|_, _| async move {});
|
||||||
|
|
||||||
drop(server);
|
drop(server);
|
||||||
fake.receive_notification::<notification::Exit>().await;
|
fake.receive_notification::<notification::Exit>().await;
|
||||||
|
|
|
@ -5789,7 +5789,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut fake_server = fake_servers.next().await.unwrap();
|
let mut fake_server = fake_servers.next().await.unwrap();
|
||||||
fake_server.handle_request::<lsp::request::GotoDefinition, _>(move |params, _| {
|
fake_server.handle_request::<lsp::request::GotoDefinition, _, _>(|params, _| async move {
|
||||||
let params = params.text_document_position_params;
|
let params = params.text_document_position_params;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri.to_file_path().unwrap(),
|
params.text_document.uri.to_file_path().unwrap(),
|
||||||
|
@ -6724,7 +6724,7 @@ mod tests {
|
||||||
project.prepare_rename(buffer.clone(), 7, cx)
|
project.prepare_rename(buffer.clone(), 7, cx)
|
||||||
});
|
});
|
||||||
fake_server
|
fake_server
|
||||||
.handle_request::<lsp::request::PrepareRenameRequest, _>(|params, _| {
|
.handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
|
||||||
assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
|
assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
|
||||||
assert_eq!(params.position, lsp::Position::new(0, 7));
|
assert_eq!(params.position, lsp::Position::new(0, 7));
|
||||||
Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
|
Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
|
||||||
|
@ -6743,7 +6743,7 @@ mod tests {
|
||||||
project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx)
|
project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx)
|
||||||
});
|
});
|
||||||
fake_server
|
fake_server
|
||||||
.handle_request::<lsp::request::Rename, _>(|params, _| {
|
.handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.text_document.uri.as_str(),
|
params.text_document_position.text_document.uri.as_str(),
|
||||||
"file:///dir/one.rs"
|
"file:///dir/one.rs"
|
||||||
|
|
|
@ -2360,7 +2360,7 @@ mod tests {
|
||||||
// Return some completions from the host's language server.
|
// Return some completions from the host's language server.
|
||||||
cx_a.foreground().start_waiting();
|
cx_a.foreground().start_waiting();
|
||||||
fake_language_server
|
fake_language_server
|
||||||
.handle_request::<lsp::request::Completion, _>(|params, _| {
|
.handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.text_document.uri,
|
params.text_document_position.text_document.uri,
|
||||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
|
@ -2424,8 +2424,8 @@ mod tests {
|
||||||
|
|
||||||
// Return a resolved completion from the host's language server.
|
// Return a resolved completion from the host's language server.
|
||||||
// The resolved completion has an additional text edit.
|
// The resolved completion has an additional text edit.
|
||||||
fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _>(
|
fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
|
||||||
|params, _| {
|
|params, _| async move {
|
||||||
assert_eq!(params.label, "first_method(…)");
|
assert_eq!(params.label, "first_method(…)");
|
||||||
lsp::CompletionItem {
|
lsp::CompletionItem {
|
||||||
label: "first_method(…)".into(),
|
label: "first_method(…)".into(),
|
||||||
|
@ -2535,7 +2535,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::Formatting, _>(|_, _| {
|
fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
|
||||||
Some(vec![
|
Some(vec![
|
||||||
lsp::TextEdit {
|
lsp::TextEdit {
|
||||||
range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
|
range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
|
||||||
|
@ -2644,12 +2644,14 @@ mod tests {
|
||||||
|
|
||||||
// Request the definition of a symbol as the guest.
|
// Request the definition of a symbol as the guest.
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::GotoDefinition, _>(|_, _| {
|
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
|
||||||
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
|
|_, _| async move {
|
||||||
lsp::Url::from_file_path("/root-2/b.rs").unwrap(),
|
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
|
||||||
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
lsp::Url::from_file_path("/root-2/b.rs").unwrap(),
|
||||||
)))
|
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||||
});
|
)))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let definitions_1 = project_b
|
let definitions_1 = project_b
|
||||||
.update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
|
.update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
|
||||||
|
@ -2671,12 +2673,14 @@ mod tests {
|
||||||
|
|
||||||
// Try getting more definitions for the same buffer, ensuring the buffer gets reused from
|
// Try getting more definitions for the same buffer, ensuring the buffer gets reused from
|
||||||
// the previous call to `definition`.
|
// the previous call to `definition`.
|
||||||
fake_language_server.handle_request::<lsp::request::GotoDefinition, _>(|_, _| {
|
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
|
||||||
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
|
|_, _| async move {
|
||||||
lsp::Url::from_file_path("/root-2/b.rs").unwrap(),
|
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
|
||||||
lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
|
lsp::Url::from_file_path("/root-2/b.rs").unwrap(),
|
||||||
)))
|
lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
|
||||||
});
|
)))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let definitions_2 = project_b
|
let definitions_2 = project_b
|
||||||
.update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
|
.update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
|
||||||
|
@ -2783,26 +2787,37 @@ mod tests {
|
||||||
|
|
||||||
// Request references to a symbol as the guest.
|
// Request references to a symbol as the guest.
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::References, _>(|params, _| {
|
fake_language_server.handle_request::<lsp::request::References, _, _>(
|
||||||
assert_eq!(
|
|params, _| async move {
|
||||||
params.text_document_position.text_document.uri.as_str(),
|
assert_eq!(
|
||||||
"file:///root-1/one.rs"
|
params.text_document_position.text_document.uri.as_str(),
|
||||||
);
|
"file:///root-1/one.rs"
|
||||||
Some(vec![
|
);
|
||||||
lsp::Location {
|
Some(vec![
|
||||||
uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
|
lsp::Location {
|
||||||
range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
|
uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
|
||||||
},
|
range: lsp::Range::new(
|
||||||
lsp::Location {
|
lsp::Position::new(0, 24),
|
||||||
uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
|
lsp::Position::new(0, 27),
|
||||||
range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
|
),
|
||||||
},
|
},
|
||||||
lsp::Location {
|
lsp::Location {
|
||||||
uri: lsp::Url::from_file_path("/root-2/three.rs").unwrap(),
|
uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
|
||||||
range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
|
range: lsp::Range::new(
|
||||||
},
|
lsp::Position::new(0, 35),
|
||||||
])
|
lsp::Position::new(0, 38),
|
||||||
});
|
),
|
||||||
|
},
|
||||||
|
lsp::Location {
|
||||||
|
uri: lsp::Url::from_file_path("/root-2/three.rs").unwrap(),
|
||||||
|
range: lsp::Range::new(
|
||||||
|
lsp::Position::new(0, 37),
|
||||||
|
lsp::Position::new(0, 40),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let references = project_b
|
let references = project_b
|
||||||
.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx))
|
.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx))
|
||||||
|
@ -3012,8 +3027,8 @@ mod tests {
|
||||||
|
|
||||||
// Request document highlights as the guest.
|
// Request document highlights as the guest.
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _>(
|
fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
|
||||||
|params, _| {
|
|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params
|
params
|
||||||
.text_document_position_params
|
.text_document_position_params
|
||||||
|
@ -3158,20 +3173,22 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _>(|_, _| {
|
fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(
|
||||||
#[allow(deprecated)]
|
|_, _| async move {
|
||||||
Some(vec![lsp::SymbolInformation {
|
#[allow(deprecated)]
|
||||||
name: "TWO".into(),
|
Some(vec![lsp::SymbolInformation {
|
||||||
location: lsp::Location {
|
name: "TWO".into(),
|
||||||
uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
|
location: lsp::Location {
|
||||||
range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
|
||||||
},
|
range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||||
kind: lsp::SymbolKind::CONSTANT,
|
},
|
||||||
tags: None,
|
kind: lsp::SymbolKind::CONSTANT,
|
||||||
container_name: None,
|
tags: None,
|
||||||
deprecated: None,
|
container_name: None,
|
||||||
}])
|
deprecated: None,
|
||||||
});
|
}])
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Request the definition of a symbol as the guest.
|
// Request the definition of a symbol as the guest.
|
||||||
let symbols = project_b
|
let symbols = project_b
|
||||||
|
@ -3289,12 +3306,14 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::GotoDefinition, _>(|_, _| {
|
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
|
||||||
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
|
|_, _| async move {
|
||||||
lsp::Url::from_file_path("/root/b.rs").unwrap(),
|
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
|
||||||
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
lsp::Url::from_file_path("/root/b.rs").unwrap(),
|
||||||
)))
|
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||||
});
|
)))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let definitions;
|
let definitions;
|
||||||
let buffer_b2;
|
let buffer_b2;
|
||||||
|
@ -3402,7 +3421,7 @@ mod tests {
|
||||||
|
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server
|
fake_language_server
|
||||||
.handle_request::<lsp::request::CodeActionRequest, _>(|params, _| {
|
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
|
@ -3421,7 +3440,7 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
fake_language_server
|
fake_language_server
|
||||||
.handle_request::<lsp::request::CodeActionRequest, _>(|params, _| {
|
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
|
@ -3492,41 +3511,43 @@ mod tests {
|
||||||
Editor::confirm_code_action(workspace, &ConfirmCodeAction(Some(0)), cx)
|
Editor::confirm_code_action(workspace, &ConfirmCodeAction(Some(0)), cx)
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _>(|_, _| {
|
fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
|
||||||
lsp::CodeAction {
|
|_, _| async move {
|
||||||
title: "Inline into all callers".to_string(),
|
lsp::CodeAction {
|
||||||
edit: Some(lsp::WorkspaceEdit {
|
title: "Inline into all callers".to_string(),
|
||||||
changes: Some(
|
edit: Some(lsp::WorkspaceEdit {
|
||||||
[
|
changes: Some(
|
||||||
(
|
[
|
||||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
(
|
||||||
vec![lsp::TextEdit::new(
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
lsp::Range::new(
|
vec![lsp::TextEdit::new(
|
||||||
lsp::Position::new(1, 22),
|
lsp::Range::new(
|
||||||
lsp::Position::new(1, 34),
|
lsp::Position::new(1, 22),
|
||||||
),
|
lsp::Position::new(1, 34),
|
||||||
"4".to_string(),
|
),
|
||||||
)],
|
"4".to_string(),
|
||||||
),
|
)],
|
||||||
(
|
),
|
||||||
lsp::Url::from_file_path("/a/other.rs").unwrap(),
|
(
|
||||||
vec![lsp::TextEdit::new(
|
lsp::Url::from_file_path("/a/other.rs").unwrap(),
|
||||||
lsp::Range::new(
|
vec![lsp::TextEdit::new(
|
||||||
lsp::Position::new(0, 0),
|
lsp::Range::new(
|
||||||
lsp::Position::new(0, 27),
|
lsp::Position::new(0, 0),
|
||||||
),
|
lsp::Position::new(0, 27),
|
||||||
"".to_string(),
|
),
|
||||||
)],
|
"".to_string(),
|
||||||
),
|
)],
|
||||||
]
|
),
|
||||||
.into_iter()
|
]
|
||||||
.collect(),
|
.into_iter()
|
||||||
),
|
.collect(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}
|
||||||
..Default::default()
|
},
|
||||||
}
|
);
|
||||||
});
|
|
||||||
|
|
||||||
// After the action is confirmed, an editor containing both modified files is opened.
|
// After the action is confirmed, an editor containing both modified files is opened.
|
||||||
confirm_action.await.unwrap();
|
confirm_action.await.unwrap();
|
||||||
|
@ -3642,7 +3663,7 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
fake_language_server
|
fake_language_server
|
||||||
.handle_request::<lsp::request::PrepareRenameRequest, _>(|params, _| {
|
.handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
|
||||||
assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
|
assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
|
||||||
assert_eq!(params.position, lsp::Position::new(0, 7));
|
assert_eq!(params.position, lsp::Position::new(0, 7));
|
||||||
Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
|
Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
|
||||||
|
@ -3672,7 +3693,7 @@ mod tests {
|
||||||
Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
|
Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
|
||||||
});
|
});
|
||||||
fake_language_server
|
fake_language_server
|
||||||
.handle_request::<lsp::request::Rename, _>(|params, _| {
|
.handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.text_document.uri.as_str(),
|
params.text_document_position.text_document.uri.as_str(),
|
||||||
"file:///dir/one.rs"
|
"file:///dir/one.rs"
|
||||||
|
@ -5320,30 +5341,34 @@ mod tests {
|
||||||
let files = files.clone();
|
let files = files.clone();
|
||||||
let project = project.downgrade();
|
let project = project.downgrade();
|
||||||
move |fake_server| {
|
move |fake_server| {
|
||||||
fake_server.handle_request::<lsp::request::Completion, _>(|_, _| {
|
fake_server.handle_request::<lsp::request::Completion, _, _>(
|
||||||
Some(lsp::CompletionResponse::Array(vec![lsp::CompletionItem {
|
|_, _| async move {
|
||||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
Some(lsp::CompletionResponse::Array(vec![lsp::CompletionItem {
|
||||||
range: lsp::Range::new(
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
lsp::Position::new(0, 0),
|
range: lsp::Range::new(
|
||||||
lsp::Position::new(0, 0),
|
lsp::Position::new(0, 0),
|
||||||
),
|
lsp::Position::new(0, 0),
|
||||||
new_text: "the-new-text".to_string(),
|
),
|
||||||
})),
|
new_text: "the-new-text".to_string(),
|
||||||
..Default::default()
|
})),
|
||||||
}]))
|
|
||||||
});
|
|
||||||
|
|
||||||
fake_server.handle_request::<lsp::request::CodeActionRequest, _>(|_, _| {
|
|
||||||
Some(vec![lsp::CodeActionOrCommand::CodeAction(
|
|
||||||
lsp::CodeAction {
|
|
||||||
title: "the-code-action".to_string(),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}]))
|
||||||
)])
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
fake_server.handle_request::<lsp::request::PrepareRenameRequest, _>(
|
fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
|
||||||
|params, _| {
|
|_, _| async move {
|
||||||
|
Some(vec![lsp::CodeActionOrCommand::CodeAction(
|
||||||
|
lsp::CodeAction {
|
||||||
|
title: "the-code-action".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)])
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
|
||||||
|
|params, _| async move {
|
||||||
Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
|
Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
|
||||||
params.position,
|
params.position,
|
||||||
params.position,
|
params.position,
|
||||||
|
@ -5351,34 +5376,38 @@ mod tests {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
fake_server.handle_request::<lsp::request::GotoDefinition, _>({
|
fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
|
||||||
let files = files.clone();
|
let files = files.clone();
|
||||||
let rng = rng.clone();
|
let rng = rng.clone();
|
||||||
move |_, _| {
|
move |_, _| {
|
||||||
let files = files.lock();
|
let files = files.clone();
|
||||||
let mut rng = rng.lock();
|
let rng = rng.clone();
|
||||||
let count = rng.gen_range::<usize, _>(1..3);
|
async move {
|
||||||
let files = (0..count)
|
let files = files.lock();
|
||||||
.map(|_| files.choose(&mut *rng).unwrap())
|
let mut rng = rng.lock();
|
||||||
.collect::<Vec<_>>();
|
let count = rng.gen_range::<usize, _>(1..3);
|
||||||
log::info!("LSP: Returning definitions in files {:?}", &files);
|
let files = (0..count)
|
||||||
Some(lsp::GotoDefinitionResponse::Array(
|
.map(|_| files.choose(&mut *rng).unwrap())
|
||||||
files
|
.collect::<Vec<_>>();
|
||||||
.into_iter()
|
log::info!("LSP: Returning definitions in files {:?}", &files);
|
||||||
.map(|file| lsp::Location {
|
Some(lsp::GotoDefinitionResponse::Array(
|
||||||
uri: lsp::Url::from_file_path(file).unwrap(),
|
files
|
||||||
range: Default::default(),
|
.into_iter()
|
||||||
})
|
.map(|file| lsp::Location {
|
||||||
.collect(),
|
uri: lsp::Url::from_file_path(file).unwrap(),
|
||||||
))
|
range: Default::default(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _>({
|
fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>({
|
||||||
let rng = rng.clone();
|
let rng = rng.clone();
|
||||||
let project = project.clone();
|
let project = project.clone();
|
||||||
move |params, mut cx| {
|
move |params, mut cx| {
|
||||||
if let Some(project) = project.upgrade(&cx) {
|
let highlights = if let Some(project) = project.upgrade(&cx) {
|
||||||
project.update(&mut cx, |project, cx| {
|
project.update(&mut cx, |project, cx| {
|
||||||
let path = params
|
let path = params
|
||||||
.text_document_position_params
|
.text_document_position_params
|
||||||
|
@ -5415,7 +5444,8 @@ mod tests {
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
};
|
||||||
|
async move { highlights }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue