mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-16 00:47:39 +03:00
Tailwind autocomplete (#2920)
Release Notes: - Added basic Tailwind CSS autocomplete support ([#746](https://github.com/zed-industries/community/issues/746)).
This commit is contained in:
commit
ddc6214216
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -4177,8 +4177,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "lsp-types"
|
name = "lsp-types"
|
||||||
version = "0.94.1"
|
version = "0.94.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/zed-industries/lsp-types?branch=updated-completion-list-item-defaults#90a040a1d195687bd19e1df47463320a44e93d7a"
|
||||||
checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -44,7 +44,7 @@ use gpui::{
|
|||||||
elements::*,
|
elements::*,
|
||||||
executor,
|
executor,
|
||||||
fonts::{self, HighlightStyle, TextStyle},
|
fonts::{self, HighlightStyle, TextStyle},
|
||||||
geometry::vector::Vector2F,
|
geometry::vector::{vec2f, Vector2F},
|
||||||
impl_actions,
|
impl_actions,
|
||||||
keymap_matcher::KeymapContext,
|
keymap_matcher::KeymapContext,
|
||||||
platform::{CursorStyle, MouseButton},
|
platform::{CursorStyle, MouseButton},
|
||||||
@ -820,6 +820,7 @@ struct CompletionsMenu {
|
|||||||
id: CompletionId,
|
id: CompletionId,
|
||||||
initial_position: Anchor,
|
initial_position: Anchor,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
|
project: Option<ModelHandle<Project>>,
|
||||||
completions: Arc<[Completion]>,
|
completions: Arc<[Completion]>,
|
||||||
match_candidates: Vec<StringMatchCandidate>,
|
match_candidates: Vec<StringMatchCandidate>,
|
||||||
matches: Arc<[StringMatch]>,
|
matches: Arc<[StringMatch]>,
|
||||||
@ -863,6 +864,48 @@ impl CompletionsMenu {
|
|||||||
fn render(&self, style: EditorStyle, cx: &mut ViewContext<Editor>) -> AnyElement<Editor> {
|
fn render(&self, style: EditorStyle, cx: &mut ViewContext<Editor>) -> AnyElement<Editor> {
|
||||||
enum CompletionTag {}
|
enum CompletionTag {}
|
||||||
|
|
||||||
|
let language_servers = self.project.as_ref().map(|project| {
|
||||||
|
project
|
||||||
|
.read(cx)
|
||||||
|
.language_servers_for_buffer(self.buffer.read(cx), cx)
|
||||||
|
.filter(|(_, server)| server.capabilities().completion_provider.is_some())
|
||||||
|
.map(|(adapter, server)| (server.server_id(), adapter.short_name))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
let needs_server_name = language_servers
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |servers| servers.len() > 1);
|
||||||
|
|
||||||
|
let get_server_name =
|
||||||
|
move |lookup_server_id: lsp::LanguageServerId| -> Option<&'static str> {
|
||||||
|
language_servers
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.find_map(|(server_id, server_name)| {
|
||||||
|
if *server_id == lookup_server_id {
|
||||||
|
Some(*server_name)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let widest_completion_ix = self
|
||||||
|
.matches
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.max_by_key(|(_, mat)| {
|
||||||
|
let completion = &self.completions[mat.candidate_id];
|
||||||
|
let mut len = completion.label.text.chars().count();
|
||||||
|
|
||||||
|
if let Some(server_name) = get_server_name(completion.server_id) {
|
||||||
|
len += server_name.chars().count();
|
||||||
|
}
|
||||||
|
|
||||||
|
len
|
||||||
|
})
|
||||||
|
.map(|(ix, _)| ix);
|
||||||
|
|
||||||
let completions = self.completions.clone();
|
let completions = self.completions.clone();
|
||||||
let matches = self.matches.clone();
|
let matches = self.matches.clone();
|
||||||
let selected_item = self.selected_item;
|
let selected_item = self.selected_item;
|
||||||
@ -889,19 +932,83 @@ impl CompletionsMenu {
|
|||||||
style.autocomplete.item
|
style.autocomplete.item
|
||||||
};
|
};
|
||||||
|
|
||||||
Text::new(completion.label.text.clone(), style.text.clone())
|
let completion_label =
|
||||||
.with_soft_wrap(false)
|
Text::new(completion.label.text.clone(), style.text.clone())
|
||||||
.with_highlights(combine_syntax_and_fuzzy_match_highlights(
|
.with_soft_wrap(false)
|
||||||
&completion.label.text,
|
.with_highlights(
|
||||||
style.text.color.into(),
|
combine_syntax_and_fuzzy_match_highlights(
|
||||||
styled_runs_for_code_label(
|
&completion.label.text,
|
||||||
&completion.label,
|
style.text.color.into(),
|
||||||
&style.syntax,
|
styled_runs_for_code_label(
|
||||||
),
|
&completion.label,
|
||||||
&mat.positions,
|
&style.syntax,
|
||||||
))
|
),
|
||||||
.contained()
|
&mat.positions,
|
||||||
.with_style(item_style)
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(server_name) = get_server_name(completion.server_id) {
|
||||||
|
Flex::row()
|
||||||
|
.with_child(completion_label)
|
||||||
|
.with_children((|| {
|
||||||
|
if !needs_server_name {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let text_style = TextStyle {
|
||||||
|
color: style.autocomplete.server_name_color,
|
||||||
|
font_size: style.text.font_size
|
||||||
|
* style.autocomplete.server_name_size_percent,
|
||||||
|
..style.text.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let label = Text::new(server_name, text_style)
|
||||||
|
.aligned()
|
||||||
|
.constrained()
|
||||||
|
.dynamically(move |constraint, _, _| {
|
||||||
|
gpui::SizeConstraint {
|
||||||
|
min: constraint.min,
|
||||||
|
max: vec2f(
|
||||||
|
constraint.max.x(),
|
||||||
|
constraint.min.y(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if Some(item_ix) == widest_completion_ix {
|
||||||
|
Some(
|
||||||
|
label
|
||||||
|
.contained()
|
||||||
|
.with_style(
|
||||||
|
style
|
||||||
|
.autocomplete
|
||||||
|
.server_name_container,
|
||||||
|
)
|
||||||
|
.into_any(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Some(label.flex_float().into_any())
|
||||||
|
}
|
||||||
|
})())
|
||||||
|
.into_any()
|
||||||
|
} else {
|
||||||
|
completion_label.into_any()
|
||||||
|
}
|
||||||
|
.contained()
|
||||||
|
.with_style(item_style)
|
||||||
|
.constrained()
|
||||||
|
.dynamically(
|
||||||
|
move |constraint, _, _| {
|
||||||
|
if Some(item_ix) == widest_completion_ix {
|
||||||
|
constraint
|
||||||
|
} else {
|
||||||
|
gpui::SizeConstraint {
|
||||||
|
min: constraint.min,
|
||||||
|
max: constraint.min,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
@ -918,19 +1025,7 @@ impl CompletionsMenu {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.with_width_from_item(
|
.with_width_from_item(widest_completion_ix)
|
||||||
self.matches
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.max_by_key(|(_, mat)| {
|
|
||||||
self.completions[mat.candidate_id]
|
|
||||||
.label
|
|
||||||
.text
|
|
||||||
.chars()
|
|
||||||
.count()
|
|
||||||
})
|
|
||||||
.map(|(ix, _)| ix),
|
|
||||||
)
|
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(container_style)
|
.with_style(container_style)
|
||||||
.into_any()
|
.into_any()
|
||||||
@ -2983,6 +3078,7 @@ impl Editor {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let id = post_inc(&mut self.next_completion_id);
|
let id = post_inc(&mut self.next_completion_id);
|
||||||
|
let project = self.project.clone();
|
||||||
let task = cx.spawn(|this, mut cx| {
|
let task = cx.spawn(|this, mut cx| {
|
||||||
async move {
|
async move {
|
||||||
let menu = if let Some(completions) = completions.await.log_err() {
|
let menu = if let Some(completions) = completions.await.log_err() {
|
||||||
@ -3001,6 +3097,7 @@ impl Editor {
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
buffer,
|
buffer,
|
||||||
|
project,
|
||||||
completions: completions.into(),
|
completions: completions.into(),
|
||||||
matches: Vec::new().into(),
|
matches: Vec::new().into(),
|
||||||
selected_item: 0,
|
selected_item: 0,
|
||||||
@ -9186,6 +9283,7 @@ pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str
|
|||||||
None
|
None
|
||||||
})
|
})
|
||||||
.flat_map(|word| word.split_inclusive('_'))
|
.flat_map(|word| word.split_inclusive('_'))
|
||||||
|
.flat_map(|word| word.split_inclusive('-'))
|
||||||
}
|
}
|
||||||
|
|
||||||
trait RangeToAnchorExt {
|
trait RangeToAnchorExt {
|
||||||
|
@ -19,7 +19,8 @@ use gpui::{
|
|||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
|
language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
|
||||||
BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point,
|
BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry,
|
||||||
|
Override, Point,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::project_settings::{LspSettings, ProjectSettings};
|
use project::project_settings::{LspSettings, ProjectSettings};
|
||||||
@ -7688,6 +7689,105 @@ async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
|
|||||||
cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
|
cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
|
let mut cx = EditorLspTestContext::new(
|
||||||
|
Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
path_suffixes: vec!["jsx".into()],
|
||||||
|
overrides: [(
|
||||||
|
"element".into(),
|
||||||
|
LanguageConfigOverride {
|
||||||
|
word_characters: Override::Set(['-'].into_iter().collect()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Some(tree_sitter_typescript::language_tsx()),
|
||||||
|
)
|
||||||
|
.with_override_query("(jsx_self_closing_element) @element")
|
||||||
|
.unwrap(),
|
||||||
|
lsp::ServerCapabilities {
|
||||||
|
completion_provider: Some(lsp::CompletionOptions {
|
||||||
|
trigger_characters: Some(vec![":".to_string()]),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.lsp
|
||||||
|
.handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
|
||||||
|
Ok(Some(lsp::CompletionResponse::Array(vec![
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "bg-blue".into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "bg-red".into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "bg-yellow".into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
])))
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.set_state(r#"<p class="bgˇ" />"#);
|
||||||
|
|
||||||
|
// Trigger completion when typing a dash, because the dash is an extra
|
||||||
|
// word character in the 'element' scope, which contains the cursor.
|
||||||
|
cx.simulate_keystroke("-");
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
cx.update_editor(|editor, _| {
|
||||||
|
if let Some(ContextMenu::Completions(menu)) = &editor.context_menu {
|
||||||
|
assert_eq!(
|
||||||
|
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||||
|
&["bg-red", "bg-blue", "bg-yellow"]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("expected completion menu to be open");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.simulate_keystroke("l");
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
cx.update_editor(|editor, _| {
|
||||||
|
if let Some(ContextMenu::Completions(menu)) = &editor.context_menu {
|
||||||
|
assert_eq!(
|
||||||
|
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||||
|
&["bg-blue", "bg-yellow"]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("expected completion menu to be open");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// When filtering completions, consider the character after the '-' to
|
||||||
|
// be the start of a subword.
|
||||||
|
cx.set_state(r#"<p class="yelˇ" />"#);
|
||||||
|
cx.simulate_keystroke("l");
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
cx.update_editor(|editor, _| {
|
||||||
|
if let Some(ContextMenu::Completions(menu)) = &editor.context_menu {
|
||||||
|
assert_eq!(
|
||||||
|
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||||
|
&["bg-yellow"]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("expected completion menu to be open");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||||
let point = DisplayPoint::new(row as u32, column as u32);
|
let point = DisplayPoint::new(row as u32, column as u32);
|
||||||
point..point
|
point..point
|
||||||
|
@ -177,20 +177,20 @@ pub fn line_end(
|
|||||||
|
|
||||||
pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
||||||
let raw_point = point.to_point(map);
|
let raw_point = point.to_point(map);
|
||||||
let language = map.buffer_snapshot.language_at(raw_point);
|
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
||||||
|
|
||||||
find_preceding_boundary(map, point, |left, right| {
|
find_preceding_boundary(map, point, |left, right| {
|
||||||
(char_kind(language, left) != char_kind(language, right) && !right.is_whitespace())
|
(char_kind(&scope, left) != char_kind(&scope, right) && !right.is_whitespace())
|
||||||
|| left == '\n'
|
|| left == '\n'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
||||||
let raw_point = point.to_point(map);
|
let raw_point = point.to_point(map);
|
||||||
let language = map.buffer_snapshot.language_at(raw_point);
|
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
||||||
find_preceding_boundary(map, point, |left, right| {
|
find_preceding_boundary(map, point, |left, right| {
|
||||||
let is_word_start =
|
let is_word_start =
|
||||||
char_kind(language, left) != char_kind(language, right) && !right.is_whitespace();
|
char_kind(&scope, left) != char_kind(&scope, right) && !right.is_whitespace();
|
||||||
let is_subword_start =
|
let is_subword_start =
|
||||||
left == '_' && right != '_' || left.is_lowercase() && right.is_uppercase();
|
left == '_' && right != '_' || left.is_lowercase() && right.is_uppercase();
|
||||||
is_word_start || is_subword_start || left == '\n'
|
is_word_start || is_subword_start || left == '\n'
|
||||||
@ -199,19 +199,19 @@ pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> Dis
|
|||||||
|
|
||||||
pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
||||||
let raw_point = point.to_point(map);
|
let raw_point = point.to_point(map);
|
||||||
let language = map.buffer_snapshot.language_at(raw_point);
|
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
||||||
find_boundary(map, point, |left, right| {
|
find_boundary(map, point, |left, right| {
|
||||||
(char_kind(language, left) != char_kind(language, right) && !left.is_whitespace())
|
(char_kind(&scope, left) != char_kind(&scope, right) && !left.is_whitespace())
|
||||||
|| right == '\n'
|
|| right == '\n'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
||||||
let raw_point = point.to_point(map);
|
let raw_point = point.to_point(map);
|
||||||
let language = map.buffer_snapshot.language_at(raw_point);
|
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
||||||
find_boundary(map, point, |left, right| {
|
find_boundary(map, point, |left, right| {
|
||||||
let is_word_end =
|
let is_word_end =
|
||||||
(char_kind(language, left) != char_kind(language, right)) && !left.is_whitespace();
|
(char_kind(&scope, left) != char_kind(&scope, right)) && !left.is_whitespace();
|
||||||
let is_subword_end =
|
let is_subword_end =
|
||||||
left != '_' && right == '_' || left.is_lowercase() && right.is_uppercase();
|
left != '_' && right == '_' || left.is_lowercase() && right.is_uppercase();
|
||||||
is_word_end || is_subword_end || right == '\n'
|
is_word_end || is_subword_end || right == '\n'
|
||||||
@ -399,14 +399,14 @@ pub fn find_boundary_in_line(
|
|||||||
|
|
||||||
pub fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool {
|
pub fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool {
|
||||||
let raw_point = point.to_point(map);
|
let raw_point = point.to_point(map);
|
||||||
let language = map.buffer_snapshot.language_at(raw_point);
|
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
||||||
let ix = map.clip_point(point, Bias::Left).to_offset(map, Bias::Left);
|
let ix = map.clip_point(point, Bias::Left).to_offset(map, Bias::Left);
|
||||||
let text = &map.buffer_snapshot;
|
let text = &map.buffer_snapshot;
|
||||||
let next_char_kind = text.chars_at(ix).next().map(|c| char_kind(language, c));
|
let next_char_kind = text.chars_at(ix).next().map(|c| char_kind(&scope, c));
|
||||||
let prev_char_kind = text
|
let prev_char_kind = text
|
||||||
.reversed_chars_at(ix)
|
.reversed_chars_at(ix)
|
||||||
.next()
|
.next()
|
||||||
.map(|c| char_kind(language, c));
|
.map(|c| char_kind(&scope, c));
|
||||||
prev_char_kind.zip(next_char_kind) == Some((CharKind::Word, CharKind::Word))
|
prev_char_kind.zip(next_char_kind) == Some((CharKind::Word, CharKind::Word))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1417,13 +1417,13 @@ impl MultiBuffer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let language = self.language_at(position.clone(), cx);
|
let snapshot = self.snapshot(cx);
|
||||||
|
let position = position.to_offset(&snapshot);
|
||||||
if char_kind(language.as_ref(), char) == CharKind::Word {
|
let scope = snapshot.language_scope_at(position);
|
||||||
|
if char_kind(&scope, char) == CharKind::Word {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let snapshot = self.snapshot(cx);
|
|
||||||
let anchor = snapshot.anchor_before(position);
|
let anchor = snapshot.anchor_before(position);
|
||||||
anchor
|
anchor
|
||||||
.buffer_id
|
.buffer_id
|
||||||
@ -1925,8 +1925,8 @@ impl MultiBufferSnapshot {
|
|||||||
let mut next_chars = self.chars_at(start).peekable();
|
let mut next_chars = self.chars_at(start).peekable();
|
||||||
let mut prev_chars = self.reversed_chars_at(start).peekable();
|
let mut prev_chars = self.reversed_chars_at(start).peekable();
|
||||||
|
|
||||||
let language = self.language_at(start);
|
let scope = self.language_scope_at(start);
|
||||||
let kind = |c| char_kind(language, c);
|
let kind = |c| char_kind(&scope, c);
|
||||||
let word_kind = cmp::max(
|
let word_kind = cmp::max(
|
||||||
prev_chars.peek().copied().map(kind),
|
prev_chars.peek().copied().map(kind),
|
||||||
next_chars.peek().copied().map(kind),
|
next_chars.peek().copied().map(kind),
|
||||||
|
@ -51,7 +51,7 @@ impl<'a> EditorLspTestContext<'a> {
|
|||||||
language
|
language
|
||||||
.path_suffixes()
|
.path_suffixes()
|
||||||
.first()
|
.first()
|
||||||
.unwrap_or(&"txt".to_string())
|
.expect("language must have a path suffix for EditorLspTestContext")
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut fake_servers = language
|
let mut fake_servers = language
|
||||||
|
@ -148,6 +148,7 @@ pub struct Completion {
|
|||||||
pub old_range: Range<Anchor>,
|
pub old_range: Range<Anchor>,
|
||||||
pub new_text: String,
|
pub new_text: String,
|
||||||
pub label: CodeLabel,
|
pub label: CodeLabel,
|
||||||
|
pub server_id: LanguageServerId,
|
||||||
pub lsp_completion: lsp::CompletionItem,
|
pub lsp_completion: lsp::CompletionItem,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2216,8 +2217,8 @@ impl BufferSnapshot {
|
|||||||
let mut next_chars = self.chars_at(start).peekable();
|
let mut next_chars = self.chars_at(start).peekable();
|
||||||
let mut prev_chars = self.reversed_chars_at(start).peekable();
|
let mut prev_chars = self.reversed_chars_at(start).peekable();
|
||||||
|
|
||||||
let language = self.language_at(start);
|
let scope = self.language_scope_at(start);
|
||||||
let kind = |c| char_kind(language, c);
|
let kind = |c| char_kind(&scope, c);
|
||||||
let word_kind = cmp::max(
|
let word_kind = cmp::max(
|
||||||
prev_chars.peek().copied().map(kind),
|
prev_chars.peek().copied().map(kind),
|
||||||
next_chars.peek().copied().map(kind),
|
next_chars.peek().copied().map(kind),
|
||||||
@ -3031,17 +3032,21 @@ pub fn contiguous_ranges(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn char_kind(language: Option<&Arc<Language>>, c: char) -> CharKind {
|
pub fn char_kind(scope: &Option<LanguageScope>, c: char) -> CharKind {
|
||||||
if c.is_whitespace() {
|
if c.is_whitespace() {
|
||||||
return CharKind::Whitespace;
|
return CharKind::Whitespace;
|
||||||
} else if c.is_alphanumeric() || c == '_' {
|
} else if c.is_alphanumeric() || c == '_' {
|
||||||
return CharKind::Word;
|
return CharKind::Word;
|
||||||
}
|
}
|
||||||
if let Some(language) = language {
|
|
||||||
if language.config.word_characters.contains(&c) {
|
if let Some(scope) = scope {
|
||||||
return CharKind::Word;
|
if let Some(characters) = scope.word_characters() {
|
||||||
|
if characters.contains(&c) {
|
||||||
|
return CharKind::Word;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CharKind::Punctuation
|
CharKind::Punctuation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ use theme::{SyntaxTheme, Theme};
|
|||||||
use tree_sitter::{self, Query};
|
use tree_sitter::{self, Query};
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
use util::{http::HttpClient, paths::PathExt};
|
use util::{http::HttpClient, paths::PathExt};
|
||||||
use util::{merge_json_value_into, post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
|
use util::{post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
use futures::channel::mpsc;
|
use futures::channel::mpsc;
|
||||||
@ -91,6 +91,7 @@ pub struct LanguageServerName(pub Arc<str>);
|
|||||||
/// once at startup, and caches the results.
|
/// once at startup, and caches the results.
|
||||||
pub struct CachedLspAdapter {
|
pub struct CachedLspAdapter {
|
||||||
pub name: LanguageServerName,
|
pub name: LanguageServerName,
|
||||||
|
pub short_name: &'static str,
|
||||||
pub initialization_options: Option<Value>,
|
pub initialization_options: Option<Value>,
|
||||||
pub disk_based_diagnostic_sources: Vec<String>,
|
pub disk_based_diagnostic_sources: Vec<String>,
|
||||||
pub disk_based_diagnostics_progress_token: Option<String>,
|
pub disk_based_diagnostics_progress_token: Option<String>,
|
||||||
@ -101,6 +102,7 @@ pub struct CachedLspAdapter {
|
|||||||
impl CachedLspAdapter {
|
impl CachedLspAdapter {
|
||||||
pub async fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
|
pub async fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
|
||||||
let name = adapter.name().await;
|
let name = adapter.name().await;
|
||||||
|
let short_name = adapter.short_name();
|
||||||
let initialization_options = adapter.initialization_options().await;
|
let initialization_options = adapter.initialization_options().await;
|
||||||
let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await;
|
let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await;
|
||||||
let disk_based_diagnostics_progress_token =
|
let disk_based_diagnostics_progress_token =
|
||||||
@ -109,6 +111,7 @@ impl CachedLspAdapter {
|
|||||||
|
|
||||||
Arc::new(CachedLspAdapter {
|
Arc::new(CachedLspAdapter {
|
||||||
name,
|
name,
|
||||||
|
short_name,
|
||||||
initialization_options,
|
initialization_options,
|
||||||
disk_based_diagnostic_sources,
|
disk_based_diagnostic_sources,
|
||||||
disk_based_diagnostics_progress_token,
|
disk_based_diagnostics_progress_token,
|
||||||
@ -176,10 +179,7 @@ impl CachedLspAdapter {
|
|||||||
self.adapter.code_action_kinds()
|
self.adapter.code_action_kinds()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn workspace_configuration(
|
pub fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
|
||||||
&self,
|
|
||||||
cx: &mut AppContext,
|
|
||||||
) -> Option<BoxFuture<'static, Value>> {
|
|
||||||
self.adapter.workspace_configuration(cx)
|
self.adapter.workspace_configuration(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,6 +220,8 @@ pub trait LspAdapterDelegate: Send + Sync {
|
|||||||
pub trait LspAdapter: 'static + Send + Sync {
|
pub trait LspAdapter: 'static + Send + Sync {
|
||||||
async fn name(&self) -> LanguageServerName;
|
async fn name(&self) -> LanguageServerName;
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str;
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
delegate: &dyn LspAdapterDelegate,
|
delegate: &dyn LspAdapterDelegate,
|
||||||
@ -288,8 +290,8 @@ pub trait LspAdapter: 'static + Send + Sync {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workspace_configuration(&self, _: &mut AppContext) -> Option<BoxFuture<'static, Value>> {
|
fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
|
||||||
None
|
futures::future::ready(serde_json::json!({})).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
|
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
|
||||||
@ -344,6 +346,8 @@ pub struct LanguageConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub block_comment: Option<(Arc<str>, Arc<str>)>,
|
pub block_comment: Option<(Arc<str>, Arc<str>)>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub scope_opt_in_language_servers: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
pub overrides: HashMap<String, LanguageConfigOverride>,
|
pub overrides: HashMap<String, LanguageConfigOverride>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub word_characters: HashSet<char>,
|
pub word_characters: HashSet<char>,
|
||||||
@ -374,6 +378,10 @@ pub struct LanguageConfigOverride {
|
|||||||
pub block_comment: Override<(Arc<str>, Arc<str>)>,
|
pub block_comment: Override<(Arc<str>, Arc<str>)>,
|
||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
pub disabled_bracket_ixs: Vec<u16>,
|
pub disabled_bracket_ixs: Vec<u16>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub word_characters: Override<HashSet<char>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub opt_into_language_servers: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Debug)]
|
#[derive(Clone, Deserialize, Debug)]
|
||||||
@ -412,6 +420,7 @@ impl Default for LanguageConfig {
|
|||||||
autoclose_before: Default::default(),
|
autoclose_before: Default::default(),
|
||||||
line_comment: Default::default(),
|
line_comment: Default::default(),
|
||||||
block_comment: Default::default(),
|
block_comment: Default::default(),
|
||||||
|
scope_opt_in_language_servers: Default::default(),
|
||||||
overrides: Default::default(),
|
overrides: Default::default(),
|
||||||
collapsed_placeholder: Default::default(),
|
collapsed_placeholder: Default::default(),
|
||||||
word_characters: Default::default(),
|
word_characters: Default::default(),
|
||||||
@ -686,41 +695,6 @@ impl LanguageRegistry {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn workspace_configuration(&self, cx: &mut AppContext) -> Task<serde_json::Value> {
|
|
||||||
let lsp_adapters = {
|
|
||||||
let state = self.state.read();
|
|
||||||
state
|
|
||||||
.available_languages
|
|
||||||
.iter()
|
|
||||||
.filter(|l| !l.loaded)
|
|
||||||
.flat_map(|l| l.lsp_adapters.clone())
|
|
||||||
.chain(
|
|
||||||
state
|
|
||||||
.languages
|
|
||||||
.iter()
|
|
||||||
.flat_map(|language| &language.adapters)
|
|
||||||
.map(|adapter| adapter.adapter.clone()),
|
|
||||||
)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut language_configs = Vec::new();
|
|
||||||
for adapter in &lsp_adapters {
|
|
||||||
if let Some(language_config) = adapter.workspace_configuration(cx) {
|
|
||||||
language_configs.push(language_config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.background().spawn(async move {
|
|
||||||
let mut config = serde_json::json!({});
|
|
||||||
let language_configs = futures::future::join_all(language_configs).await;
|
|
||||||
for language_config in language_configs {
|
|
||||||
merge_json_value_into(language_config, &mut config);
|
|
||||||
}
|
|
||||||
config
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(&self, language: Arc<Language>) {
|
pub fn add(&self, language: Arc<Language>) {
|
||||||
self.state.write().add(language);
|
self.state.write().add(language);
|
||||||
}
|
}
|
||||||
@ -1384,13 +1358,23 @@ impl Language {
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_override_query(mut self, source: &str) -> Result<Self> {
|
pub fn with_override_query(mut self, source: &str) -> anyhow::Result<Self> {
|
||||||
let query = Query::new(self.grammar_mut().ts_language, source)?;
|
let query = Query::new(self.grammar_mut().ts_language, source)?;
|
||||||
|
|
||||||
let mut override_configs_by_id = HashMap::default();
|
let mut override_configs_by_id = HashMap::default();
|
||||||
for (ix, name) in query.capture_names().iter().enumerate() {
|
for (ix, name) in query.capture_names().iter().enumerate() {
|
||||||
if !name.starts_with('_') {
|
if !name.starts_with('_') {
|
||||||
let value = self.config.overrides.remove(name).unwrap_or_default();
|
let value = self.config.overrides.remove(name).unwrap_or_default();
|
||||||
|
for server_name in &value.opt_into_language_servers {
|
||||||
|
if !self
|
||||||
|
.config
|
||||||
|
.scope_opt_in_language_servers
|
||||||
|
.contains(server_name)
|
||||||
|
{
|
||||||
|
util::debug_panic!("Server {server_name:?} has been opted-in by scope {name:?} but has not been marked as an opt-in server");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override_configs_by_id.insert(ix as u32, (name.clone(), value));
|
override_configs_by_id.insert(ix as u32, (name.clone(), value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1596,6 +1580,13 @@ impl LanguageScope {
|
|||||||
.map(|e| (&e.0, &e.1))
|
.map(|e| (&e.0, &e.1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn word_characters(&self) -> Option<&HashSet<char>> {
|
||||||
|
Override::as_option(
|
||||||
|
self.config_override().map(|o| &o.word_characters),
|
||||||
|
Some(&self.language.config.word_characters),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn brackets(&self) -> impl Iterator<Item = (&BracketPair, bool)> {
|
pub fn brackets(&self) -> impl Iterator<Item = (&BracketPair, bool)> {
|
||||||
let mut disabled_ids = self
|
let mut disabled_ids = self
|
||||||
.config_override()
|
.config_override()
|
||||||
@ -1622,6 +1613,20 @@ impl LanguageScope {
|
|||||||
c.is_whitespace() || self.language.config.autoclose_before.contains(c)
|
c.is_whitespace() || self.language.config.autoclose_before.contains(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn language_allowed(&self, name: &LanguageServerName) -> bool {
|
||||||
|
let config = &self.language.config;
|
||||||
|
let opt_in_servers = &config.scope_opt_in_language_servers;
|
||||||
|
if opt_in_servers.iter().any(|o| *o == *name.0) {
|
||||||
|
if let Some(over) = self.config_override() {
|
||||||
|
over.opt_into_language_servers.iter().any(|o| *o == *name.0)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn config_override(&self) -> Option<&LanguageConfigOverride> {
|
fn config_override(&self) -> Option<&LanguageConfigOverride> {
|
||||||
let id = self.override_id?;
|
let id = self.override_id?;
|
||||||
let grammar = self.language.grammar.as_ref()?;
|
let grammar = self.language.grammar.as_ref()?;
|
||||||
@ -1726,6 +1731,10 @@ impl LspAdapter for Arc<FakeLspAdapter> {
|
|||||||
LanguageServerName(self.name.into())
|
LanguageServerName(self.name.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"FakeLspAdapter"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn LspAdapterDelegate,
|
_: &dyn LspAdapterDelegate,
|
||||||
|
@ -434,6 +434,7 @@ pub fn serialize_completion(completion: &Completion) -> proto::Completion {
|
|||||||
old_start: Some(serialize_anchor(&completion.old_range.start)),
|
old_start: Some(serialize_anchor(&completion.old_range.start)),
|
||||||
old_end: Some(serialize_anchor(&completion.old_range.end)),
|
old_end: Some(serialize_anchor(&completion.old_range.end)),
|
||||||
new_text: completion.new_text.clone(),
|
new_text: completion.new_text.clone(),
|
||||||
|
server_id: completion.server_id.0 as u64,
|
||||||
lsp_completion: serde_json::to_vec(&completion.lsp_completion).unwrap(),
|
lsp_completion: serde_json::to_vec(&completion.lsp_completion).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -466,6 +467,7 @@ pub async fn deserialize_completion(
|
|||||||
lsp_completion.filter_text.as_deref(),
|
lsp_completion.filter_text.as_deref(),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
server_id: LanguageServerId(completion.server_id as usize),
|
||||||
lsp_completion,
|
lsp_completion,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -42,8 +42,8 @@
|
|||||||
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
|
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "0af9125c4eae12a4973fb66574c53a54962a9e1e",
|
"revision": "ce20dc083ee485524b802669890291c0d8090170",
|
||||||
"version": "1.21.0"
|
"version": "1.22.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -20,7 +20,7 @@ anyhow.workspace = true
|
|||||||
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553", optional = true }
|
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553", optional = true }
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
lsp-types = "0.94"
|
lsp-types = { git = "https://github.com/zed-industries/lsp-types", branch = "updated-completion-list-item-defaults" }
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
@ -4,7 +4,7 @@ pub use lsp_types::*;
|
|||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use futures::{channel::oneshot, io::BufWriter, AsyncRead, AsyncWrite};
|
use futures::{channel::oneshot, io::BufWriter, AsyncRead, AsyncWrite, FutureExt};
|
||||||
use gpui::{executor, AsyncAppContext, Task};
|
use gpui::{executor, AsyncAppContext, Task};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use postage::{barrier, prelude::Stream};
|
use postage::{barrier, prelude::Stream};
|
||||||
@ -26,12 +26,14 @@ use std::{
|
|||||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||||
Arc, Weak,
|
Arc, Weak,
|
||||||
},
|
},
|
||||||
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use std::{path::Path, process::Stdio};
|
use std::{path::Path, process::Stdio};
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
|
|
||||||
const JSON_RPC_VERSION: &str = "2.0";
|
const JSON_RPC_VERSION: &str = "2.0";
|
||||||
const CONTENT_LEN_HEADER: &str = "Content-Length: ";
|
const CONTENT_LEN_HEADER: &str = "Content-Length: ";
|
||||||
|
const LSP_REQUEST_TIMEOUT: Duration = Duration::from_secs(60 * 2);
|
||||||
|
|
||||||
type NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppContext)>;
|
type NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppContext)>;
|
||||||
type ResponseHandler = Box<dyn Send + FnOnce(Result<String, Error>)>;
|
type ResponseHandler = Box<dyn Send + FnOnce(Result<String, Error>)>;
|
||||||
@ -303,7 +305,7 @@ impl LanguageServer {
|
|||||||
stdout.read_exact(&mut buffer).await?;
|
stdout.read_exact(&mut buffer).await?;
|
||||||
|
|
||||||
if let Ok(message) = str::from_utf8(&buffer) {
|
if let Ok(message) = str::from_utf8(&buffer) {
|
||||||
log::trace!("incoming message:{}", message);
|
log::trace!("incoming message: {}", message);
|
||||||
for handler in io_handlers.lock().values_mut() {
|
for handler in io_handlers.lock().values_mut() {
|
||||||
handler(IoKind::StdOut, message);
|
handler(IoKind::StdOut, message);
|
||||||
}
|
}
|
||||||
@ -468,6 +470,14 @@ impl LanguageServer {
|
|||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
|
completion_list: Some(CompletionListCapability {
|
||||||
|
item_defaults: Some(vec![
|
||||||
|
"commitCharacters".to_owned(),
|
||||||
|
"editRange".to_owned(),
|
||||||
|
"insertTextMode".to_owned(),
|
||||||
|
"data".to_owned(),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
rename: Some(RenameClientCapabilities {
|
rename: Some(RenameClientCapabilities {
|
||||||
@ -740,7 +750,7 @@ impl LanguageServer {
|
|||||||
outbound_tx: &channel::Sender<String>,
|
outbound_tx: &channel::Sender<String>,
|
||||||
executor: &Arc<executor::Background>,
|
executor: &Arc<executor::Background>,
|
||||||
params: T::Params,
|
params: T::Params,
|
||||||
) -> impl 'static + Future<Output = Result<T::Result>>
|
) -> impl 'static + Future<Output = anyhow::Result<T::Result>>
|
||||||
where
|
where
|
||||||
T::Result: 'static + Send,
|
T::Result: 'static + Send,
|
||||||
{
|
{
|
||||||
@ -781,10 +791,25 @@ impl LanguageServer {
|
|||||||
.try_send(message)
|
.try_send(message)
|
||||||
.context("failed to write to language server's stdin");
|
.context("failed to write to language server's stdin");
|
||||||
|
|
||||||
|
let mut timeout = executor.timer(LSP_REQUEST_TIMEOUT).fuse();
|
||||||
|
let started = Instant::now();
|
||||||
async move {
|
async move {
|
||||||
handle_response?;
|
handle_response?;
|
||||||
send?;
|
send?;
|
||||||
rx.await?
|
|
||||||
|
let method = T::METHOD;
|
||||||
|
futures::select! {
|
||||||
|
response = rx.fuse() => {
|
||||||
|
let elapsed = started.elapsed();
|
||||||
|
log::trace!("Took {elapsed:?} to recieve response to {method:?} id {id}");
|
||||||
|
response?
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = timeout => {
|
||||||
|
log::error!("Cancelled LSP request task for {method:?} id {id} which took over {LSP_REQUEST_TIMEOUT:?}");
|
||||||
|
anyhow::bail!("LSP request timeout");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,10 @@ use language::{
|
|||||||
CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
|
CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
|
||||||
Unclipped,
|
Unclipped,
|
||||||
};
|
};
|
||||||
use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, OneOf, ServerCapabilities};
|
use lsp::{
|
||||||
|
CompletionListItemDefaultsEditRange, DocumentHighlightKind, LanguageServer, LanguageServerId,
|
||||||
|
OneOf, ServerCapabilities,
|
||||||
|
};
|
||||||
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
|
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
|
||||||
use text::LineEnding;
|
use text::LineEnding;
|
||||||
|
|
||||||
@ -1340,13 +1343,19 @@ impl LspCommand for GetCompletions {
|
|||||||
completions: Option<lsp::CompletionResponse>,
|
completions: Option<lsp::CompletionResponse>,
|
||||||
_: ModelHandle<Project>,
|
_: ModelHandle<Project>,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
_: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> Result<Vec<Completion>> {
|
) -> Result<Vec<Completion>> {
|
||||||
|
let mut response_list = None;
|
||||||
let completions = if let Some(completions) = completions {
|
let completions = if let Some(completions) = completions {
|
||||||
match completions {
|
match completions {
|
||||||
lsp::CompletionResponse::Array(completions) => completions,
|
lsp::CompletionResponse::Array(completions) => completions,
|
||||||
lsp::CompletionResponse::List(list) => list.items,
|
|
||||||
|
lsp::CompletionResponse::List(mut list) => {
|
||||||
|
let items = std::mem::take(&mut list.items);
|
||||||
|
response_list = Some(list);
|
||||||
|
items
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Default::default()
|
Default::default()
|
||||||
@ -1356,6 +1365,7 @@ impl LspCommand for GetCompletions {
|
|||||||
let language = buffer.language().cloned();
|
let language = buffer.language().cloned();
|
||||||
let snapshot = buffer.snapshot();
|
let snapshot = buffer.snapshot();
|
||||||
let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
|
let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
|
||||||
|
|
||||||
let mut range_for_token = None;
|
let mut range_for_token = None;
|
||||||
completions
|
completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -1376,6 +1386,7 @@ impl LspCommand for GetCompletions {
|
|||||||
edit.new_text.clone(),
|
edit.new_text.clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the language server does not provide a range, then infer
|
// If the language server does not provide a range, then infer
|
||||||
// the range based on the syntax tree.
|
// the range based on the syntax tree.
|
||||||
None => {
|
None => {
|
||||||
@ -1383,27 +1394,51 @@ impl LspCommand for GetCompletions {
|
|||||||
log::info!("completion out of expected range");
|
log::info!("completion out of expected range");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let Range { start, end } = range_for_token
|
|
||||||
.get_or_insert_with(|| {
|
let default_edit_range = response_list
|
||||||
let offset = self.position.to_offset(&snapshot);
|
.as_ref()
|
||||||
let (range, kind) = snapshot.surrounding_word(offset);
|
.and_then(|list| list.item_defaults.as_ref())
|
||||||
if kind == Some(CharKind::Word) {
|
.and_then(|defaults| defaults.edit_range.as_ref())
|
||||||
range
|
.and_then(|range| match range {
|
||||||
} else {
|
CompletionListItemDefaultsEditRange::Range(r) => Some(r),
|
||||||
offset..offset
|
_ => None,
|
||||||
}
|
});
|
||||||
})
|
|
||||||
.clone();
|
let range = if let Some(range) = default_edit_range {
|
||||||
|
let range = range_from_lsp(range.clone());
|
||||||
|
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||||
|
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||||
|
if start != range.start.0 || end != range.end.0 {
|
||||||
|
log::info!("completion out of expected range");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.anchor_before(start)..snapshot.anchor_after(end)
|
||||||
|
} else {
|
||||||
|
range_for_token
|
||||||
|
.get_or_insert_with(|| {
|
||||||
|
let offset = self.position.to_offset(&snapshot);
|
||||||
|
let (range, kind) = snapshot.surrounding_word(offset);
|
||||||
|
let range = if kind == Some(CharKind::Word) {
|
||||||
|
range
|
||||||
|
} else {
|
||||||
|
offset..offset
|
||||||
|
};
|
||||||
|
|
||||||
|
snapshot.anchor_before(range.start)
|
||||||
|
..snapshot.anchor_after(range.end)
|
||||||
|
})
|
||||||
|
.clone()
|
||||||
|
};
|
||||||
|
|
||||||
let text = lsp_completion
|
let text = lsp_completion
|
||||||
.insert_text
|
.insert_text
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap_or(&lsp_completion.label)
|
.unwrap_or(&lsp_completion.label)
|
||||||
.clone();
|
.clone();
|
||||||
(
|
(range, text)
|
||||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
|
||||||
text,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => {
|
Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => {
|
||||||
log::info!("unsupported insert/replace completion");
|
log::info!("unsupported insert/replace completion");
|
||||||
return None;
|
return None;
|
||||||
@ -1427,6 +1462,7 @@ impl LspCommand for GetCompletions {
|
|||||||
lsp_completion.filter_text.as_deref(),
|
lsp_completion.filter_text.as_deref(),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
server_id,
|
||||||
lsp_completion,
|
lsp_completion,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -156,6 +156,11 @@ struct DelayedDebounced {
|
|||||||
cancel_channel: Option<oneshot::Sender<()>>,
|
cancel_channel: Option<oneshot::Sender<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum LanguageServerToQuery {
|
||||||
|
Primary,
|
||||||
|
Other(LanguageServerId),
|
||||||
|
}
|
||||||
|
|
||||||
impl DelayedDebounced {
|
impl DelayedDebounced {
|
||||||
fn new() -> DelayedDebounced {
|
fn new() -> DelayedDebounced {
|
||||||
DelayedDebounced {
|
DelayedDebounced {
|
||||||
@ -634,7 +639,7 @@ impl Project {
|
|||||||
cx.observe_global::<SettingsStore, _>(Self::on_settings_changed)
|
cx.observe_global::<SettingsStore, _>(Self::on_settings_changed)
|
||||||
],
|
],
|
||||||
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
|
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
|
||||||
_maintain_workspace_config: Self::maintain_workspace_config(languages.clone(), cx),
|
_maintain_workspace_config: Self::maintain_workspace_config(cx),
|
||||||
active_entry: None,
|
active_entry: None,
|
||||||
languages,
|
languages,
|
||||||
client,
|
client,
|
||||||
@ -704,7 +709,7 @@ impl Project {
|
|||||||
collaborators: Default::default(),
|
collaborators: Default::default(),
|
||||||
join_project_response_message_id: response.message_id,
|
join_project_response_message_id: response.message_id,
|
||||||
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
|
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
|
||||||
_maintain_workspace_config: Self::maintain_workspace_config(languages.clone(), cx),
|
_maintain_workspace_config: Self::maintain_workspace_config(cx),
|
||||||
languages,
|
languages,
|
||||||
user_store: user_store.clone(),
|
user_store: user_store.clone(),
|
||||||
fs,
|
fs,
|
||||||
@ -2472,35 +2477,42 @@ impl Project {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maintain_workspace_config(
|
fn maintain_workspace_config(cx: &mut ModelContext<Project>) -> Task<()> {
|
||||||
languages: Arc<LanguageRegistry>,
|
|
||||||
cx: &mut ModelContext<Project>,
|
|
||||||
) -> Task<()> {
|
|
||||||
let (mut settings_changed_tx, mut settings_changed_rx) = watch::channel();
|
let (mut settings_changed_tx, mut settings_changed_rx) = watch::channel();
|
||||||
let _ = postage::stream::Stream::try_recv(&mut settings_changed_rx);
|
let _ = postage::stream::Stream::try_recv(&mut settings_changed_rx);
|
||||||
|
|
||||||
let settings_observation = cx.observe_global::<SettingsStore, _>(move |_, _| {
|
let settings_observation = cx.observe_global::<SettingsStore, _>(move |_, _| {
|
||||||
*settings_changed_tx.borrow_mut() = ();
|
*settings_changed_tx.borrow_mut() = ();
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
cx.spawn_weak(|this, mut cx| async move {
|
||||||
while let Some(_) = settings_changed_rx.next().await {
|
while let Some(_) = settings_changed_rx.next().await {
|
||||||
let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await;
|
let Some(this) = this.upgrade(&cx) else {
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
|
||||||
this.read_with(&cx, |this, _| {
|
|
||||||
for server_state in this.language_servers.values() {
|
|
||||||
if let LanguageServerState::Running { server, .. } = server_state {
|
|
||||||
server
|
|
||||||
.notify::<lsp::notification::DidChangeConfiguration>(
|
|
||||||
lsp::DidChangeConfigurationParams {
|
|
||||||
settings: workspace_config.clone(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
break;
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
let servers: Vec<_> = this.read_with(&cx, |this, _| {
|
||||||
|
this.language_servers
|
||||||
|
.values()
|
||||||
|
.filter_map(|state| match state {
|
||||||
|
LanguageServerState::Starting(_) => None,
|
||||||
|
LanguageServerState::Running {
|
||||||
|
adapter, server, ..
|
||||||
|
} => Some((adapter.clone(), server.clone())),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
|
for (adapter, server) in servers {
|
||||||
|
let workspace_config =
|
||||||
|
cx.update(|cx| adapter.workspace_configuration(cx)).await;
|
||||||
|
server
|
||||||
|
.notify::<lsp::notification::DidChangeConfiguration>(
|
||||||
|
lsp::DidChangeConfigurationParams {
|
||||||
|
settings: workspace_config.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2615,7 +2627,6 @@ impl Project {
|
|||||||
let state = LanguageServerState::Starting({
|
let state = LanguageServerState::Starting({
|
||||||
let adapter = adapter.clone();
|
let adapter = adapter.clone();
|
||||||
let server_name = adapter.name.0.clone();
|
let server_name = adapter.name.0.clone();
|
||||||
let languages = self.languages.clone();
|
|
||||||
let language = language.clone();
|
let language = language.clone();
|
||||||
let key = key.clone();
|
let key = key.clone();
|
||||||
|
|
||||||
@ -2625,7 +2636,6 @@ impl Project {
|
|||||||
initialization_options,
|
initialization_options,
|
||||||
pending_server,
|
pending_server,
|
||||||
adapter.clone(),
|
adapter.clone(),
|
||||||
languages,
|
|
||||||
language.clone(),
|
language.clone(),
|
||||||
server_id,
|
server_id,
|
||||||
key,
|
key,
|
||||||
@ -2729,7 +2739,6 @@ impl Project {
|
|||||||
initialization_options: Option<serde_json::Value>,
|
initialization_options: Option<serde_json::Value>,
|
||||||
pending_server: PendingLanguageServer,
|
pending_server: PendingLanguageServer,
|
||||||
adapter: Arc<CachedLspAdapter>,
|
adapter: Arc<CachedLspAdapter>,
|
||||||
languages: Arc<LanguageRegistry>,
|
|
||||||
language: Arc<Language>,
|
language: Arc<Language>,
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
key: (WorktreeId, LanguageServerName),
|
key: (WorktreeId, LanguageServerName),
|
||||||
@ -2740,7 +2749,6 @@ impl Project {
|
|||||||
initialization_options,
|
initialization_options,
|
||||||
pending_server,
|
pending_server,
|
||||||
adapter.clone(),
|
adapter.clone(),
|
||||||
languages,
|
|
||||||
server_id,
|
server_id,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
@ -2773,16 +2781,13 @@ impl Project {
|
|||||||
initialization_options: Option<serde_json::Value>,
|
initialization_options: Option<serde_json::Value>,
|
||||||
pending_server: PendingLanguageServer,
|
pending_server: PendingLanguageServer,
|
||||||
adapter: Arc<CachedLspAdapter>,
|
adapter: Arc<CachedLspAdapter>,
|
||||||
languages: Arc<LanguageRegistry>,
|
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
) -> Result<Option<Arc<LanguageServer>>> {
|
) -> Result<Option<Arc<LanguageServer>>> {
|
||||||
let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await;
|
let workspace_config = cx.update(|cx| adapter.workspace_configuration(cx)).await;
|
||||||
let language_server = match pending_server.task.await? {
|
let language_server = match pending_server.task.await? {
|
||||||
Some(server) => server.initialize(initialization_options).await?,
|
Some(server) => server,
|
||||||
None => {
|
None => return Ok(None),
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
@ -2821,12 +2826,12 @@ impl Project {
|
|||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_request::<lsp::request::WorkspaceConfiguration, _, _>({
|
.on_request::<lsp::request::WorkspaceConfiguration, _, _>({
|
||||||
let languages = languages.clone();
|
let adapter = adapter.clone();
|
||||||
move |params, mut cx| {
|
move |params, mut cx| {
|
||||||
let languages = languages.clone();
|
let adapter = adapter.clone();
|
||||||
async move {
|
async move {
|
||||||
let workspace_config =
|
let workspace_config =
|
||||||
cx.update(|cx| languages.workspace_configuration(cx)).await;
|
cx.update(|cx| adapter.workspace_configuration(cx)).await;
|
||||||
Ok(params
|
Ok(params
|
||||||
.items
|
.items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -2932,6 +2937,8 @@ impl Project {
|
|||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
let language_server = language_server.initialize(initialization_options).await?;
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
.notify::<lsp::notification::DidChangeConfiguration>(
|
.notify::<lsp::notification::DidChangeConfiguration>(
|
||||||
lsp::DidChangeConfigurationParams {
|
lsp::DidChangeConfigurationParams {
|
||||||
@ -3892,7 +3899,7 @@ impl Project {
|
|||||||
let file = File::from_dyn(buffer.file())?;
|
let file = File::from_dyn(buffer.file())?;
|
||||||
let buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
|
let buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
|
||||||
let server = self
|
let server = self
|
||||||
.primary_language_servers_for_buffer(buffer, cx)
|
.primary_language_server_for_buffer(buffer, cx)
|
||||||
.map(|s| s.1.clone());
|
.map(|s| s.1.clone());
|
||||||
Some((buffer_handle, buffer_abs_path, server))
|
Some((buffer_handle, buffer_abs_path, server))
|
||||||
})
|
})
|
||||||
@ -4197,7 +4204,12 @@ impl Project {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Vec<LocationLink>>> {
|
) -> Task<Result<Vec<LocationLink>>> {
|
||||||
let position = position.to_point_utf16(buffer.read(cx));
|
let position = position.to_point_utf16(buffer.read(cx));
|
||||||
self.request_lsp(buffer.clone(), GetDefinition { position }, cx)
|
self.request_lsp(
|
||||||
|
buffer.clone(),
|
||||||
|
LanguageServerToQuery::Primary,
|
||||||
|
GetDefinition { position },
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn type_definition<T: ToPointUtf16>(
|
pub fn type_definition<T: ToPointUtf16>(
|
||||||
@ -4207,7 +4219,12 @@ impl Project {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Vec<LocationLink>>> {
|
) -> Task<Result<Vec<LocationLink>>> {
|
||||||
let position = position.to_point_utf16(buffer.read(cx));
|
let position = position.to_point_utf16(buffer.read(cx));
|
||||||
self.request_lsp(buffer.clone(), GetTypeDefinition { position }, cx)
|
self.request_lsp(
|
||||||
|
buffer.clone(),
|
||||||
|
LanguageServerToQuery::Primary,
|
||||||
|
GetTypeDefinition { position },
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn references<T: ToPointUtf16>(
|
pub fn references<T: ToPointUtf16>(
|
||||||
@ -4217,7 +4234,12 @@ impl Project {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Vec<Location>>> {
|
) -> Task<Result<Vec<Location>>> {
|
||||||
let position = position.to_point_utf16(buffer.read(cx));
|
let position = position.to_point_utf16(buffer.read(cx));
|
||||||
self.request_lsp(buffer.clone(), GetReferences { position }, cx)
|
self.request_lsp(
|
||||||
|
buffer.clone(),
|
||||||
|
LanguageServerToQuery::Primary,
|
||||||
|
GetReferences { position },
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn document_highlights<T: ToPointUtf16>(
|
pub fn document_highlights<T: ToPointUtf16>(
|
||||||
@ -4227,7 +4249,12 @@ impl Project {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Vec<DocumentHighlight>>> {
|
) -> Task<Result<Vec<DocumentHighlight>>> {
|
||||||
let position = position.to_point_utf16(buffer.read(cx));
|
let position = position.to_point_utf16(buffer.read(cx));
|
||||||
self.request_lsp(buffer.clone(), GetDocumentHighlights { position }, cx)
|
self.request_lsp(
|
||||||
|
buffer.clone(),
|
||||||
|
LanguageServerToQuery::Primary,
|
||||||
|
GetDocumentHighlights { position },
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
|
pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
|
||||||
@ -4455,17 +4482,66 @@ impl Project {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Option<Hover>>> {
|
) -> Task<Result<Option<Hover>>> {
|
||||||
let position = position.to_point_utf16(buffer.read(cx));
|
let position = position.to_point_utf16(buffer.read(cx));
|
||||||
self.request_lsp(buffer.clone(), GetHover { position }, cx)
|
self.request_lsp(
|
||||||
|
buffer.clone(),
|
||||||
|
LanguageServerToQuery::Primary,
|
||||||
|
GetHover { position },
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn completions<T: ToPointUtf16>(
|
pub fn completions<T: ToOffset + ToPointUtf16>(
|
||||||
&self,
|
&self,
|
||||||
buffer: &ModelHandle<Buffer>,
|
buffer: &ModelHandle<Buffer>,
|
||||||
position: T,
|
position: T,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Vec<Completion>>> {
|
) -> Task<Result<Vec<Completion>>> {
|
||||||
let position = position.to_point_utf16(buffer.read(cx));
|
let position = position.to_point_utf16(buffer.read(cx));
|
||||||
self.request_lsp(buffer.clone(), GetCompletions { position }, cx)
|
if self.is_local() {
|
||||||
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
|
let offset = position.to_offset(&snapshot);
|
||||||
|
let scope = snapshot.language_scope_at(offset);
|
||||||
|
|
||||||
|
let server_ids: Vec<_> = self
|
||||||
|
.language_servers_for_buffer(buffer.read(cx), cx)
|
||||||
|
.filter(|(_, server)| server.capabilities().completion_provider.is_some())
|
||||||
|
.filter(|(adapter, _)| {
|
||||||
|
scope
|
||||||
|
.as_ref()
|
||||||
|
.map(|scope| scope.language_allowed(&adapter.name))
|
||||||
|
.unwrap_or(true)
|
||||||
|
})
|
||||||
|
.map(|(_, server)| server.server_id())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let buffer = buffer.clone();
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
let mut tasks = Vec::with_capacity(server_ids.len());
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
for server_id in server_ids {
|
||||||
|
tasks.push(this.request_lsp(
|
||||||
|
buffer.clone(),
|
||||||
|
LanguageServerToQuery::Other(server_id),
|
||||||
|
GetCompletions { position },
|
||||||
|
cx,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut completions = Vec::new();
|
||||||
|
for task in tasks {
|
||||||
|
if let Ok(new_completions) = task.await {
|
||||||
|
completions.extend_from_slice(&new_completions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(completions)
|
||||||
|
})
|
||||||
|
} else if let Some(project_id) = self.remote_id() {
|
||||||
|
self.send_lsp_proto_request(buffer.clone(), project_id, GetCompletions { position }, cx)
|
||||||
|
} else {
|
||||||
|
Task::ready(Ok(Default::default()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_additional_edits_for_completion(
|
pub fn apply_additional_edits_for_completion(
|
||||||
@ -4479,7 +4555,8 @@ impl Project {
|
|||||||
let buffer_id = buffer.remote_id();
|
let buffer_id = buffer.remote_id();
|
||||||
|
|
||||||
if self.is_local() {
|
if self.is_local() {
|
||||||
let lang_server = match self.primary_language_servers_for_buffer(buffer, cx) {
|
let server_id = completion.server_id;
|
||||||
|
let lang_server = match self.language_server_for_buffer(buffer, server_id, cx) {
|
||||||
Some((_, server)) => server.clone(),
|
Some((_, server)) => server.clone(),
|
||||||
_ => return Task::ready(Ok(Default::default())),
|
_ => return Task::ready(Ok(Default::default())),
|
||||||
};
|
};
|
||||||
@ -4586,7 +4663,12 @@ impl Project {
|
|||||||
) -> Task<Result<Vec<CodeAction>>> {
|
) -> Task<Result<Vec<CodeAction>>> {
|
||||||
let buffer = buffer_handle.read(cx);
|
let buffer = buffer_handle.read(cx);
|
||||||
let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
|
let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
|
||||||
self.request_lsp(buffer_handle.clone(), GetCodeActions { range }, cx)
|
self.request_lsp(
|
||||||
|
buffer_handle.clone(),
|
||||||
|
LanguageServerToQuery::Primary,
|
||||||
|
GetCodeActions { range },
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_code_action(
|
pub fn apply_code_action(
|
||||||
@ -4942,7 +5024,12 @@ impl Project {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Option<Range<Anchor>>>> {
|
) -> Task<Result<Option<Range<Anchor>>>> {
|
||||||
let position = position.to_point_utf16(buffer.read(cx));
|
let position = position.to_point_utf16(buffer.read(cx));
|
||||||
self.request_lsp(buffer, PrepareRename { position }, cx)
|
self.request_lsp(
|
||||||
|
buffer,
|
||||||
|
LanguageServerToQuery::Primary,
|
||||||
|
PrepareRename { position },
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn perform_rename<T: ToPointUtf16>(
|
pub fn perform_rename<T: ToPointUtf16>(
|
||||||
@ -4956,6 +5043,7 @@ impl Project {
|
|||||||
let position = position.to_point_utf16(buffer.read(cx));
|
let position = position.to_point_utf16(buffer.read(cx));
|
||||||
self.request_lsp(
|
self.request_lsp(
|
||||||
buffer,
|
buffer,
|
||||||
|
LanguageServerToQuery::Primary,
|
||||||
PerformRename {
|
PerformRename {
|
||||||
position,
|
position,
|
||||||
new_name,
|
new_name,
|
||||||
@ -4983,6 +5071,7 @@ impl Project {
|
|||||||
});
|
});
|
||||||
self.request_lsp(
|
self.request_lsp(
|
||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
|
LanguageServerToQuery::Primary,
|
||||||
OnTypeFormatting {
|
OnTypeFormatting {
|
||||||
position,
|
position,
|
||||||
trigger,
|
trigger,
|
||||||
@ -5008,7 +5097,12 @@ impl Project {
|
|||||||
let lsp_request = InlayHints { range };
|
let lsp_request = InlayHints { range };
|
||||||
|
|
||||||
if self.is_local() {
|
if self.is_local() {
|
||||||
let lsp_request_task = self.request_lsp(buffer_handle.clone(), lsp_request, cx);
|
let lsp_request_task = self.request_lsp(
|
||||||
|
buffer_handle.clone(),
|
||||||
|
LanguageServerToQuery::Primary,
|
||||||
|
lsp_request,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
buffer_handle
|
buffer_handle
|
||||||
.update(&mut cx, |buffer, _| {
|
.update(&mut cx, |buffer, _| {
|
||||||
@ -5441,10 +5535,10 @@ impl Project {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Wire this up to allow selecting a server?
|
|
||||||
fn request_lsp<R: LspCommand>(
|
fn request_lsp<R: LspCommand>(
|
||||||
&self,
|
&self,
|
||||||
buffer_handle: ModelHandle<Buffer>,
|
buffer_handle: ModelHandle<Buffer>,
|
||||||
|
server: LanguageServerToQuery,
|
||||||
request: R,
|
request: R,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<R::Response>>
|
) -> Task<Result<R::Response>>
|
||||||
@ -5453,11 +5547,19 @@ impl Project {
|
|||||||
{
|
{
|
||||||
let buffer = buffer_handle.read(cx);
|
let buffer = buffer_handle.read(cx);
|
||||||
if self.is_local() {
|
if self.is_local() {
|
||||||
|
let language_server = match server {
|
||||||
|
LanguageServerToQuery::Primary => {
|
||||||
|
match self.primary_language_server_for_buffer(buffer, cx) {
|
||||||
|
Some((_, server)) => Some(Arc::clone(server)),
|
||||||
|
None => return Task::ready(Ok(Default::default())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LanguageServerToQuery::Other(id) => self
|
||||||
|
.language_server_for_buffer(buffer, id, cx)
|
||||||
|
.map(|(_, server)| Arc::clone(server)),
|
||||||
|
};
|
||||||
let file = File::from_dyn(buffer.file()).and_then(File::as_local);
|
let file = File::from_dyn(buffer.file()).and_then(File::as_local);
|
||||||
if let Some((file, language_server)) = file.zip(
|
if let (Some(file), Some(language_server)) = (file, language_server) {
|
||||||
self.primary_language_servers_for_buffer(buffer, cx)
|
|
||||||
.map(|(_, server)| server.clone()),
|
|
||||||
) {
|
|
||||||
let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx);
|
let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx);
|
||||||
return cx.spawn(|this, cx| async move {
|
return cx.spawn(|this, cx| async move {
|
||||||
if !request.check_capabilities(language_server.capabilities()) {
|
if !request.check_capabilities(language_server.capabilities()) {
|
||||||
@ -5490,31 +5592,40 @@ impl Project {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if let Some(project_id) = self.remote_id() {
|
} else if let Some(project_id) = self.remote_id() {
|
||||||
let rpc = self.client.clone();
|
return self.send_lsp_proto_request(buffer_handle, project_id, request, cx);
|
||||||
let message = request.to_proto(project_id, buffer);
|
|
||||||
return cx.spawn_weak(|this, cx| async move {
|
|
||||||
// Ensure the project is still alive by the time the task
|
|
||||||
// is scheduled.
|
|
||||||
this.upgrade(&cx)
|
|
||||||
.ok_or_else(|| anyhow!("project dropped"))?;
|
|
||||||
|
|
||||||
let response = rpc.request(message).await?;
|
|
||||||
|
|
||||||
let this = this
|
|
||||||
.upgrade(&cx)
|
|
||||||
.ok_or_else(|| anyhow!("project dropped"))?;
|
|
||||||
if this.read_with(&cx, |this, _| this.is_read_only()) {
|
|
||||||
Err(anyhow!("disconnected before completing request"))
|
|
||||||
} else {
|
|
||||||
request
|
|
||||||
.response_from_proto(response, this, buffer_handle, cx)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Task::ready(Ok(Default::default()))
|
Task::ready(Ok(Default::default()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn send_lsp_proto_request<R: LspCommand>(
|
||||||
|
&self,
|
||||||
|
buffer: ModelHandle<Buffer>,
|
||||||
|
project_id: u64,
|
||||||
|
request: R,
|
||||||
|
cx: &mut ModelContext<'_, Project>,
|
||||||
|
) -> Task<anyhow::Result<<R as LspCommand>::Response>> {
|
||||||
|
let rpc = self.client.clone();
|
||||||
|
let message = request.to_proto(project_id, buffer.read(cx));
|
||||||
|
cx.spawn_weak(|this, cx| async move {
|
||||||
|
// Ensure the project is still alive by the time the task
|
||||||
|
// is scheduled.
|
||||||
|
this.upgrade(&cx)
|
||||||
|
.ok_or_else(|| anyhow!("project dropped"))?;
|
||||||
|
let response = rpc.request(message).await?;
|
||||||
|
let this = this
|
||||||
|
.upgrade(&cx)
|
||||||
|
.ok_or_else(|| anyhow!("project dropped"))?;
|
||||||
|
if this.read_with(&cx, |this, _| this.is_read_only()) {
|
||||||
|
Err(anyhow!("disconnected before completing request"))
|
||||||
|
} else {
|
||||||
|
request
|
||||||
|
.response_from_proto(response, this, buffer, cx)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn sort_candidates_and_open_buffers(
|
fn sort_candidates_and_open_buffers(
|
||||||
mut matching_paths_rx: Receiver<SearchMatchCandidate>,
|
mut matching_paths_rx: Receiver<SearchMatchCandidate>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
@ -7150,7 +7261,7 @@ impl Project {
|
|||||||
let buffer_version = buffer_handle.read_with(&cx, |buffer, _| buffer.version());
|
let buffer_version = buffer_handle.read_with(&cx, |buffer, _| buffer.version());
|
||||||
let response = this
|
let response = this
|
||||||
.update(&mut cx, |this, cx| {
|
.update(&mut cx, |this, cx| {
|
||||||
this.request_lsp(buffer_handle, request, cx)
|
this.request_lsp(buffer_handle, LanguageServerToQuery::Primary, request, cx)
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
@ -7867,7 +7978,7 @@ impl Project {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn primary_language_servers_for_buffer(
|
fn primary_language_server_for_buffer(
|
||||||
&self,
|
&self,
|
||||||
buffer: &Buffer,
|
buffer: &Buffer,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
|
@ -2272,7 +2272,18 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
|
|||||||
},
|
},
|
||||||
Some(tree_sitter_typescript::language_typescript()),
|
Some(tree_sitter_typescript::language_typescript()),
|
||||||
);
|
);
|
||||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
let mut fake_language_servers = language
|
||||||
|
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||||
|
capabilities: lsp::ServerCapabilities {
|
||||||
|
completion_provider: Some(lsp::CompletionOptions {
|
||||||
|
trigger_characters: Some(vec![":".to_string()]),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.background());
|
let fs = FakeFs::new(cx.background());
|
||||||
fs.insert_tree(
|
fs.insert_tree(
|
||||||
@ -2358,7 +2369,18 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
|
|||||||
},
|
},
|
||||||
Some(tree_sitter_typescript::language_typescript()),
|
Some(tree_sitter_typescript::language_typescript()),
|
||||||
);
|
);
|
||||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
let mut fake_language_servers = language
|
||||||
|
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||||
|
capabilities: lsp::ServerCapabilities {
|
||||||
|
completion_provider: Some(lsp::CompletionOptions {
|
||||||
|
trigger_characters: Some(vec![":".to_string()]),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.background());
|
let fs = FakeFs::new(cx.background());
|
||||||
fs.insert_tree(
|
fs.insert_tree(
|
||||||
|
@ -225,15 +225,14 @@ impl SearchQuery {
|
|||||||
if self.as_str().is_empty() {
|
if self.as_str().is_empty() {
|
||||||
return Default::default();
|
return Default::default();
|
||||||
}
|
}
|
||||||
let language = buffer.language_at(0);
|
|
||||||
|
let range_offset = subrange.as_ref().map(|r| r.start).unwrap_or(0);
|
||||||
let rope = if let Some(range) = subrange {
|
let rope = if let Some(range) = subrange {
|
||||||
buffer.as_rope().slice(range)
|
buffer.as_rope().slice(range)
|
||||||
} else {
|
} else {
|
||||||
buffer.as_rope().clone()
|
buffer.as_rope().clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let kind = |c| char_kind(language, c);
|
|
||||||
|
|
||||||
let mut matches = Vec::new();
|
let mut matches = Vec::new();
|
||||||
match self {
|
match self {
|
||||||
Self::Text {
|
Self::Text {
|
||||||
@ -249,6 +248,9 @@ impl SearchQuery {
|
|||||||
|
|
||||||
let mat = mat.unwrap();
|
let mat = mat.unwrap();
|
||||||
if *whole_word {
|
if *whole_word {
|
||||||
|
let scope = buffer.language_scope_at(range_offset + mat.start());
|
||||||
|
let kind = |c| char_kind(&scope, c);
|
||||||
|
|
||||||
let prev_kind = rope.reversed_chars_at(mat.start()).next().map(kind);
|
let prev_kind = rope.reversed_chars_at(mat.start()).next().map(kind);
|
||||||
let start_kind = kind(rope.chars_at(mat.start()).next().unwrap());
|
let start_kind = kind(rope.chars_at(mat.start()).next().unwrap());
|
||||||
let end_kind = kind(rope.reversed_chars_at(mat.end()).next().unwrap());
|
let end_kind = kind(rope.reversed_chars_at(mat.end()).next().unwrap());
|
||||||
|
@ -657,7 +657,8 @@ message Completion {
|
|||||||
Anchor old_start = 1;
|
Anchor old_start = 1;
|
||||||
Anchor old_end = 2;
|
Anchor old_end = 2;
|
||||||
string new_text = 3;
|
string new_text = 3;
|
||||||
bytes lsp_completion = 4;
|
uint64 server_id = 4;
|
||||||
|
bytes lsp_completion = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetCodeActions {
|
message GetCodeActions {
|
||||||
|
@ -834,6 +834,9 @@ pub struct AutocompleteStyle {
|
|||||||
pub selected_item: ContainerStyle,
|
pub selected_item: ContainerStyle,
|
||||||
pub hovered_item: ContainerStyle,
|
pub hovered_item: ContainerStyle,
|
||||||
pub match_highlight: HighlightStyle,
|
pub match_highlight: HighlightStyle,
|
||||||
|
pub server_name_container: ContainerStyle,
|
||||||
|
pub server_name_color: Color,
|
||||||
|
pub server_name_size_percent: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, Deserialize, JsonSchema)]
|
#[derive(Clone, Copy, Default, Deserialize, JsonSchema)]
|
||||||
|
@ -589,12 +589,12 @@ pub(crate) fn next_word_start(
|
|||||||
ignore_punctuation: bool,
|
ignore_punctuation: bool,
|
||||||
times: usize,
|
times: usize,
|
||||||
) -> DisplayPoint {
|
) -> DisplayPoint {
|
||||||
let language = map.buffer_snapshot.language_at(point.to_point(map));
|
let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
|
||||||
for _ in 0..times {
|
for _ in 0..times {
|
||||||
let mut crossed_newline = false;
|
let mut crossed_newline = false;
|
||||||
point = movement::find_boundary(map, point, |left, right| {
|
point = movement::find_boundary(map, point, |left, right| {
|
||||||
let left_kind = char_kind(language, left).coerce_punctuation(ignore_punctuation);
|
let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
|
||||||
let right_kind = char_kind(language, right).coerce_punctuation(ignore_punctuation);
|
let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
|
||||||
let at_newline = right == '\n';
|
let at_newline = right == '\n';
|
||||||
|
|
||||||
let found = (left_kind != right_kind && right_kind != CharKind::Whitespace)
|
let found = (left_kind != right_kind && right_kind != CharKind::Whitespace)
|
||||||
@ -614,12 +614,12 @@ fn next_word_end(
|
|||||||
ignore_punctuation: bool,
|
ignore_punctuation: bool,
|
||||||
times: usize,
|
times: usize,
|
||||||
) -> DisplayPoint {
|
) -> DisplayPoint {
|
||||||
let language = map.buffer_snapshot.language_at(point.to_point(map));
|
let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
|
||||||
for _ in 0..times {
|
for _ in 0..times {
|
||||||
*point.column_mut() += 1;
|
*point.column_mut() += 1;
|
||||||
point = movement::find_boundary(map, point, |left, right| {
|
point = movement::find_boundary(map, point, |left, right| {
|
||||||
let left_kind = char_kind(language, left).coerce_punctuation(ignore_punctuation);
|
let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
|
||||||
let right_kind = char_kind(language, right).coerce_punctuation(ignore_punctuation);
|
let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
|
||||||
|
|
||||||
left_kind != right_kind && left_kind != CharKind::Whitespace
|
left_kind != right_kind && left_kind != CharKind::Whitespace
|
||||||
});
|
});
|
||||||
@ -645,13 +645,13 @@ fn previous_word_start(
|
|||||||
ignore_punctuation: bool,
|
ignore_punctuation: bool,
|
||||||
times: usize,
|
times: usize,
|
||||||
) -> DisplayPoint {
|
) -> DisplayPoint {
|
||||||
let language = map.buffer_snapshot.language_at(point.to_point(map));
|
let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
|
||||||
for _ in 0..times {
|
for _ in 0..times {
|
||||||
// This works even though find_preceding_boundary is called for every character in the line containing
|
// This works even though find_preceding_boundary is called for every character in the line containing
|
||||||
// cursor because the newline is checked only once.
|
// cursor because the newline is checked only once.
|
||||||
point = movement::find_preceding_boundary(map, point, |left, right| {
|
point = movement::find_preceding_boundary(map, point, |left, right| {
|
||||||
let left_kind = char_kind(language, left).coerce_punctuation(ignore_punctuation);
|
let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
|
||||||
let right_kind = char_kind(language, right).coerce_punctuation(ignore_punctuation);
|
let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
|
||||||
|
|
||||||
(left_kind != right_kind && !right.is_whitespace()) || left == '\n'
|
(left_kind != right_kind && !right.is_whitespace()) || left == '\n'
|
||||||
});
|
});
|
||||||
@ -665,7 +665,7 @@ fn first_non_whitespace(
|
|||||||
from: DisplayPoint,
|
from: DisplayPoint,
|
||||||
) -> DisplayPoint {
|
) -> DisplayPoint {
|
||||||
let mut last_point = start_of_line(map, display_lines, from);
|
let mut last_point = start_of_line(map, display_lines, from);
|
||||||
let language = map.buffer_snapshot.language_at(from.to_point(map));
|
let scope = map.buffer_snapshot.language_scope_at(from.to_point(map));
|
||||||
for (ch, point) in map.chars_at(last_point) {
|
for (ch, point) in map.chars_at(last_point) {
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
return from;
|
return from;
|
||||||
@ -673,7 +673,7 @@ fn first_non_whitespace(
|
|||||||
|
|
||||||
last_point = point;
|
last_point = point;
|
||||||
|
|
||||||
if char_kind(language, ch) != CharKind::Whitespace {
|
if char_kind(&scope, ch) != CharKind::Whitespace {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,19 +86,19 @@ fn expand_changed_word_selection(
|
|||||||
ignore_punctuation: bool,
|
ignore_punctuation: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if times.is_none() || times.unwrap() == 1 {
|
if times.is_none() || times.unwrap() == 1 {
|
||||||
let language = map
|
let scope = map
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
.language_at(selection.start.to_point(map));
|
.language_scope_at(selection.start.to_point(map));
|
||||||
let in_word = map
|
let in_word = map
|
||||||
.chars_at(selection.head())
|
.chars_at(selection.head())
|
||||||
.next()
|
.next()
|
||||||
.map(|(c, _)| char_kind(language, c) != CharKind::Whitespace)
|
.map(|(c, _)| char_kind(&scope, c) != CharKind::Whitespace)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if in_word {
|
if in_word {
|
||||||
selection.end = movement::find_boundary(map, selection.end, |left, right| {
|
selection.end = movement::find_boundary(map, selection.end, |left, right| {
|
||||||
let left_kind = char_kind(language, left).coerce_punctuation(ignore_punctuation);
|
let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
|
||||||
let right_kind = char_kind(language, right).coerce_punctuation(ignore_punctuation);
|
let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
|
||||||
|
|
||||||
left_kind != right_kind && left_kind != CharKind::Whitespace
|
left_kind != right_kind && left_kind != CharKind::Whitespace
|
||||||
});
|
});
|
||||||
|
@ -177,18 +177,20 @@ fn in_word(
|
|||||||
ignore_punctuation: bool,
|
ignore_punctuation: bool,
|
||||||
) -> Option<Range<DisplayPoint>> {
|
) -> Option<Range<DisplayPoint>> {
|
||||||
// Use motion::right so that we consider the character under the cursor when looking for the start
|
// Use motion::right so that we consider the character under the cursor when looking for the start
|
||||||
let language = map.buffer_snapshot.language_at(relative_to.to_point(map));
|
let scope = map
|
||||||
|
.buffer_snapshot
|
||||||
|
.language_scope_at(relative_to.to_point(map));
|
||||||
let start = movement::find_preceding_boundary_in_line(
|
let start = movement::find_preceding_boundary_in_line(
|
||||||
map,
|
map,
|
||||||
right(map, relative_to, 1),
|
right(map, relative_to, 1),
|
||||||
|left, right| {
|
|left, right| {
|
||||||
char_kind(language, left).coerce_punctuation(ignore_punctuation)
|
char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
|
||||||
!= char_kind(language, right).coerce_punctuation(ignore_punctuation)
|
!= char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let end = movement::find_boundary_in_line(map, relative_to, |left, right| {
|
let end = movement::find_boundary_in_line(map, relative_to, |left, right| {
|
||||||
char_kind(language, left).coerce_punctuation(ignore_punctuation)
|
char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
|
||||||
!= char_kind(language, right).coerce_punctuation(ignore_punctuation)
|
!= char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(start..end)
|
Some(start..end)
|
||||||
@ -211,11 +213,13 @@ fn around_word(
|
|||||||
relative_to: DisplayPoint,
|
relative_to: DisplayPoint,
|
||||||
ignore_punctuation: bool,
|
ignore_punctuation: bool,
|
||||||
) -> Option<Range<DisplayPoint>> {
|
) -> Option<Range<DisplayPoint>> {
|
||||||
let language = map.buffer_snapshot.language_at(relative_to.to_point(map));
|
let scope = map
|
||||||
|
.buffer_snapshot
|
||||||
|
.language_scope_at(relative_to.to_point(map));
|
||||||
let in_word = map
|
let in_word = map
|
||||||
.chars_at(relative_to)
|
.chars_at(relative_to)
|
||||||
.next()
|
.next()
|
||||||
.map(|(c, _)| char_kind(language, c) != CharKind::Whitespace)
|
.map(|(c, _)| char_kind(&scope, c) != CharKind::Whitespace)
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
if in_word {
|
if in_word {
|
||||||
@ -239,21 +243,23 @@ fn around_next_word(
|
|||||||
relative_to: DisplayPoint,
|
relative_to: DisplayPoint,
|
||||||
ignore_punctuation: bool,
|
ignore_punctuation: bool,
|
||||||
) -> Option<Range<DisplayPoint>> {
|
) -> Option<Range<DisplayPoint>> {
|
||||||
let language = map.buffer_snapshot.language_at(relative_to.to_point(map));
|
let scope = map
|
||||||
|
.buffer_snapshot
|
||||||
|
.language_scope_at(relative_to.to_point(map));
|
||||||
// Get the start of the word
|
// Get the start of the word
|
||||||
let start = movement::find_preceding_boundary_in_line(
|
let start = movement::find_preceding_boundary_in_line(
|
||||||
map,
|
map,
|
||||||
right(map, relative_to, 1),
|
right(map, relative_to, 1),
|
||||||
|left, right| {
|
|left, right| {
|
||||||
char_kind(language, left).coerce_punctuation(ignore_punctuation)
|
char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
|
||||||
!= char_kind(language, right).coerce_punctuation(ignore_punctuation)
|
!= char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut word_found = false;
|
let mut word_found = false;
|
||||||
let end = movement::find_boundary(map, relative_to, |left, right| {
|
let end = movement::find_boundary(map, relative_to, |left, right| {
|
||||||
let left_kind = char_kind(language, left).coerce_punctuation(ignore_punctuation);
|
let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
|
||||||
let right_kind = char_kind(language, right).coerce_punctuation(ignore_punctuation);
|
let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
|
||||||
|
|
||||||
let found = (word_found && left_kind != right_kind) || right == '\n' && left == '\n';
|
let found = (word_found && left_kind != right_kind) || right == '\n' && left == '\n';
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ use std::{borrow::Cow, str, sync::Arc};
|
|||||||
use util::asset_str;
|
use util::asset_str;
|
||||||
|
|
||||||
mod c;
|
mod c;
|
||||||
|
mod css;
|
||||||
mod elixir;
|
mod elixir;
|
||||||
mod go;
|
mod go;
|
||||||
mod html;
|
mod html;
|
||||||
@ -18,6 +19,7 @@ mod python;
|
|||||||
mod ruby;
|
mod ruby;
|
||||||
mod rust;
|
mod rust;
|
||||||
mod svelte;
|
mod svelte;
|
||||||
|
mod tailwind;
|
||||||
mod typescript;
|
mod typescript;
|
||||||
mod yaml;
|
mod yaml;
|
||||||
|
|
||||||
@ -51,7 +53,14 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: Arc<NodeRuntime>) {
|
|||||||
tree_sitter_cpp::language(),
|
tree_sitter_cpp::language(),
|
||||||
vec![Arc::new(c::CLspAdapter)],
|
vec![Arc::new(c::CLspAdapter)],
|
||||||
);
|
);
|
||||||
language("css", tree_sitter_css::language(), vec![]);
|
language(
|
||||||
|
"css",
|
||||||
|
tree_sitter_css::language(),
|
||||||
|
vec![
|
||||||
|
Arc::new(css::CssLspAdapter::new(node_runtime.clone())),
|
||||||
|
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||||
|
],
|
||||||
|
);
|
||||||
language(
|
language(
|
||||||
"elixir",
|
"elixir",
|
||||||
tree_sitter_elixir::language(),
|
tree_sitter_elixir::language(),
|
||||||
@ -95,6 +104,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: Arc<NodeRuntime>) {
|
|||||||
vec![
|
vec![
|
||||||
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
|
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
|
||||||
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
|
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
|
||||||
|
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
language(
|
language(
|
||||||
@ -111,12 +121,16 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: Arc<NodeRuntime>) {
|
|||||||
vec![
|
vec![
|
||||||
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
|
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
|
||||||
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
|
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
|
||||||
|
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
language(
|
language(
|
||||||
"html",
|
"html",
|
||||||
tree_sitter_html::language(),
|
tree_sitter_html::language(),
|
||||||
vec![Arc::new(html::HtmlLspAdapter::new(node_runtime.clone()))],
|
vec![
|
||||||
|
Arc::new(html::HtmlLspAdapter::new(node_runtime.clone())),
|
||||||
|
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
language(
|
language(
|
||||||
"ruby",
|
"ruby",
|
||||||
|
@ -19,6 +19,10 @@ impl super::LspAdapter for CLspAdapter {
|
|||||||
LanguageServerName("clangd".into())
|
LanguageServerName("clangd".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"clangd"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
delegate: &dyn LspAdapterDelegate,
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
130
crates/zed/src/languages/css.rs
Normal file
130
crates/zed/src/languages/css.rs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||||
|
use lsp::LanguageServerBinary;
|
||||||
|
use node_runtime::NodeRuntime;
|
||||||
|
use serde_json::json;
|
||||||
|
use smol::fs;
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
ffi::OsString,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
|
const SERVER_PATH: &'static str =
|
||||||
|
"node_modules/vscode-langservers-extracted/bin/vscode-css-language-server";
|
||||||
|
|
||||||
|
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||||
|
vec![server_path.into(), "--stdio".into()]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CssLspAdapter {
|
||||||
|
node: Arc<NodeRuntime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CssLspAdapter {
|
||||||
|
pub fn new(node: Arc<NodeRuntime>) -> Self {
|
||||||
|
CssLspAdapter { node }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LspAdapter for CssLspAdapter {
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("vscode-css-language-server".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"css"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||||
|
Ok(Box::new(
|
||||||
|
self.node
|
||||||
|
.npm_package_latest_version("vscode-langservers-extracted")
|
||||||
|
.await?,
|
||||||
|
) as Box<_>)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
version: Box<dyn 'static + Send + Any>,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
let version = version.downcast::<String>().unwrap();
|
||||||
|
let server_path = container_dir.join(SERVER_PATH);
|
||||||
|
|
||||||
|
if fs::metadata(&server_path).await.is_err() {
|
||||||
|
self.node
|
||||||
|
.npm_install_packages(
|
||||||
|
&container_dir,
|
||||||
|
[("vscode-langservers-extracted", version.as_str())],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: self.node.binary_path().await?,
|
||||||
|
arguments: server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir, &self.node).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir, &self.node).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||||
|
Some(json!({
|
||||||
|
"provideFormatter": true
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_cached_server_binary(
|
||||||
|
container_dir: PathBuf,
|
||||||
|
node: &NodeRuntime,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
(|| async move {
|
||||||
|
let mut last_version_dir = None;
|
||||||
|
let mut entries = fs::read_dir(&container_dir).await?;
|
||||||
|
while let Some(entry) = entries.next().await {
|
||||||
|
let entry = entry?;
|
||||||
|
if entry.file_type().await?.is_dir() {
|
||||||
|
last_version_dir = Some(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||||
|
let server_path = last_version_dir.join(SERVER_PATH);
|
||||||
|
if server_path.exists() {
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: node.binary_path().await?,
|
||||||
|
arguments: server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(
|
||||||
|
"missing executable in directory {:?}",
|
||||||
|
last_version_dir
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
}
|
@ -8,3 +8,4 @@ brackets = [
|
|||||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
|
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
|
||||||
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
||||||
]
|
]
|
||||||
|
word_characters = ["-"]
|
||||||
|
@ -27,6 +27,10 @@ impl LspAdapter for ElixirLspAdapter {
|
|||||||
LanguageServerName("elixir-ls".into())
|
LanguageServerName("elixir-ls".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"elixir-ls"
|
||||||
|
}
|
||||||
|
|
||||||
fn will_start_server(
|
fn will_start_server(
|
||||||
&self,
|
&self,
|
||||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||||
|
@ -37,6 +37,10 @@ impl super::LspAdapter for GoLspAdapter {
|
|||||||
LanguageServerName("gopls".into())
|
LanguageServerName("gopls".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"gopls"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
delegate: &dyn LspAdapterDelegate,
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
@ -37,6 +37,10 @@ impl LspAdapter for HtmlLspAdapter {
|
|||||||
LanguageServerName("vscode-html-language-server".into())
|
LanguageServerName("vscode-html-language-server".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"html"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn LspAdapterDelegate,
|
_: &dyn LspAdapterDelegate,
|
||||||
|
@ -10,3 +10,4 @@ brackets = [
|
|||||||
{ start = "<", end = ">", close = true, newline = true, not_in = ["comment", "string"] },
|
{ start = "<", end = ">", close = true, newline = true, not_in = ["comment", "string"] },
|
||||||
{ start = "!--", end = " --", close = true, newline = false, not_in = ["comment", "string"] },
|
{ start = "!--", end = " --", close = true, newline = false, not_in = ["comment", "string"] },
|
||||||
]
|
]
|
||||||
|
word_characters = ["-"]
|
||||||
|
@ -14,7 +14,12 @@ brackets = [
|
|||||||
{ start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
|
{ start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
|
||||||
]
|
]
|
||||||
word_characters = ["$", "#"]
|
word_characters = ["$", "#"]
|
||||||
|
scope_opt_in_language_servers = ["tailwindcss-language-server"]
|
||||||
|
|
||||||
[overrides.element]
|
[overrides.element]
|
||||||
line_comment = { remove = true }
|
line_comment = { remove = true }
|
||||||
block_comment = ["{/* ", " */}"]
|
block_comment = ["{/* ", " */}"]
|
||||||
|
|
||||||
|
[overrides.string]
|
||||||
|
word_characters = ["-"]
|
||||||
|
opt_into_language_servers = ["tailwindcss-language-server"]
|
||||||
|
@ -43,6 +43,10 @@ impl LspAdapter for JsonLspAdapter {
|
|||||||
LanguageServerName("json-language-server".into())
|
LanguageServerName("json-language-server".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"json"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn LspAdapterDelegate,
|
_: &dyn LspAdapterDelegate,
|
||||||
@ -102,7 +106,7 @@ impl LspAdapter for JsonLspAdapter {
|
|||||||
fn workspace_configuration(
|
fn workspace_configuration(
|
||||||
&self,
|
&self,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Option<BoxFuture<'static, serde_json::Value>> {
|
) -> BoxFuture<'static, serde_json::Value> {
|
||||||
let action_names = cx.all_action_names().collect::<Vec<_>>();
|
let action_names = cx.all_action_names().collect::<Vec<_>>();
|
||||||
let staff_mode = cx.is_staff();
|
let staff_mode = cx.is_staff();
|
||||||
let language_names = &self.languages.language_names();
|
let language_names = &self.languages.language_names();
|
||||||
@ -113,29 +117,28 @@ impl LspAdapter for JsonLspAdapter {
|
|||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
Some(
|
|
||||||
future::ready(serde_json::json!({
|
future::ready(serde_json::json!({
|
||||||
"json": {
|
"json": {
|
||||||
"format": {
|
"format": {
|
||||||
"enable": true,
|
"enable": true,
|
||||||
|
},
|
||||||
|
"schemas": [
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
schema_file_match(&paths::SETTINGS),
|
||||||
|
&*paths::LOCAL_SETTINGS_RELATIVE_PATH,
|
||||||
|
],
|
||||||
|
"schema": settings_schema,
|
||||||
},
|
},
|
||||||
"schemas": [
|
{
|
||||||
{
|
"fileMatch": [schema_file_match(&paths::KEYMAP)],
|
||||||
"fileMatch": [
|
"schema": KeymapFile::generate_json_schema(&action_names),
|
||||||
schema_file_match(&paths::SETTINGS),
|
}
|
||||||
&*paths::LOCAL_SETTINGS_RELATIVE_PATH,
|
]
|
||||||
],
|
}
|
||||||
"schema": settings_schema,
|
}))
|
||||||
},
|
.boxed()
|
||||||
{
|
|
||||||
"fileMatch": [schema_file_match(&paths::KEYMAP)],
|
|
||||||
"schema": KeymapFile::generate_json_schema(&action_names),
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn language_ids(&self) -> HashMap<String, String> {
|
async fn language_ids(&self) -> HashMap<String, String> {
|
||||||
|
@ -70,6 +70,10 @@ impl LspAdapter for PluginLspAdapter {
|
|||||||
LanguageServerName(name.into())
|
LanguageServerName(name.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"PluginLspAdapter"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn LspAdapterDelegate,
|
_: &dyn LspAdapterDelegate,
|
||||||
|
@ -22,6 +22,10 @@ impl super::LspAdapter for LuaLspAdapter {
|
|||||||
LanguageServerName("lua-language-server".into())
|
LanguageServerName("lua-language-server".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"lua"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
delegate: &dyn LspAdapterDelegate,
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
@ -41,6 +41,10 @@ impl LspAdapter for IntelephenseLspAdapter {
|
|||||||
LanguageServerName("intelephense".into())
|
LanguageServerName("intelephense".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"php"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_delegate: &dyn LspAdapterDelegate,
|
_delegate: &dyn LspAdapterDelegate,
|
||||||
|
@ -35,6 +35,10 @@ impl LspAdapter for PythonLspAdapter {
|
|||||||
LanguageServerName("pyright".into())
|
LanguageServerName("pyright".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"pyright"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn LspAdapterDelegate,
|
_: &dyn LspAdapterDelegate,
|
||||||
|
@ -12,6 +12,10 @@ impl LspAdapter for RubyLanguageServer {
|
|||||||
LanguageServerName("solargraph".into())
|
LanguageServerName("solargraph".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"solargraph"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn LspAdapterDelegate,
|
_: &dyn LspAdapterDelegate,
|
||||||
|
@ -22,6 +22,10 @@ impl LspAdapter for RustLspAdapter {
|
|||||||
LanguageServerName("rust-analyzer".into())
|
LanguageServerName("rust-analyzer".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"rust"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
delegate: &dyn LspAdapterDelegate,
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
@ -36,6 +36,10 @@ impl LspAdapter for SvelteLspAdapter {
|
|||||||
LanguageServerName("svelte-language-server".into())
|
LanguageServerName("svelte-language-server".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"svelte"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn LspAdapterDelegate,
|
_: &dyn LspAdapterDelegate,
|
||||||
|
161
crates/zed/src/languages/tailwind.rs
Normal file
161
crates/zed/src/languages/tailwind.rs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use collections::HashMap;
|
||||||
|
use futures::{
|
||||||
|
future::{self, BoxFuture},
|
||||||
|
FutureExt, StreamExt,
|
||||||
|
};
|
||||||
|
use gpui::AppContext;
|
||||||
|
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||||
|
use lsp::LanguageServerBinary;
|
||||||
|
use node_runtime::NodeRuntime;
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use smol::fs;
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
ffi::OsString,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
|
const SERVER_PATH: &'static str = "node_modules/.bin/tailwindcss-language-server";
|
||||||
|
|
||||||
|
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||||
|
vec![server_path.into(), "--stdio".into()]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TailwindLspAdapter {
|
||||||
|
node: Arc<NodeRuntime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TailwindLspAdapter {
|
||||||
|
pub fn new(node: Arc<NodeRuntime>) -> Self {
|
||||||
|
TailwindLspAdapter { node }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LspAdapter for TailwindLspAdapter {
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("tailwindcss-language-server".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"tailwind"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||||
|
Ok(Box::new(
|
||||||
|
self.node
|
||||||
|
.npm_package_latest_version("@tailwindcss/language-server")
|
||||||
|
.await?,
|
||||||
|
) as Box<_>)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
version: Box<dyn 'static + Send + Any>,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
let version = version.downcast::<String>().unwrap();
|
||||||
|
let server_path = container_dir.join(SERVER_PATH);
|
||||||
|
|
||||||
|
if fs::metadata(&server_path).await.is_err() {
|
||||||
|
self.node
|
||||||
|
.npm_install_packages(
|
||||||
|
&container_dir,
|
||||||
|
[("@tailwindcss/language-server", version.as_str())],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: self.node.binary_path().await?,
|
||||||
|
arguments: server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir, &self.node).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir, &self.node).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||||
|
Some(json!({
|
||||||
|
"provideFormatter": true,
|
||||||
|
"userLanguages": {
|
||||||
|
"html": "html",
|
||||||
|
"css": "css",
|
||||||
|
"javascript": "javascript",
|
||||||
|
"typescriptreact": "typescriptreact",
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
|
||||||
|
future::ready(json!({
|
||||||
|
"tailwindCSS": {
|
||||||
|
"emmetCompletions": true,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn language_ids(&self) -> HashMap<String, String> {
|
||||||
|
HashMap::from_iter(
|
||||||
|
[
|
||||||
|
("HTML".to_string(), "html".to_string()),
|
||||||
|
("CSS".to_string(), "css".to_string()),
|
||||||
|
("JavaScript".to_string(), "javascript".to_string()),
|
||||||
|
("TSX".to_string(), "typescriptreact".to_string()),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_cached_server_binary(
|
||||||
|
container_dir: PathBuf,
|
||||||
|
node: &NodeRuntime,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
(|| async move {
|
||||||
|
let mut last_version_dir = None;
|
||||||
|
let mut entries = fs::read_dir(&container_dir).await?;
|
||||||
|
while let Some(entry) = entries.next().await {
|
||||||
|
let entry = entry?;
|
||||||
|
if entry.file_type().await?.is_dir() {
|
||||||
|
last_version_dir = Some(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||||
|
let server_path = last_version_dir.join(SERVER_PATH);
|
||||||
|
if server_path.exists() {
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: node.binary_path().await?,
|
||||||
|
arguments: server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(
|
||||||
|
"missing executable in directory {:?}",
|
||||||
|
last_version_dir
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
}
|
@ -13,7 +13,12 @@ brackets = [
|
|||||||
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
||||||
]
|
]
|
||||||
word_characters = ["#", "$"]
|
word_characters = ["#", "$"]
|
||||||
|
scope_opt_in_language_servers = ["tailwindcss-language-server"]
|
||||||
|
|
||||||
[overrides.element]
|
[overrides.element]
|
||||||
line_comment = { remove = true }
|
line_comment = { remove = true }
|
||||||
block_comment = ["{/* ", " */}"]
|
block_comment = ["{/* ", " */}"]
|
||||||
|
|
||||||
|
[overrides.string]
|
||||||
|
word_characters = ["-"]
|
||||||
|
opt_into_language_servers = ["tailwindcss-language-server"]
|
||||||
|
@ -56,6 +56,10 @@ impl LspAdapter for TypeScriptLspAdapter {
|
|||||||
LanguageServerName("typescript-language-server".into())
|
LanguageServerName("typescript-language-server".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"tsserver"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn LspAdapterDelegate,
|
_: &dyn LspAdapterDelegate,
|
||||||
@ -202,24 +206,26 @@ impl EsLintLspAdapter {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl LspAdapter for EsLintLspAdapter {
|
impl LspAdapter for EsLintLspAdapter {
|
||||||
fn workspace_configuration(&self, _: &mut AppContext) -> Option<BoxFuture<'static, Value>> {
|
fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
|
||||||
Some(
|
future::ready(json!({
|
||||||
future::ready(json!({
|
"": {
|
||||||
"": {
|
"validate": "on",
|
||||||
"validate": "on",
|
"rulesCustomizations": [],
|
||||||
"rulesCustomizations": [],
|
"run": "onType",
|
||||||
"run": "onType",
|
"nodePath": null,
|
||||||
"nodePath": null,
|
}
|
||||||
}
|
}))
|
||||||
}))
|
.boxed()
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn name(&self) -> LanguageServerName {
|
async fn name(&self) -> LanguageServerName {
|
||||||
LanguageServerName("eslint".into())
|
LanguageServerName("eslint".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"eslint"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
delegate: &dyn LspAdapterDelegate,
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
@ -40,6 +40,10 @@ impl LspAdapter for YamlLspAdapter {
|
|||||||
LanguageServerName("yaml-language-server".into())
|
LanguageServerName("yaml-language-server".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"yaml"
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn LspAdapterDelegate,
|
_: &dyn LspAdapterDelegate,
|
||||||
@ -86,21 +90,20 @@ impl LspAdapter for YamlLspAdapter {
|
|||||||
) -> Option<LanguageServerBinary> {
|
) -> Option<LanguageServerBinary> {
|
||||||
get_cached_server_binary(container_dir, &self.node).await
|
get_cached_server_binary(container_dir, &self.node).await
|
||||||
}
|
}
|
||||||
fn workspace_configuration(&self, cx: &mut AppContext) -> Option<BoxFuture<'static, Value>> {
|
fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
|
||||||
let tab_size = all_language_settings(None, cx)
|
let tab_size = all_language_settings(None, cx)
|
||||||
.language(Some("YAML"))
|
.language(Some("YAML"))
|
||||||
.tab_size;
|
.tab_size;
|
||||||
Some(
|
|
||||||
future::ready(serde_json::json!({
|
future::ready(serde_json::json!({
|
||||||
"yaml": {
|
"yaml": {
|
||||||
"keyOrdering": false
|
"keyOrdering": false
|
||||||
},
|
},
|
||||||
"[yaml]": {
|
"[yaml]": {
|
||||||
"editor.tabSize": tab_size,
|
"editor.tabSize": tab_size,
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.boxed(),
|
.boxed()
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,6 +206,9 @@ export default function editor(): any {
|
|||||||
match_highlight: foreground(theme.middle, "accent", "active"),
|
match_highlight: foreground(theme.middle, "accent", "active"),
|
||||||
background: background(theme.middle, "active"),
|
background: background(theme.middle, "active"),
|
||||||
},
|
},
|
||||||
|
server_name_container: { padding: { left: 40 } },
|
||||||
|
server_name_color: text(theme.middle, "sans", "disabled", {}).color,
|
||||||
|
server_name_size_percent: 0.75,
|
||||||
},
|
},
|
||||||
diagnostic_header: {
|
diagnostic_header: {
|
||||||
background: background(theme.middle),
|
background: background(theme.middle),
|
||||||
|
Loading…
Reference in New Issue
Block a user