Detailed changes
@@ -1722,6 +1722,8 @@ dependencies = [
"text",
"theme",
"tree-sitter",
+ "tree-sitter-html",
+ "tree-sitter-javascript",
"tree-sitter-rust",
"unindent",
"util",
@@ -2896,6 +2898,8 @@ dependencies = [
"text",
"theme",
"tree-sitter",
+ "tree-sitter-html",
+ "tree-sitter-javascript",
"tree-sitter-json 0.19.0",
"tree-sitter-python",
"tree-sitter-rust",
@@ -6075,6 +6079,15 @@ dependencies = [
"tree-sitter",
]
+[[package]]
+name = "tree-sitter-css"
+version = "0.19.0"
+source = "git+https://github.com/tree-sitter/tree-sitter-css?rev=769203d0f9abe1a9a691ac2b9fe4bb4397a73c51#769203d0f9abe1a9a691ac2b9fe4bb4397a73c51"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
[[package]]
name = "tree-sitter-elixir"
version = "0.19.0"
@@ -6093,6 +6106,26 @@ dependencies = [
"tree-sitter",
]
+[[package]]
+name = "tree-sitter-html"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "184e6b77953a354303dc87bf5fe36558c83569ce92606e7b382a0dc1b7443443"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
+[[package]]
+name = "tree-sitter-javascript"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2490fab08630b2c8943c320f7b63473cbf65511c8d83aec551beb9b4375906ed"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
[[package]]
name = "tree-sitter-json"
version = "0.19.0"
@@ -7279,8 +7312,10 @@ dependencies = [
"tree-sitter",
"tree-sitter-c",
"tree-sitter-cpp",
+ "tree-sitter-css",
"tree-sitter-elixir",
"tree-sitter-go",
+ "tree-sitter-html",
"tree-sitter-json 0.20.0",
"tree-sitter-markdown",
"tree-sitter-python",
@@ -52,6 +52,8 @@ serde = { version = "1.0", features = ["derive", "rc"] }
smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"
tree-sitter-rust = { version = "*", optional = true }
+tree-sitter-html = { version = "*", optional = true }
+tree-sitter-javascript = { version = "*", optional = true }
[dev-dependencies]
text = { path = "../text", features = ["test-support"] }
@@ -68,3 +70,5 @@ rand = "0.8"
unindent = "0.1.7"
tree-sitter = "0.20"
tree-sitter-rust = "0.20"
+tree-sitter-html = "0.19"
+tree-sitter-javascript = "0.20"
@@ -410,7 +410,7 @@ pub struct Editor {
add_selections_state: Option<AddSelectionsState>,
select_next_state: Option<SelectNextState>,
selection_history: SelectionHistory,
- autoclose_stack: InvalidationStack<BracketPairState>,
+ autoclose_regions: Vec<AutocloseRegion>,
snippet_stack: InvalidationStack<SnippetState>,
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
ime_transaction: Option<TransactionId>,
@@ -569,8 +569,10 @@ struct SelectNextState {
done: bool,
}
-struct BracketPairState {
- ranges: Vec<Range<Anchor>>,
+#[derive(Debug)]
+struct AutocloseRegion {
+ selection_id: usize,
+ range: Range<Anchor>,
pair: BracketPair,
}
@@ -1010,7 +1012,7 @@ impl Editor {
add_selections_state: None,
select_next_state: None,
selection_history: Default::default(),
- autoclose_stack: Default::default(),
+ autoclose_regions: Default::default(),
snippet_stack: Default::default(),
select_larger_syntax_node_stack: Vec::new(),
ime_transaction: Default::default(),
@@ -1116,7 +1118,7 @@ impl Editor {
&self,
point: T,
cx: &'a AppContext,
- ) -> Option<&'a Arc<Language>> {
+ ) -> Option<Arc<Language>> {
self.buffer.read(cx).language_at(point, cx)
}
@@ -1401,8 +1403,7 @@ impl Editor {
self.add_selections_state = None;
self.select_next_state = None;
self.select_larger_syntax_node_stack.clear();
- self.autoclose_stack
- .invalidate(&self.selections.disjoint_anchors(), buffer);
+ self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
self.snippet_stack
.invalidate(&self.selections.disjoint_anchors(), buffer);
self.take_rename(false, cx);
@@ -1849,15 +1850,159 @@ impl Editor {
return;
}
- if !self.skip_autoclose_end(text, cx) {
- self.transact(cx, |this, cx| {
- if !this.surround_with_bracket_pair(text, cx) {
- this.insert(text, cx);
- this.autoclose_bracket_pairs(cx);
+ let text: Arc<str> = text.into();
+ let selections = self.selections.all_adjusted(cx);
+ let mut edits = Vec::new();
+ let mut new_selections = Vec::with_capacity(selections.len());
+ let mut new_autoclose_regions = Vec::new();
+ let snapshot = self.buffer.read(cx).read(cx);
+
+ for (selection, autoclose_region) in
+ self.selections_with_autoclose_regions(selections, &snapshot)
+ {
+ if let Some(language) = snapshot.language_at(selection.head()) {
+ // Determine if the inserted text matches the opening or closing
+ // bracket of any of this language's bracket pairs.
+ let mut bracket_pair = None;
+ let mut is_bracket_pair_start = false;
+ for pair in language.brackets() {
+ if pair.start.ends_with(text.as_ref()) {
+ bracket_pair = Some(pair.clone());
+ is_bracket_pair_start = true;
+ break;
+ } else if pair.end.as_str() == text.as_ref() {
+ bracket_pair = Some(pair.clone());
+ break;
+ }
}
- });
- self.trigger_completion_on_input(text, cx);
+
+ if let Some(bracket_pair) = bracket_pair {
+ if selection.is_empty() {
+ if is_bracket_pair_start {
+ let prefix_len = bracket_pair.start.len() - text.len();
+
+ // If the inserted text is a suffix of an opening bracket and the
+ // selection is preceded by the rest of the opening bracket, then
+ // insert the closing bracket.
+ let following_text_allows_autoclose = snapshot
+ .chars_at(selection.start)
+ .next()
+ .map_or(true, |c| language.should_autoclose_before(c));
+ let preceding_text_matches_prefix = prefix_len == 0
+ || (selection.start.column >= (prefix_len as u32)
+ && snapshot.contains_str_at(
+ Point::new(
+ selection.start.row,
+ selection.start.column - (prefix_len as u32),
+ ),
+ &bracket_pair.start[..prefix_len],
+ ));
+ if following_text_allows_autoclose && preceding_text_matches_prefix {
+ let anchor = snapshot.anchor_before(selection.end);
+ new_selections
+ .push((selection.map(|_| anchor.clone()), text.len()));
+ new_autoclose_regions.push((
+ anchor.clone(),
+ text.len(),
+ selection.id,
+ bracket_pair.clone(),
+ ));
+ edits.push((
+ selection.range(),
+ format!("{}{}", text, bracket_pair.end).into(),
+ ));
+ continue;
+ }
+ } else if let Some(region) = autoclose_region {
+ // If the selection is followed by an auto-inserted closing bracket,
+ // then don't insert anything else; just move the selection past the
+ // closing bracket.
+ let should_skip = selection.end == region.range.end.to_point(&snapshot);
+ if should_skip {
+ let anchor = snapshot.anchor_after(selection.end);
+ new_selections.push((
+ selection.map(|_| anchor.clone()),
+ region.pair.end.len(),
+ ));
+ continue;
+ }
+ }
+ }
+ // If an opening bracket is typed while text is selected, then
+ // surround that text with the bracket pair.
+ else if is_bracket_pair_start {
+ edits.push((selection.start..selection.start, text.clone()));
+ edits.push((
+ selection.end..selection.end,
+ bracket_pair.end.as_str().into(),
+ ));
+ new_selections.push((
+ Selection {
+ id: selection.id,
+ start: snapshot.anchor_after(selection.start),
+ end: snapshot.anchor_before(selection.end),
+ reversed: selection.reversed,
+ goal: selection.goal,
+ },
+ 0,
+ ));
+ continue;
+ }
+ }
+ }
+
+ // If not handling any auto-close operation, then just replace the selected
+ // text with the given input and move the selection to the end of the
+ // newly inserted text.
+ let anchor = snapshot.anchor_after(selection.end);
+ new_selections.push((selection.map(|_| anchor.clone()), 0));
+ edits.push((selection.start..selection.end, text.clone()));
}
+
+ drop(snapshot);
+ self.transact(cx, |this, cx| {
+ this.buffer.update(cx, |buffer, cx| {
+ buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
+ });
+
+ let new_anchor_selections = new_selections.iter().map(|e| &e.0);
+ let new_selection_deltas = new_selections.iter().map(|e| e.1);
+ let snapshot = this.buffer.read(cx).read(cx);
+ let new_selections = resolve_multiple::<usize, _>(new_anchor_selections, &snapshot)
+ .zip(new_selection_deltas)
+ .map(|(selection, delta)| selection.map(|e| e + delta))
+ .collect::<Vec<_>>();
+
+ let mut i = 0;
+ for (position, delta, selection_id, pair) in new_autoclose_regions {
+ let position = position.to_offset(&snapshot) + delta;
+ let start = snapshot.anchor_before(position);
+ let end = snapshot.anchor_after(position);
+ while let Some(existing_state) = this.autoclose_regions.get(i) {
+ match existing_state.range.start.cmp(&start, &snapshot) {
+ Ordering::Less => i += 1,
+ Ordering::Greater => break,
+ Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) {
+ Ordering::Less => i += 1,
+ Ordering::Equal => break,
+ Ordering::Greater => break,
+ },
+ }
+ }
+ this.autoclose_regions.insert(
+ i,
+ AutocloseRegion {
+ selection_id,
+ range: start..end,
+ pair,
+ },
+ );
+ }
+
+ drop(snapshot);
+ this.change_selections(None, cx, |s| s.select(new_selections));
+ this.trigger_completion_on_input(&text, cx);
+ });
}
pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
@@ -1876,7 +2021,7 @@ impl Editor {
let end = selection.end;
let mut insert_extra_newline = false;
- if let Some(language) = buffer.language() {
+ if let Some(language) = buffer.language_at(start) {
let leading_whitespace_len = buffer
.reversed_chars_at(start)
.take_while(|c| c.is_whitespace() && *c != '\n')
@@ -2029,232 +2174,89 @@ impl Editor {
}
}
- fn surround_with_bracket_pair(&mut self, text: &str, cx: &mut ViewContext<Self>) -> bool {
- let snapshot = self.buffer.read(cx).snapshot(cx);
- if let Some(pair) = snapshot
- .language()
- .and_then(|language| language.brackets().iter().find(|b| b.start == text))
- .cloned()
- {
- if self
- .selections
- .all::<usize>(cx)
- .iter()
- .any(|selection| selection.is_empty())
- {
- return false;
- }
-
- let mut selections = self.selections.disjoint_anchors().to_vec();
- for selection in &mut selections {
- selection.end = selection.end.bias_left(&snapshot);
- }
- drop(snapshot);
-
- self.buffer.update(cx, |buffer, cx| {
- let pair_start: Arc<str> = pair.start.clone().into();
- let pair_end: Arc<str> = pair.end.clone().into();
- buffer.edit(
- selections.iter().flat_map(|s| {
- [
- (s.start.clone()..s.start.clone(), pair_start.clone()),
- (s.end.clone()..s.end.clone(), pair_end.clone()),
- ]
- }),
- None,
- cx,
- );
- });
-
- let snapshot = self.buffer.read(cx).read(cx);
- for selection in &mut selections {
- selection.end = selection.end.bias_right(&snapshot);
- }
- drop(snapshot);
-
- self.change_selections(None, cx, |s| s.select_anchors(selections));
- true
- } else {
- false
- }
- }
-
- fn autoclose_bracket_pairs(&mut self, cx: &mut ViewContext<Self>) {
+ /// If any empty selections is touching the start of its innermost containing autoclose
+ /// region, expand it to select the brackets.
+ fn select_autoclose_pair(&mut self, cx: &mut ViewContext<Self>) {
let selections = self.selections.all::<usize>(cx);
- let mut bracket_pair_state = None;
- let mut new_selections = None;
- self.buffer.update(cx, |buffer, cx| {
- let mut snapshot = buffer.snapshot(cx);
- let left_biased_selections = selections
- .iter()
- .map(|selection| selection.map(|p| snapshot.anchor_before(p)))
- .collect::<Vec<_>>();
-
- let autoclose_pair = snapshot.language().and_then(|language| {
- let first_selection_start = selections.first().unwrap().start;
- let pair = language.brackets().iter().find(|pair| {
- pair.close
- && snapshot.contains_str_at(
- first_selection_start.saturating_sub(pair.start.len()),
- &pair.start,
- )
- });
- pair.and_then(|pair| {
- let should_autoclose = selections.iter().all(|selection| {
- // Ensure all selections are parked at the end of a pair start.
- if snapshot.contains_str_at(
- selection.start.saturating_sub(pair.start.len()),
- &pair.start,
- ) {
- snapshot
- .chars_at(selection.start)
- .next()
- .map_or(true, |c| language.should_autoclose_before(c))
- } else {
- false
+ let buffer = self.buffer.read(cx).read(cx);
+ let mut new_selections = Vec::new();
+ for (mut selection, region) in self.selections_with_autoclose_regions(selections, &buffer) {
+ if let (Some(region), true) = (region, selection.is_empty()) {
+ let mut range = region.range.to_offset(&buffer);
+ if selection.start == range.start {
+ if range.start >= region.pair.start.len() {
+ range.start -= region.pair.start.len();
+ if buffer.contains_str_at(range.start, ®ion.pair.start) {
+ if buffer.contains_str_at(range.end, ®ion.pair.end) {
+ range.end += region.pair.end.len();
+ selection.start = range.start;
+ selection.end = range.end;
+ }
}
- });
-
- if should_autoclose {
- Some(pair.clone())
- } else {
- None
}
- })
- });
-
- if let Some(pair) = autoclose_pair {
- let selection_ranges = selections
- .iter()
- .map(|selection| {
- let start = selection.start.to_offset(&snapshot);
- start..start
- })
- .collect::<SmallVec<[_; 32]>>();
-
- let pair_end: Arc<str> = pair.end.clone().into();
- buffer.edit(
- selection_ranges
- .iter()
- .map(|range| (range.clone(), pair_end.clone())),
- None,
- cx,
- );
- snapshot = buffer.snapshot(cx);
-
- new_selections = Some(
- resolve_multiple::<usize, _>(left_biased_selections.iter(), &snapshot)
- .collect::<Vec<_>>(),
- );
-
- if pair.end.len() == 1 {
- let mut delta = 0;
- bracket_pair_state = Some(BracketPairState {
- ranges: selections
- .iter()
- .map(move |selection| {
- let offset = selection.start + delta;
- delta += 1;
- snapshot.anchor_before(offset)..snapshot.anchor_after(offset)
- })
- .collect(),
- pair,
- });
}
}
- });
-
- if let Some(new_selections) = new_selections {
- self.change_selections(None, cx, |s| {
- s.select(new_selections);
- });
+ new_selections.push(selection);
}
- if let Some(bracket_pair_state) = bracket_pair_state {
- self.autoclose_stack.push(bracket_pair_state);
- }
- }
- fn skip_autoclose_end(&mut self, text: &str, cx: &mut ViewContext<Self>) -> bool {
- let buffer = self.buffer.read(cx).snapshot(cx);
- let old_selections = self.selections.all::<usize>(cx);
- let autoclose_pair = if let Some(autoclose_pair) = self.autoclose_stack.last() {
- autoclose_pair
- } else {
- return false;
- };
- if text != autoclose_pair.pair.end {
- return false;
- }
-
- debug_assert_eq!(old_selections.len(), autoclose_pair.ranges.len());
-
- if old_selections
- .iter()
- .zip(autoclose_pair.ranges.iter().map(|r| r.to_offset(&buffer)))
- .all(|(selection, autoclose_range)| {
- let autoclose_range_end = autoclose_range.end.to_offset(&buffer);
- selection.is_empty() && selection.start == autoclose_range_end
- })
- {
- let new_selections = old_selections
- .into_iter()
- .map(|selection| {
- let cursor = selection.start + 1;
- Selection {
- id: selection.id,
- start: cursor,
- end: cursor,
- reversed: false,
- goal: SelectionGoal::None,
- }
- })
- .collect();
- self.autoclose_stack.pop();
- self.change_selections(Some(Autoscroll::Fit), cx, |s| {
- s.select(new_selections);
- });
- true
- } else {
- false
- }
+ drop(buffer);
+ self.change_selections(None, cx, |selections| selections.select(new_selections));
}
- fn select_autoclose_pair(&mut self, cx: &mut ViewContext<Self>) -> bool {
- let buffer = self.buffer.read(cx).snapshot(cx);
- let old_selections = self.selections.all::<usize>(cx);
- let autoclose_pair = if let Some(autoclose_pair) = self.autoclose_stack.last() {
- autoclose_pair
- } else {
- return false;
- };
+ /// Iterate the given selections, and for each one, find the smallest surrounding
+ /// autoclose region. This uses the ordering of the selections and the autoclose
+ /// regions to avoid repeated comparisons.
+ fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
+ &'a self,
+ selections: impl IntoIterator<Item = Selection<D>>,
+ buffer: &'a MultiBufferSnapshot,
+ ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
+ let mut i = 0;
+ let mut regions = self.autoclose_regions.as_slice();
+ selections.into_iter().map(move |selection| {
+ let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
+
+ let mut enclosing = None;
+ while let Some(pair_state) = regions.get(i) {
+ if pair_state.range.end.to_offset(buffer) < range.start {
+ regions = ®ions[i + 1..];
+ i = 0;
+ } else if pair_state.range.start.to_offset(buffer) > range.end {
+ break;
+ } else if pair_state.selection_id == selection.id {
+ enclosing = Some(pair_state);
+ i += 1;
+ }
+ }
- debug_assert_eq!(old_selections.len(), autoclose_pair.ranges.len());
+ (selection.clone(), enclosing)
+ })
+ }
- let mut new_selections = Vec::new();
- for (selection, autoclose_range) in old_selections
- .iter()
- .zip(autoclose_pair.ranges.iter().map(|r| r.to_offset(&buffer)))
- {
- if selection.is_empty()
- && autoclose_range.is_empty()
- && selection.start == autoclose_range.start
- {
- new_selections.push(Selection {
- id: selection.id,
- start: selection.start - autoclose_pair.pair.start.len(),
- end: selection.end + autoclose_pair.pair.end.len(),
- reversed: true,
- goal: selection.goal,
- });
- } else {
- return false;
+ /// Remove any autoclose regions that no longer contain their selection.
+ fn invalidate_autoclose_regions(
+ &mut self,
+ mut selections: &[Selection<Anchor>],
+ buffer: &MultiBufferSnapshot,
+ ) {
+ self.autoclose_regions.retain(|state| {
+ let mut i = 0;
+ while let Some(selection) = selections.get(i) {
+ if selection.end.cmp(&state.range.start, buffer).is_lt() {
+ selections = &selections[1..];
+ continue;
+ }
+ if selection.start.cmp(&state.range.end, buffer).is_gt() {
+ break;
+ }
+ if selection.id == state.selection_id {
+ return true;
+ } else {
+ i += 1;
+ }
}
- }
-
- self.change_selections(Some(Autoscroll::Fit), cx, |selections| {
- selections.select(new_selections)
+ false
});
- true
}
fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
@@ -2909,51 +2911,49 @@ impl Editor {
pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
self.transact(cx, |this, cx| {
- if !this.select_autoclose_pair(cx) {
- let mut selections = this.selections.all::<Point>(cx);
- if !this.selections.line_mode {
- let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
- for selection in &mut selections {
- if selection.is_empty() {
- let old_head = selection.head();
- let mut new_head = movement::left(
- &display_map,
- old_head.to_display_point(&display_map),
- )
- .to_point(&display_map);
- if let Some((buffer, line_buffer_range)) = display_map
- .buffer_snapshot
- .buffer_line_for_row(old_head.row)
- {
- let indent_size =
- buffer.indent_size_for_line(line_buffer_range.start.row);
- let language_name =
- buffer.language().map(|language| language.name());
- let indent_len = match indent_size.kind {
- IndentKind::Space => {
- cx.global::<Settings>().tab_size(language_name.as_deref())
- }
- IndentKind::Tab => NonZeroU32::new(1).unwrap(),
- };
- if old_head.column <= indent_size.len && old_head.column > 0 {
- let indent_len = indent_len.get();
- new_head = cmp::min(
- new_head,
- Point::new(
- old_head.row,
- ((old_head.column - 1) / indent_len) * indent_len,
- ),
- );
+ this.select_autoclose_pair(cx);
+ let mut selections = this.selections.all::<Point>(cx);
+ if !this.selections.line_mode {
+ let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
+ for selection in &mut selections {
+ if selection.is_empty() {
+ let old_head = selection.head();
+ let mut new_head =
+ movement::left(&display_map, old_head.to_display_point(&display_map))
+ .to_point(&display_map);
+ if let Some((buffer, line_buffer_range)) = display_map
+ .buffer_snapshot
+ .buffer_line_for_row(old_head.row)
+ {
+ let indent_size =
+ buffer.indent_size_for_line(line_buffer_range.start.row);
+ let language_name = buffer
+ .language_at(line_buffer_range.start)
+ .map(|language| language.name());
+ let indent_len = match indent_size.kind {
+ IndentKind::Space => {
+ cx.global::<Settings>().tab_size(language_name.as_deref())
}
+ IndentKind::Tab => NonZeroU32::new(1).unwrap(),
+ };
+ if old_head.column <= indent_size.len && old_head.column > 0 {
+ let indent_len = indent_len.get();
+ new_head = cmp::min(
+ new_head,
+ Point::new(
+ old_head.row,
+ ((old_head.column - 1) / indent_len) * indent_len,
+ ),
+ );
}
-
- selection.set_head(new_head, SelectionGoal::None);
}
+
+ selection.set_head(new_head, SelectionGoal::None);
}
}
-
- this.change_selections(Some(Autoscroll::Fit), cx, |s| s.select(selections));
}
+
+ this.change_selections(Some(Autoscroll::Fit), cx, |s| s.select(selections));
this.insert("", cx);
});
}
@@ -3957,17 +3957,16 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
self.transact(cx, |this, cx| {
- if !this.select_autoclose_pair(cx) {
- this.change_selections(Some(Autoscroll::Fit), cx, |s| {
- let line_mode = s.line_mode;
- s.move_with(|map, selection| {
- if selection.is_empty() && !line_mode {
- let cursor = movement::previous_word_start(map, selection.head());
- selection.set_head(cursor, SelectionGoal::None);
- }
- });
+ this.select_autoclose_pair(cx);
+ this.change_selections(Some(Autoscroll::Fit), cx, |s| {
+ let line_mode = s.line_mode;
+ s.move_with(|map, selection| {
+ if selection.is_empty() && !line_mode {
+ let cursor = movement::previous_word_start(map, selection.head());
+ selection.set_head(cursor, SelectionGoal::None);
+ }
});
- }
+ });
this.insert("", cx);
});
}
@@ -3978,17 +3977,16 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
self.transact(cx, |this, cx| {
- if !this.select_autoclose_pair(cx) {
- this.change_selections(Some(Autoscroll::Fit), cx, |s| {
- let line_mode = s.line_mode;
- s.move_with(|map, selection| {
- if selection.is_empty() && !line_mode {
- let cursor = movement::previous_subword_start(map, selection.head());
- selection.set_head(cursor, SelectionGoal::None);
- }
- });
+ this.select_autoclose_pair(cx);
+ this.change_selections(Some(Autoscroll::Fit), cx, |s| {
+ let line_mode = s.line_mode;
+ s.move_with(|map, selection| {
+ if selection.is_empty() && !line_mode {
+ let cursor = movement::previous_subword_start(map, selection.head());
+ selection.set_head(cursor, SelectionGoal::None);
+ }
});
- }
+ });
this.insert("", cx);
});
}
@@ -4491,108 +4489,218 @@ impl Editor {
pub fn toggle_comments(&mut self, _: &ToggleComments, cx: &mut ViewContext<Self>) {
self.transact(cx, |this, cx| {
let mut selections = this.selections.all::<Point>(cx);
- let mut all_selection_lines_are_comments = true;
- let mut edit_ranges = Vec::new();
+ let mut edits = Vec::new();
+ let mut selection_edit_ranges = Vec::new();
let mut last_toggled_row = None;
- this.buffer.update(cx, |buffer, cx| {
- // TODO: Handle selections that cross excerpts
- for selection in &mut selections {
- // Get the line comment prefix. Split its trailing whitespace into a separate string,
- // as that portion won't be used for detecting if a line is a comment.
- let full_comment_prefix: Arc<str> = if let Some(prefix) = buffer
- .language_at(selection.start, cx)
- .and_then(|l| l.line_comment_prefix())
- {
- prefix.into()
+ let snapshot = this.buffer.read(cx).read(cx);
+ let empty_str: Arc<str> = "".into();
+ let mut suffixes_inserted = Vec::new();
+
+ fn comment_prefix_range(
+ snapshot: &MultiBufferSnapshot,
+ row: u32,
+ comment_prefix: &str,
+ comment_prefix_whitespace: &str,
+ ) -> Range<Point> {
+ let start = Point::new(row, snapshot.indent_size_for_line(row).len);
+
+ let mut line_bytes = snapshot
+ .bytes_in_range(start..snapshot.max_point())
+ .flatten()
+ .copied();
+
+ // If this line currently begins with the line comment prefix, then record
+ // the range containing the prefix.
+ if line_bytes
+ .by_ref()
+ .take(comment_prefix.len())
+ .eq(comment_prefix.bytes())
+ {
+ // Include any whitespace that matches the comment prefix.
+ let matching_whitespace_len = line_bytes
+ .zip(comment_prefix_whitespace.bytes())
+ .take_while(|(a, b)| a == b)
+ .count() as u32;
+ let end = Point::new(
+ start.row,
+ start.column + comment_prefix.len() as u32 + matching_whitespace_len,
+ );
+ start..end
+ } else {
+ start..start
+ }
+ }
+
+ fn comment_suffix_range(
+ snapshot: &MultiBufferSnapshot,
+ row: u32,
+ comment_suffix: &str,
+ comment_suffix_has_leading_space: bool,
+ ) -> Range<Point> {
+ let end = Point::new(row, snapshot.line_len(row));
+ let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
+
+ let mut line_end_bytes = snapshot
+ .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
+ .flatten()
+ .copied();
+
+ let leading_space_len = if suffix_start_column > 0
+ && line_end_bytes.next() == Some(b' ')
+ && comment_suffix_has_leading_space
+ {
+ 1
+ } else {
+ 0
+ };
+
+ // If this line currently begins with the line comment prefix, then record
+ // the range containing the prefix.
+ if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
+ let start = Point::new(end.row, suffix_start_column - leading_space_len);
+ start..end
+ } else {
+ end..end
+ }
+ }
+
+ // TODO: Handle selections that cross excerpts
+ for selection in &mut selections {
+ let language = if let Some(language) = snapshot.language_at(selection.start) {
+ language
+ } else {
+ continue;
+ };
+
+ selection_edit_ranges.clear();
+
+ // If multiple selections contain a given row, avoid processing that
+ // row more than once.
+ let mut start_row = selection.start.row;
+ if last_toggled_row == Some(start_row) {
+ start_row += 1;
+ }
+ let end_row =
+ if selection.end.row > selection.start.row && selection.end.column == 0 {
+ selection.end.row - 1
} else {
- return;
+ selection.end.row
};
- let comment_prefix = full_comment_prefix.trim_end_matches(' ');
- let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
- edit_ranges.clear();
- let snapshot = buffer.snapshot(cx);
+ last_toggled_row = Some(end_row);
- let end_row =
- if selection.end.row > selection.start.row && selection.end.column == 0 {
- selection.end.row
- } else {
- selection.end.row + 1
- };
+ if start_row > end_row {
+ continue;
+ }
- for row in selection.start.row..end_row {
- // If multiple selections contain a given row, avoid processing that
- // row more than once.
- if last_toggled_row == Some(row) {
- continue;
- } else {
- last_toggled_row = Some(row);
- }
+ // If the language has line comments, toggle those.
+ if let Some(full_comment_prefix) = language.line_comment_prefix() {
+ // Split the comment prefix's trailing whitespace into a separate string,
+ // as that portion won't be used for detecting if a line is a comment.
+ let comment_prefix = full_comment_prefix.trim_end_matches(' ');
+ let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
+ let mut all_selection_lines_are_comments = true;
+ for row in start_row..=end_row {
if snapshot.is_line_blank(row) {
continue;
}
- let start = Point::new(row, snapshot.indent_size_for_line(row).len);
- let mut line_bytes = snapshot
- .bytes_in_range(start..snapshot.max_point())
- .flatten()
- .copied();
-
- // If this line currently begins with the line comment prefix, then record
- // the range containing the prefix.
- if all_selection_lines_are_comments
- && line_bytes
- .by_ref()
- .take(comment_prefix.len())
- .eq(comment_prefix.bytes())
- {
- // Include any whitespace that matches the comment prefix.
- let matching_whitespace_len = line_bytes
- .zip(comment_prefix_whitespace.bytes())
- .take_while(|(a, b)| a == b)
- .count()
- as u32;
- let end = Point::new(
- row,
- start.column
- + comment_prefix.len() as u32
- + matching_whitespace_len,
- );
- edit_ranges.push(start..end);
- }
- // If this line does not begin with the line comment prefix, then record
- // the position where the prefix should be inserted.
- else {
+ let prefix_range = comment_prefix_range(
+ snapshot.deref(),
+ row,
+ comment_prefix,
+ comment_prefix_whitespace,
+ );
+ if prefix_range.is_empty() {
all_selection_lines_are_comments = false;
- edit_ranges.push(start..start);
}
+ selection_edit_ranges.push(prefix_range);
}
- if !edit_ranges.is_empty() {
- if all_selection_lines_are_comments {
- let empty_str: Arc<str> = "".into();
- buffer.edit(
- edit_ranges
- .iter()
- .cloned()
- .map(|range| (range, empty_str.clone())),
- None,
- cx,
- );
- } else {
- let min_column =
- edit_ranges.iter().map(|r| r.start.column).min().unwrap();
- let edits = edit_ranges.iter().map(|range| {
- let position = Point::new(range.start.row, min_column);
- (position..position, full_comment_prefix.clone())
- });
- buffer.edit(edits, None, cx);
- }
+ if all_selection_lines_are_comments {
+ edits.extend(
+ selection_edit_ranges
+ .iter()
+ .cloned()
+ .map(|range| (range, empty_str.clone())),
+ );
+ } else {
+ let min_column = selection_edit_ranges
+ .iter()
+ .map(|r| r.start.column)
+ .min()
+ .unwrap_or(0);
+ edits.extend(selection_edit_ranges.iter().map(|range| {
+ let position = Point::new(range.start.row, min_column);
+ (position..position, full_comment_prefix.clone())
+ }));
+ }
+ } else if let Some((full_comment_prefix, comment_suffix)) =
+ language.block_comment_delimiters()
+ {
+ let comment_prefix = full_comment_prefix.trim_end_matches(' ');
+ let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
+ let prefix_range = comment_prefix_range(
+ snapshot.deref(),
+ start_row,
+ comment_prefix,
+ comment_prefix_whitespace,
+ );
+ let suffix_range = comment_suffix_range(
+ snapshot.deref(),
+ end_row,
+ comment_suffix.trim_start_matches(' '),
+ comment_suffix.starts_with(' '),
+ );
+
+ if prefix_range.is_empty() || suffix_range.is_empty() {
+ edits.push((
+ prefix_range.start..prefix_range.start,
+ full_comment_prefix.clone(),
+ ));
+ edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
+ suffixes_inserted.push((end_row, comment_suffix.len()));
+ } else {
+ edits.push((prefix_range, empty_str.clone()));
+ edits.push((suffix_range, empty_str.clone()));
}
+ } else {
+ continue;
}
+ }
+
+ drop(snapshot);
+ this.buffer.update(cx, |buffer, cx| {
+ buffer.edit(edits, None, cx);
});
- let selections = this.selections.all::<usize>(cx);
+ // Adjust selections so that they end before any comment suffixes that
+ // were inserted.
+ let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
+ let mut selections = this.selections.all::<Point>(cx);
+ let snapshot = this.buffer.read(cx).read(cx);
+ for selection in &mut selections {
+ while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
+ match row.cmp(&selection.end.row) {
+ Ordering::Less => {
+ suffixes_inserted.next();
+ continue;
+ }
+ Ordering::Greater => break,
+ Ordering::Equal => {
+ if selection.end.column == snapshot.line_len(row) {
+ if selection.is_empty() {
+ selection.start.column -= suffix_len as u32;
+ }
+ selection.end.column -= suffix_len as u32;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ drop(snapshot);
this.change_selections(Some(Autoscroll::Fit), cx, |s| s.select(selections));
});
}
@@ -6007,6 +6115,10 @@ impl Editor {
}
impl EditorSnapshot {
+ pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
+ self.display_snapshot.buffer_snapshot.language_at(position)
+ }
+
pub fn is_focused(&self) -> bool {
self.is_focused
}
@@ -6495,12 +6607,6 @@ impl<T> DerefMut for InvalidationStack<T> {
}
}
-impl InvalidationRegion for BracketPairState {
- fn ranges(&self) -> &[Range<Anchor>] {
- &self.ranges
- }
-}
-
impl InvalidationRegion for SnippetState {
fn ranges(&self) -> &[Range<Anchor>] {
&self.ranges[self.active_index]
@@ -6713,7 +6819,7 @@ mod tests {
platform::{WindowBounds, WindowOptions},
};
use indoc::indoc;
- use language::{FakeLspAdapter, LanguageConfig};
+ use language::{FakeLspAdapter, LanguageConfig, LanguageRegistry};
use project::FakeFs;
use settings::EditorSettings;
use std::{cell::RefCell, rc::Rc, time::Instant};
@@ -1228,9 +1228,9 @@ impl MultiBuffer {
&self,
point: T,
cx: &'a AppContext,
- ) -> Option<&'a Arc<Language>> {
+ ) -> Option<Arc<Language>> {
self.point_to_buffer_offset(point, cx)
- .and_then(|(buffer, _)| buffer.read(cx).language())
+ .and_then(|(buffer, offset)| buffer.read(cx).language_at(offset))
}
pub fn files<'a>(&'a self, cx: &'a AppContext) -> SmallVec<[&'a dyn File; 2]> {
@@ -1966,6 +1966,24 @@ impl MultiBufferSnapshot {
}
}
+ pub fn point_to_buffer_offset<T: ToOffset>(
+ &self,
+ point: T,
+ ) -> Option<(&BufferSnapshot, usize)> {
+ let offset = point.to_offset(&self);
+ let mut cursor = self.excerpts.cursor::<usize>();
+ cursor.seek(&offset, Bias::Right, &());
+ if cursor.item().is_none() {
+ cursor.prev(&());
+ }
+
+ cursor.item().map(|excerpt| {
+ let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
+ let buffer_point = excerpt_start + offset - *cursor.start();
+ (&excerpt.buffer, buffer_point)
+ })
+ }
+
pub fn suggested_indents(
&self,
rows: impl IntoIterator<Item = u32>,
@@ -1975,8 +1993,10 @@ impl MultiBufferSnapshot {
let mut rows_for_excerpt = Vec::new();
let mut cursor = self.excerpts.cursor::<Point>();
-
let mut rows = rows.into_iter().peekable();
+ let mut prev_row = u32::MAX;
+ let mut prev_language_indent_size = IndentSize::default();
+
while let Some(row) = rows.next() {
cursor.seek(&Point::new(row, 0), Bias::Right, &());
let excerpt = match cursor.item() {
@@ -1984,7 +2004,17 @@ impl MultiBufferSnapshot {
_ => continue,
};
- let single_indent_size = excerpt.buffer.single_indent_size(cx);
+ // Retrieve the language and indent size once for each disjoint region being indented.
+ let single_indent_size = if row.saturating_sub(1) == prev_row {
+ prev_language_indent_size
+ } else {
+ excerpt
+ .buffer
+ .language_indent_size_at(Point::new(row, 0), cx)
+ };
+ prev_language_indent_size = single_indent_size;
+ prev_row = row;
+
let start_buffer_row = excerpt.range.context.start.to_point(&excerpt.buffer).row;
let start_multibuffer_row = cursor.start().row;
@@ -2513,11 +2543,9 @@ impl MultiBufferSnapshot {
self.trailing_excerpt_update_count
}
- pub fn language(&self) -> Option<&Arc<Language>> {
- self.excerpts
- .iter()
- .next()
- .and_then(|excerpt| excerpt.buffer.language())
+ pub fn language_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc<Language>> {
+ self.point_to_buffer_offset(point)
+ .and_then(|(buffer, offset)| buffer.language_at(offset))
}
pub fn is_dirty(&self) -> bool {
@@ -64,6 +64,8 @@ util = { path = "../util", features = ["test-support"] }
ctor = "0.1"
env_logger = "0.9"
rand = "0.8.3"
+tree-sitter-html = "*"
+tree-sitter-javascript = "*"
tree-sitter-json = "*"
tree-sitter-rust = "*"
tree-sitter-python = "*"
@@ -95,14 +95,15 @@ pub struct BufferSnapshot {
parse_count: usize,
}
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct IndentSize {
pub len: u32,
pub kind: IndentKind,
}
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum IndentKind {
+ #[default]
Space,
Tab,
}
@@ -247,7 +248,6 @@ pub enum AutoindentMode {
struct AutoindentRequest {
before_edit: BufferSnapshot,
entries: Vec<AutoindentRequestEntry>,
- indent_size: IndentSize,
is_block_mode: bool,
}
@@ -260,6 +260,7 @@ struct AutoindentRequestEntry {
/// only be adjusted if the suggested indentation level has *changed*
/// since the edit was made.
first_line_is_new: bool,
+ indent_size: IndentSize,
original_indent_column: Option<u32>,
}
@@ -719,6 +720,16 @@ impl Buffer {
self.language.as_ref()
}
+ pub fn language_at<D: ToOffset>(&self, position: D) -> Option<Arc<Language>> {
+ let offset = position.to_offset(self);
+ self.syntax_map
+ .lock()
+ .layers_for_range(offset..offset, &self.text)
+ .last()
+ .map(|info| info.language.clone())
+ .or_else(|| self.language.clone())
+ }
+
pub fn parse_count(&self) -> usize {
self.parse_count
}
@@ -866,10 +877,13 @@ impl Buffer {
// buffer before this batch of edits.
let mut row_ranges = Vec::new();
let mut old_to_new_rows = BTreeMap::new();
+ let mut language_indent_sizes_by_new_row = Vec::new();
for entry in &request.entries {
let position = entry.range.start;
let new_row = position.to_point(&snapshot).row;
let new_end_row = entry.range.end.to_point(&snapshot).row + 1;
+ language_indent_sizes_by_new_row.push((new_row, entry.indent_size));
+
if !entry.first_line_is_new {
let old_row = position.to_point(&request.before_edit).row;
old_to_new_rows.insert(old_row, new_row);
@@ -883,6 +897,8 @@ impl Buffer {
let mut old_suggestions = BTreeMap::<u32, IndentSize>::default();
let old_edited_ranges =
contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields);
+ let mut language_indent_sizes = language_indent_sizes_by_new_row.iter().peekable();
+ let mut language_indent_size = IndentSize::default();
for old_edited_range in old_edited_ranges {
let suggestions = request
.before_edit
@@ -891,6 +907,17 @@ impl Buffer {
.flatten();
for (old_row, suggestion) in old_edited_range.zip(suggestions) {
if let Some(suggestion) = suggestion {
+ let new_row = *old_to_new_rows.get(&old_row).unwrap();
+
+ // Find the indent size based on the language for this row.
+ while let Some((row, size)) = language_indent_sizes.peek() {
+ if *row > new_row {
+ break;
+ }
+ language_indent_size = *size;
+ language_indent_sizes.next();
+ }
+
let suggested_indent = old_to_new_rows
.get(&suggestion.basis_row)
.and_then(|from_row| old_suggestions.get(from_row).copied())
@@ -899,9 +926,8 @@ impl Buffer {
.before_edit
.indent_size_for_line(suggestion.basis_row)
})
- .with_delta(suggestion.delta, request.indent_size);
- old_suggestions
- .insert(*old_to_new_rows.get(&old_row).unwrap(), suggested_indent);
+ .with_delta(suggestion.delta, language_indent_size);
+ old_suggestions.insert(new_row, suggested_indent);
}
}
yield_now().await;
@@ -922,6 +948,8 @@ impl Buffer {
// Compute new suggestions for each line, but only include them in the result
// if they differ from the old suggestion for that line.
+ let mut language_indent_sizes = language_indent_sizes_by_new_row.iter().peekable();
+ let mut language_indent_size = IndentSize::default();
for new_edited_row_range in new_edited_row_ranges {
let suggestions = snapshot
.suggest_autoindents(new_edited_row_range.clone())
@@ -929,13 +957,22 @@ impl Buffer {
.flatten();
for (new_row, suggestion) in new_edited_row_range.zip(suggestions) {
if let Some(suggestion) = suggestion {
+ // Find the indent size based on the language for this row.
+ while let Some((row, size)) = language_indent_sizes.peek() {
+ if *row > new_row {
+ break;
+ }
+ language_indent_size = *size;
+ language_indent_sizes.next();
+ }
+
let suggested_indent = indent_sizes
.get(&suggestion.basis_row)
.copied()
.unwrap_or_else(|| {
snapshot.indent_size_for_line(suggestion.basis_row)
})
- .with_delta(suggestion.delta, request.indent_size);
+ .with_delta(suggestion.delta, language_indent_size);
if old_suggestions
.get(&new_row)
.map_or(true, |old_indentation| {
@@ -1266,7 +1303,6 @@ impl Buffer {
let edit_id = edit_operation.local_timestamp();
if let Some((before_edit, mode)) = autoindent_request {
- let indent_size = before_edit.single_indent_size(cx);
let (start_columns, is_block_mode) = match mode {
AutoindentMode::Block {
original_indent_columns: start_columns,
@@ -1315,6 +1351,7 @@ impl Buffer {
AutoindentRequestEntry {
first_line_is_new,
original_indent_column: start_column,
+ indent_size: before_edit.language_indent_size_at(range.start, cx),
range: self.anchor_before(new_start + range_of_insertion_to_indent.start)
..self.anchor_after(new_start + range_of_insertion_to_indent.end),
}
@@ -1324,7 +1361,6 @@ impl Buffer {
self.autoindent_requests.push(Arc::new(AutoindentRequest {
before_edit,
entries,
- indent_size,
is_block_mode,
}));
}
@@ -1642,8 +1678,8 @@ impl BufferSnapshot {
indent_size_for_line(self, row)
}
- pub fn single_indent_size(&self, cx: &AppContext) -> IndentSize {
- let language_name = self.language().map(|language| language.name());
+ pub fn language_indent_size_at<T: ToOffset>(&self, position: T, cx: &AppContext) -> IndentSize {
+ let language_name = self.language_at(position).map(|language| language.name());
let settings = cx.global::<Settings>();
if settings.hard_tabs(language_name.as_deref()) {
IndentSize::tab()
@@ -1713,6 +1749,8 @@ impl BufferSnapshot {
if capture.index == config.indent_capture_ix {
start.get_or_insert(Point::from_ts_point(capture.node.start_position()));
end.get_or_insert(Point::from_ts_point(capture.node.end_position()));
+ } else if Some(capture.index) == config.start_capture_ix {
+ start = Some(Point::from_ts_point(capture.node.end_position()));
} else if Some(capture.index) == config.end_capture_ix {
end = Some(Point::from_ts_point(capture.node.start_position()));
}
@@ -1902,8 +1940,14 @@ impl BufferSnapshot {
}
}
- pub fn language(&self) -> Option<&Arc<Language>> {
- self.language.as_ref()
+ pub fn language_at<D: ToOffset>(&self, position: D) -> Option<&Arc<Language>> {
+ let offset = position.to_offset(self);
+ self.syntax
+ .layers_for_range(offset..offset, &self.text)
+ .filter(|l| l.node.end_byte() > offset)
+ .last()
+ .map(|info| info.language)
+ .or(self.language.as_ref())
}
pub fn surrounding_word<T: ToOffset>(&self, start: T) -> (Range<usize>, Option<CharKind>) {
@@ -1938,8 +1982,8 @@ impl BufferSnapshot {
pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut result: Option<Range<usize>> = None;
- 'outer: for (_, _, node) in self.syntax.layers_for_range(range.clone(), &self.text) {
- let mut cursor = node.walk();
+ 'outer: for layer in self.syntax.layers_for_range(range.clone(), &self.text) {
+ let mut cursor = layer.node.walk();
// Descend to the first leaf that touches the start of the range,
// and if the range is non-empty, extends beyond the start.
@@ -26,6 +26,7 @@ use serde_json::Value;
use std::{
any::Any,
cell::RefCell,
+ fmt::Debug,
mem,
ops::Range,
path::{Path, PathBuf},
@@ -135,7 +136,7 @@ impl CachedLspAdapter {
pub async fn label_for_completion(
&self,
completion_item: &lsp::CompletionItem,
- language: &Language,
+ language: &Arc<Language>,
) -> Option<CodeLabel> {
self.adapter
.label_for_completion(completion_item, language)
@@ -146,7 +147,7 @@ impl CachedLspAdapter {
&self,
name: &str,
kind: lsp::SymbolKind,
- language: &Language,
+ language: &Arc<Language>,
) -> Option<CodeLabel> {
self.adapter.label_for_symbol(name, kind, language).await
}
@@ -175,7 +176,7 @@ pub trait LspAdapter: 'static + Send + Sync {
async fn label_for_completion(
&self,
_: &lsp::CompletionItem,
- _: &Language,
+ _: &Arc<Language>,
) -> Option<CodeLabel> {
None
}
@@ -184,7 +185,7 @@ pub trait LspAdapter: 'static + Send + Sync {
&self,
_: &str,
_: lsp::SymbolKind,
- _: &Language,
+ _: &Arc<Language>,
) -> Option<CodeLabel> {
None
}
@@ -230,7 +231,10 @@ pub struct LanguageConfig {
pub decrease_indent_pattern: Option<Regex>,
#[serde(default)]
pub autoclose_before: String,
- pub line_comment: Option<String>,
+ #[serde(default)]
+ pub line_comment: Option<Arc<str>>,
+ #[serde(default)]
+ pub block_comment: Option<(Arc<str>, Arc<str>)>,
}
impl Default for LanguageConfig {
@@ -244,6 +248,7 @@ impl Default for LanguageConfig {
decrease_indent_pattern: Default::default(),
autoclose_before: Default::default(),
line_comment: Default::default(),
+ block_comment: Default::default(),
}
}
}
@@ -270,7 +275,7 @@ pub struct FakeLspAdapter {
pub disk_based_diagnostics_sources: Vec<String>,
}
-#[derive(Clone, Debug, Deserialize)]
+#[derive(Clone, Debug, Default, Deserialize)]
pub struct BracketPair {
pub start: String,
pub end: String,
@@ -304,6 +309,7 @@ pub struct Grammar {
struct IndentConfig {
query: Query,
indent_capture_ix: u32,
+ start_capture_ix: Option<u32>,
end_capture_ix: Option<u32>,
}
@@ -661,11 +667,13 @@ impl Language {
let grammar = self.grammar_mut();
let query = Query::new(grammar.ts_language, source)?;
let mut indent_capture_ix = None;
+ let mut start_capture_ix = None;
let mut end_capture_ix = None;
get_capture_indices(
&query,
&mut [
("indent", &mut indent_capture_ix),
+ ("start", &mut start_capture_ix),
("end", &mut end_capture_ix),
],
);
@@ -673,6 +681,7 @@ impl Language {
grammar.indents_config = Some(IndentConfig {
query,
indent_capture_ix,
+ start_capture_ix,
end_capture_ix,
});
}
@@ -763,8 +772,15 @@ impl Language {
self.config.name.clone()
}
- pub fn line_comment_prefix(&self) -> Option<&str> {
- self.config.line_comment.as_deref()
+ pub fn line_comment_prefix(&self) -> Option<&Arc<str>> {
+ self.config.line_comment.as_ref()
+ }
+
+ pub fn block_comment_delimiters(&self) -> Option<(&Arc<str>, &Arc<str>)> {
+ self.config
+ .block_comment
+ .as_ref()
+ .map(|(start, end)| (start, end))
}
pub async fn disk_based_diagnostic_sources(&self) -> &[String] {
@@ -789,7 +805,7 @@ impl Language {
}
pub async fn label_for_completion(
- &self,
+ self: &Arc<Self>,
completion: &lsp::CompletionItem,
) -> Option<CodeLabel> {
self.adapter
@@ -798,7 +814,11 @@ impl Language {
.await
}
- pub async fn label_for_symbol(&self, name: &str, kind: lsp::SymbolKind) -> Option<CodeLabel> {
+ pub async fn label_for_symbol(
+ self: &Arc<Self>,
+ name: &str,
+ kind: lsp::SymbolKind,
+ ) -> Option<CodeLabel> {
self.adapter
.as_ref()?
.label_for_symbol(name, kind, self)
@@ -806,20 +826,17 @@ impl Language {
}
pub fn highlight_text<'a>(
- &'a self,
+ self: &'a Arc<Self>,
text: &'a Rope,
range: Range<usize>,
) -> Vec<(Range<usize>, HighlightId)> {
let mut result = Vec::new();
if let Some(grammar) = &self.grammar {
let tree = grammar.parse_text(text, None);
- let captures = SyntaxSnapshot::single_tree_captures(
- range.clone(),
- text,
- &tree,
- grammar,
- |grammar| grammar.highlights_query.as_ref(),
- );
+ let captures =
+ SyntaxSnapshot::single_tree_captures(range.clone(), text, &tree, self, |grammar| {
+ grammar.highlights_query.as_ref()
+ });
let highlight_maps = vec![grammar.highlight_map()];
let mut offset = 0;
for chunk in BufferChunks::new(text, range, Some((captures, highlight_maps)), vec![]) {
@@ -861,6 +878,14 @@ impl Language {
}
}
+impl Debug for Language {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("Language")
+ .field("name", &self.config.name)
+ .finish()
+ }
+}
+
impl Grammar {
pub fn id(&self) -> usize {
self.id
@@ -92,6 +92,13 @@ struct SyntaxLayer {
language: Arc<Language>,
}
+#[derive(Debug)]
+pub struct SyntaxLayerInfo<'a> {
+ pub depth: usize,
+ pub node: Node<'a>,
+ pub language: &'a Arc<Language>,
+}
+
#[derive(Debug, Clone)]
struct SyntaxLayerSummary {
min_depth: usize,
@@ -473,13 +480,18 @@ impl SyntaxSnapshot {
range: Range<usize>,
text: &'a Rope,
tree: &'a Tree,
- grammar: &'a Grammar,
+ language: &'a Arc<Language>,
query: fn(&Grammar) -> Option<&Query>,
) -> SyntaxMapCaptures<'a> {
SyntaxMapCaptures::new(
range.clone(),
text,
- [(grammar, 0, tree.root_node())].into_iter(),
+ [SyntaxLayerInfo {
+ language,
+ depth: 0,
+ node: tree.root_node(),
+ }]
+ .into_iter(),
query,
)
}
@@ -513,19 +525,19 @@ impl SyntaxSnapshot {
}
#[cfg(test)]
- pub fn layers(&self, buffer: &BufferSnapshot) -> Vec<(&Grammar, usize, Node)> {
- self.layers_for_range(0..buffer.len(), buffer)
+ pub fn layers<'a>(&'a self, buffer: &'a BufferSnapshot) -> Vec<SyntaxLayerInfo> {
+ self.layers_for_range(0..buffer.len(), buffer).collect()
}
pub fn layers_for_range<'a, T: ToOffset>(
- &self,
+ &'a self,
range: Range<T>,
- buffer: &BufferSnapshot,
- ) -> Vec<(&Grammar, usize, Node)> {
+ buffer: &'a BufferSnapshot,
+ ) -> impl 'a + Iterator<Item = SyntaxLayerInfo> {
let start = buffer.anchor_before(range.start.to_offset(buffer));
let end = buffer.anchor_after(range.end.to_offset(buffer));
- let mut cursor = self.layers.filter::<_, ()>(|summary| {
+ let mut cursor = self.layers.filter::<_, ()>(move |summary| {
if summary.max_depth > summary.min_depth {
true
} else {
@@ -535,23 +547,26 @@ impl SyntaxSnapshot {
}
});
- let mut result = Vec::new();
+ // let mut result = Vec::new();
cursor.next(buffer);
- while let Some(layer) = cursor.item() {
- if let Some(grammar) = &layer.language.grammar {
- result.push((
- grammar.as_ref(),
- layer.depth,
- layer.tree.root_node_with_offset(
+ std::iter::from_fn(move || {
+ if let Some(layer) = cursor.item() {
+ let info = SyntaxLayerInfo {
+ language: &layer.language,
+ depth: layer.depth,
+ node: layer.tree.root_node_with_offset(
layer.range.start.to_offset(buffer),
layer.range.start.to_point(buffer).to_ts_point(),
),
- ));
+ };
+ cursor.next(buffer);
+ Some(info)
+ } else {
+ None
}
- cursor.next(buffer)
- }
+ })
- result
+ // result
}
}
@@ -559,7 +574,7 @@ impl<'a> SyntaxMapCaptures<'a> {
fn new(
range: Range<usize>,
text: &'a Rope,
- layers: impl Iterator<Item = (&'a Grammar, usize, Node<'a>)>,
+ layers: impl Iterator<Item = SyntaxLayerInfo<'a>>,
query: fn(&Grammar) -> Option<&Query>,
) -> Self {
let mut result = Self {
@@ -567,11 +582,19 @@ impl<'a> SyntaxMapCaptures<'a> {
grammars: Vec::new(),
active_layer_count: 0,
};
- for (grammar, depth, node) in layers {
- let query = if let Some(query) = query(grammar) {
- query
- } else {
- continue;
+ for SyntaxLayerInfo {
+ language,
+ depth,
+ node,
+ } in layers
+ {
+ let grammar = match &language.grammar {
+ Some(grammer) => grammer,
+ None => continue,
+ };
+ let query = match query(&grammar) {
+ Some(query) => query,
+ None => continue,
};
let mut query_cursor = QueryCursorHandle::new();
@@ -678,15 +701,23 @@ impl<'a> SyntaxMapMatches<'a> {
fn new(
range: Range<usize>,
text: &'a Rope,
- layers: impl Iterator<Item = (&'a Grammar, usize, Node<'a>)>,
+ layers: impl Iterator<Item = SyntaxLayerInfo<'a>>,
query: fn(&Grammar) -> Option<&Query>,
) -> Self {
let mut result = Self::default();
- for (grammar, depth, node) in layers {
- let query = if let Some(query) = query(grammar) {
- query
- } else {
- continue;
+ for SyntaxLayerInfo {
+ language,
+ depth,
+ node,
+ } in layers
+ {
+ let grammar = match &language.grammar {
+ Some(grammer) => grammer,
+ None => continue,
+ };
+ let query = match query(&grammar) {
+ Some(query) => query,
+ None => continue,
};
let mut query_cursor = QueryCursorHandle::new();
@@ -1624,8 +1655,8 @@ mod tests {
let reference_layers = reference_syntax_map.layers(&buffer);
for (edited_layer, reference_layer) in layers.into_iter().zip(reference_layers.into_iter())
{
- assert_eq!(edited_layer.2.to_sexp(), reference_layer.2.to_sexp());
- assert_eq!(edited_layer.2.range(), reference_layer.2.range());
+ assert_eq!(edited_layer.node.to_sexp(), reference_layer.node.to_sexp());
+ assert_eq!(edited_layer.node.range(), reference_layer.node.range());
}
}
@@ -1770,13 +1801,13 @@ mod tests {
mutated_layers.into_iter().zip(reference_layers.into_iter())
{
assert_eq!(
- edited_layer.2.to_sexp(),
- reference_layer.2.to_sexp(),
+ edited_layer.node.to_sexp(),
+ reference_layer.node.to_sexp(),
"different layer at step {i}"
);
assert_eq!(
- edited_layer.2.range(),
- reference_layer.2.range(),
+ edited_layer.node.range(),
+ reference_layer.node.range(),
"different layer at step {i}"
);
}
@@ -1822,13 +1853,15 @@ mod tests {
range: Range<Point>,
expected_layers: &[&str],
) {
- let layers = syntax_map.layers_for_range(range, &buffer);
+ let layers = syntax_map
+ .layers_for_range(range, &buffer)
+ .collect::<Vec<_>>();
assert_eq!(
layers.len(),
expected_layers.len(),
"wrong number of layers"
);
- for (i, ((_, _, node), expected_s_exp)) in
+ for (i, (SyntaxLayerInfo { node, .. }, expected_s_exp)) in
layers.iter().zip(expected_layers.iter()).enumerate()
{
let actual_s_exp = node.to_sexp();
@@ -14,7 +14,7 @@ use std::{
};
use text::network::Network;
use unindent::Unindent as _;
-use util::post_inc;
+use util::{post_inc, test::marked_text_ranges};
#[cfg(test)]
#[ctor::ctor]
@@ -1035,6 +1035,120 @@ fn test_autoindent_language_without_indents_query(cx: &mut MutableAppContext) {
});
}
+#[gpui::test]
+fn test_autoindent_with_injected_languages(cx: &mut MutableAppContext) {
+ cx.set_global({
+ let mut settings = Settings::test(cx);
+ settings.language_overrides.extend([
+ (
+ "HTML".into(),
+ settings::EditorSettings {
+ tab_size: Some(2.try_into().unwrap()),
+ ..Default::default()
+ },
+ ),
+ (
+ "JavaScript".into(),
+ settings::EditorSettings {
+ tab_size: Some(8.try_into().unwrap()),
+ ..Default::default()
+ },
+ ),
+ ]);
+ settings
+ });
+
+ let html_language = Arc::new(
+ Language::new(
+ LanguageConfig {
+ name: "HTML".into(),
+ ..Default::default()
+ },
+ Some(tree_sitter_html::language()),
+ )
+ .with_indents_query(
+ "
+ (element
+ (start_tag) @start
+ (end_tag)? @end) @indent
+ ",
+ )
+ .unwrap()
+ .with_injection_query(
+ r#"
+ (script_element
+ (raw_text) @content
+ (#set! "language" "javascript"))
+ "#,
+ )
+ .unwrap(),
+ );
+
+ let javascript_language = Arc::new(
+ Language::new(
+ LanguageConfig {
+ name: "JavaScript".into(),
+ ..Default::default()
+ },
+ Some(tree_sitter_javascript::language()),
+ )
+ .with_indents_query(
+ r#"
+ (object "}" @end) @indent
+ "#,
+ )
+ .unwrap(),
+ );
+
+ let language_registry = Arc::new(LanguageRegistry::test());
+ language_registry.add(html_language.clone());
+ language_registry.add(javascript_language.clone());
+
+ cx.add_model(|cx| {
+ let (text, ranges) = marked_text_ranges(
+ &"
+ <div>ˇ
+ </div>
+ <script>
+ init({ˇ
+ })
+ </script>
+ <span>ˇ
+ </span>
+ "
+ .unindent(),
+ false,
+ );
+
+ let mut buffer = Buffer::new(0, text, cx);
+ buffer.set_language_registry(language_registry);
+ buffer.set_language(Some(html_language), cx);
+ buffer.edit(
+ ranges.into_iter().map(|range| (range, "\na")),
+ Some(AutoindentMode::EachLine),
+ cx,
+ );
+ assert_eq!(
+ buffer.text(),
+ "
+ <div>
+ a
+ </div>
+ <script>
+ init({
+ a
+ })
+ </script>
+ <span>
+ a
+ </span>
+ "
+ .unindent()
+ );
+ buffer
+ });
+}
+
#[gpui::test]
fn test_serialization(cx: &mut gpui::MutableAppContext) {
let mut now = Instant::now();
@@ -1449,7 +1563,7 @@ fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> Str
buffer.read_with(cx, |buffer, _| {
let snapshot = buffer.snapshot();
let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
- layers[0].2.to_sexp()
+ layers[0].node.to_sexp()
})
}
@@ -92,6 +92,7 @@ toml = "0.5"
tree-sitter = "0.20"
tree-sitter-c = "0.20.1"
tree-sitter-cpp = "0.20.0"
+tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "05e3631c6a0701c1fa518b0fee7be95a2ceef5e2" }
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8" }
@@ -100,6 +101,7 @@ tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown",
tree-sitter-python = "0.20.2"
tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" }
tree-sitter-typescript = "0.20.1"
+tree-sitter-html = "0.19.0"
url = "2.2"
[dev-dependencies]
@@ -7,6 +7,7 @@ use std::{borrow::Cow, str, sync::Arc};
mod c;
mod elixir;
mod go;
+mod html;
mod installation;
mod json;
mod language_plugin;
@@ -46,6 +47,11 @@ pub async fn init(languages: Arc<LanguageRegistry>, _executor: Arc<Background>)
tree_sitter_cpp::language(),
Some(CachedLspAdapter::new(c::CLspAdapter).await),
),
+ (
+ "css",
+ tree_sitter_css::language(),
+ None, //
+ ),
(
"elixir",
tree_sitter_elixir::language(),
@@ -96,8 +102,13 @@ pub async fn init(languages: Arc<LanguageRegistry>, _executor: Arc<Background>)
tree_sitter_typescript::language_tsx(),
Some(CachedLspAdapter::new(typescript::TypeScriptLspAdapter).await),
),
+ (
+ "html",
+ tree_sitter_html::language(),
+ Some(CachedLspAdapter::new(html::HtmlLspAdapter).await),
+ ),
] {
- languages.add(Arc::new(language(name, grammar, lsp_adapter)));
+ languages.add(language(name, grammar, lsp_adapter));
}
}
@@ -105,7 +116,7 @@ pub(crate) fn language(
name: &str,
grammar: tree_sitter::Language,
lsp_adapter: Option<Arc<CachedLspAdapter>>,
-) -> Language {
+) -> Arc<Language> {
let config = toml::from_slice(
&LanguageDir::get(&format!("{}/config.toml", name))
.unwrap()
@@ -142,7 +153,7 @@ pub(crate) fn language(
if let Some(lsp_adapter) = lsp_adapter {
language = language.with_lsp_adapter(lsp_adapter)
}
- language
+ Arc::new(language)
}
fn load_query(name: &str, filename_prefix: &str) -> Option<Cow<'static, str>> {
@@ -112,7 +112,7 @@ impl super::LspAdapter for CLspAdapter {
async fn label_for_completion(
&self,
completion: &lsp::CompletionItem,
- language: &Language,
+ language: &Arc<Language>,
) -> Option<CodeLabel> {
let label = completion
.label
@@ -190,7 +190,7 @@ impl super::LspAdapter for CLspAdapter {
&self,
name: &str,
kind: lsp::SymbolKind,
- language: &Language,
+ language: &Arc<Language>,
) -> Option<CodeLabel> {
let (text, filter_range, display_range) = match kind {
lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
@@ -251,7 +251,6 @@ mod tests {
use gpui::MutableAppContext;
use language::{AutoindentMode, Buffer};
use settings::Settings;
- use std::sync::Arc;
#[gpui::test]
fn test_c_autoindent(cx: &mut MutableAppContext) {
@@ -262,7 +261,7 @@ mod tests {
let language = crate::languages::language("c", tree_sitter_c::language(), None);
cx.add_model(|cx| {
- let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(language), cx);
+ let mut buffer = Buffer::new(0, "", cx).with_language(language, cx);
// empty function
buffer.edit([(0..0, "int main() {}")], None, cx);
@@ -0,0 +1,3 @@
+("(" @open ")" @close)
+("[" @open "]" @close)
+("{" @open "}" @close)
@@ -0,0 +1,9 @@
+name = "CSS"
+path_suffixes = ["css"]
+autoclose_before = ";:.,=}])>"
+brackets = [
+ { start = "{", end = "}", close = true, newline = true },
+ { start = "[", end = "]", close = true, newline = true },
+ { start = "(", end = ")", close = true, newline = true },
+ { start = "\"", end = "\"", close = true, newline = false }
+]
@@ -0,0 +1,76 @@
+(comment) @comment
+
+[
+ (tag_name)
+ (nesting_selector)
+ (universal_selector)
+] @tag
+
+[
+ "~"
+ ">"
+ "+"
+ "-"
+ "*"
+ "/"
+ "="
+ "^="
+ "|="
+ "~="
+ "$="
+ "*="
+ "and"
+ "or"
+ "not"
+ "only"
+] @operator
+
+(attribute_selector (plain_value) @string)
+
+(attribute_name) @attribute
+(pseudo_element_selector (tag_name) @attribute)
+(pseudo_class_selector (class_name) @attribute)
+
+[
+ (class_name)
+ (id_name)
+ (namespace_name)
+ (property_name)
+ (feature_name)
+] @property
+
+(function_name) @function
+
+((property_name) @variable
+ (#match? @variable "^--"))
+((plain_value) @variable
+ (#match? @variable "^--"))
+
+[
+ "@media"
+ "@import"
+ "@charset"
+ "@namespace"
+ "@supports"
+ "@keyframes"
+ (at_keyword)
+ (to)
+ (from)
+ (important)
+] @keyword
+
+(string_value) @string
+(color_value) @string.special
+
+[
+ (integer_value)
+ (float_value)
+] @number
+
+(unit) @type
+
+[
+ "#"
+ ","
+ ":"
+] @punctuation.delimiter
@@ -0,0 +1 @@
+(_ "{" "}" @end) @indent
@@ -113,7 +113,7 @@ impl LspAdapter for ElixirLspAdapter {
async fn label_for_completion(
&self,
completion: &lsp::CompletionItem,
- language: &Language,
+ language: &Arc<Language>,
) -> Option<CodeLabel> {
match completion.kind.zip(completion.detail.as_ref()) {
Some((_, detail)) if detail.starts_with("(function)") => {
@@ -168,7 +168,7 @@ impl LspAdapter for ElixirLspAdapter {
&self,
name: &str,
kind: SymbolKind,
- language: &Language,
+ language: &Arc<Language>,
) -> Option<CodeLabel> {
let (text, filter_range, display_range) = match kind {
SymbolKind::METHOD | SymbolKind::FUNCTION => {
@@ -134,7 +134,7 @@ impl super::LspAdapter for GoLspAdapter {
async fn label_for_completion(
&self,
completion: &lsp::CompletionItem,
- language: &Language,
+ language: &Arc<Language>,
) -> Option<CodeLabel> {
let label = &completion.label;
@@ -235,7 +235,7 @@ impl super::LspAdapter for GoLspAdapter {
&self,
name: &str,
kind: lsp::SymbolKind,
- language: &Language,
+ language: &Arc<Language>,
) -> Option<CodeLabel> {
let (text, filter_range, display_range) = match kind {
lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
@@ -0,0 +1,101 @@
+use super::installation::{npm_install_packages, npm_package_latest_version};
+use anyhow::{anyhow, Context, Result};
+use async_trait::async_trait;
+use client::http::HttpClient;
+use futures::StreamExt;
+use language::{LanguageServerName, LspAdapter};
+use serde_json::json;
+use smol::fs;
+use std::{any::Any, path::PathBuf, sync::Arc};
+use util::ResultExt;
+
+pub struct HtmlLspAdapter;
+
+impl HtmlLspAdapter {
+ const BIN_PATH: &'static str =
+ "node_modules/vscode-langservers-extracted/bin/vscode-html-language-server";
+}
+
+#[async_trait]
+impl LspAdapter for HtmlLspAdapter {
+ async fn name(&self) -> LanguageServerName {
+ LanguageServerName("vscode-html-language-server".into())
+ }
+
+ async fn server_args(&self) -> Vec<String> {
+ vec!["--stdio".into()]
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ _: Arc<dyn HttpClient>,
+ ) -> Result<Box<dyn 'static + Any + Send>> {
+ Ok(Box::new(npm_package_latest_version("vscode-langservers-extracted").await?) as Box<_>)
+ }
+
+ async fn fetch_server_binary(
+ &self,
+ version: Box<dyn 'static + Send + Any>,
+ _: Arc<dyn HttpClient>,
+ container_dir: PathBuf,
+ ) -> Result<PathBuf> {
+ let version = version.downcast::<String>().unwrap();
+ let version_dir = container_dir.join(version.as_str());
+ fs::create_dir_all(&version_dir)
+ .await
+ .context("failed to create version directory")?;
+ let binary_path = version_dir.join(Self::BIN_PATH);
+
+ if fs::metadata(&binary_path).await.is_err() {
+ npm_install_packages(
+ [("vscode-langservers-extracted", version.as_str())],
+ &version_dir,
+ )
+ .await?;
+
+ if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() {
+ while let Some(entry) = entries.next().await {
+ if let Some(entry) = entry.log_err() {
+ let entry_path = entry.path();
+ if entry_path.as_path() != version_dir {
+ fs::remove_dir_all(&entry_path).await.log_err();
+ }
+ }
+ }
+ }
+ }
+
+ Ok(binary_path)
+ }
+
+ async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+ (|| 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 bin_path = last_version_dir.join(Self::BIN_PATH);
+ if bin_path.exists() {
+ Ok(bin_path)
+ } else {
+ Err(anyhow!(
+ "missing executable in directory {:?}",
+ last_version_dir
+ ))
+ }
+ })()
+ .await
+ .log_err()
+ }
+
+ async fn initialization_options(&self) -> Option<serde_json::Value> {
+ Some(json!({
+ "provideFormatter": true
+ }))
+ }
+}
@@ -0,0 +1,2 @@
+("<" @open ">" @close)
+("\"" @open "\"" @close)
@@ -0,0 +1,12 @@
+name = "HTML"
+path_suffixes = ["html"]
+autoclose_before = ">})"
+brackets = [
+ { start = "<", end = ">", close = true, newline = true },
+ { start = "{", end = "}", close = true, newline = true },
+ { start = "(", end = ")", close = true, newline = true },
+ { start = "\"", end = "\"", close = true, newline = false },
+ { start = "!--", end = " --", close = true, newline = false },
+]
+
+block_comment = ["<!-- ", " -->"]
@@ -0,0 +1,15 @@
+(tag_name) @keyword
+(erroneous_end_tag_name) @keyword
+(doctype) @constant
+(attribute_name) @property
+(attribute_value) @string
+(comment) @comment
+
+"=" @operator
+
+[
+ "<"
+ ">"
+ "</"
+ "/>"
+] @punctuation.bracket
@@ -0,0 +1,6 @@
+(start_tag ">" @end) @indent
+(self_closing_tag "/>" @end) @indent
+
+(element
+ (start_tag) @start
+ (end_tag)? @end) @indent
@@ -0,0 +1,7 @@
+(script_element
+ (raw_text) @content
+ (#set! "language" "javascript"))
+
+(style_element
+ (raw_text) @content
+ (#set! "language" "css"))
@@ -90,7 +90,7 @@ impl LspAdapter for PythonLspAdapter {
async fn label_for_completion(
&self,
item: &lsp::CompletionItem,
- language: &language::Language,
+ language: &Arc<language::Language>,
) -> Option<language::CodeLabel> {
let label = &item.label;
let grammar = language.grammar()?;
@@ -112,7 +112,7 @@ impl LspAdapter for PythonLspAdapter {
&self,
name: &str,
kind: lsp::SymbolKind,
- language: &language::Language,
+ language: &Arc<language::Language>,
) -> Option<language::CodeLabel> {
let (text, filter_range, display_range) = match kind {
lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
@@ -149,7 +149,6 @@ mod tests {
use gpui::{ModelContext, MutableAppContext};
use language::{AutoindentMode, Buffer};
use settings::Settings;
- use std::sync::Arc;
#[gpui::test]
fn test_python_autoindent(cx: &mut MutableAppContext) {
@@ -160,7 +159,7 @@ mod tests {
cx.set_global(settings);
cx.add_model(|cx| {
- let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(language), cx);
+ let mut buffer = Buffer::new(0, "", cx).with_language(language, cx);
let append = |buffer: &mut Buffer, text: &str, cx: &mut ModelContext<Buffer>| {
let ix = buffer.len();
buffer.edit([(ix..ix, text)], Some(AutoindentMode::EachLine), cx);
@@ -119,7 +119,7 @@ impl LspAdapter for RustLspAdapter {
async fn label_for_completion(
&self,
completion: &lsp::CompletionItem,
- language: &Language,
+ language: &Arc<Language>,
) -> Option<CodeLabel> {
match completion.kind {
Some(lsp::CompletionItemKind::FIELD) if completion.detail.is_some() => {
@@ -196,7 +196,7 @@ impl LspAdapter for RustLspAdapter {
&self,
name: &str,
kind: lsp::SymbolKind,
- language: &Language,
+ language: &Arc<Language>,
) -> Option<CodeLabel> {
let (text, filter_range, display_range) = match kind {
lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
@@ -439,7 +439,7 @@ mod tests {
cx.set_global(settings);
cx.add_model(|cx| {
- let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(language), cx);
+ let mut buffer = Buffer::new(0, "", cx).with_language(language, cx);
// indent between braces
buffer.set_text("fn a() {}", cx);
@@ -115,7 +115,7 @@ impl LspAdapter for TypeScriptLspAdapter {
async fn label_for_completion(
&self,
item: &lsp::CompletionItem,
- language: &language::Language,
+ language: &Arc<language::Language>,
) -> Option<language::CodeLabel> {
use lsp::CompletionItemKind as Kind;
let len = item.label.len();
@@ -144,7 +144,6 @@ impl LspAdapter for TypeScriptLspAdapter {
#[cfg(test)]
mod tests {
- use std::sync::Arc;
use gpui::MutableAppContext;
use unindent::Unindent;
@@ -172,9 +171,8 @@ mod tests {
"#
.unindent();
- let buffer = cx.add_model(|cx| {
- language::Buffer::new(0, text, cx).with_language(Arc::new(language), cx)
- });
+ let buffer =
+ cx.add_model(|cx| language::Buffer::new(0, text, cx).with_language(language, cx));
let outline = buffer.read(cx).snapshot().outline(None).unwrap();
assert_eq!(
outline
@@ -1133,7 +1133,7 @@ mod tests {
assert!(!editor.is_dirty(cx));
assert_eq!(editor.title(cx), "untitled");
assert!(Arc::ptr_eq(
- editor.language_at(0, cx).unwrap(),
+ &editor.language_at(0, cx).unwrap(),
&languages::PLAIN_TEXT
));
editor.handle_input("hi", cx);
@@ -1220,7 +1220,7 @@ mod tests {
editor.update(cx, |editor, cx| {
assert!(Arc::ptr_eq(
- editor.language_at(0, cx).unwrap(),
+ &editor.language_at(0, cx).unwrap(),
&languages::PLAIN_TEXT
));
editor.handle_input("hi", cx);