mirror of https://github.com/helix-editor/helix
Compare commits
13 Commits
c9a1c0c656
...
c7ad7f4162
Author | SHA1 | Date |
---|---|---|
|
c7ad7f4162 | |
|
205e7ece70 | |
|
1315b7e2b1 | |
|
52192ae29e | |
|
fba1a6188a | |
|
b90d8960a8 | |
|
6e4ec96101 | |
|
62f270e5d2 | |
|
70650ff55a | |
|
57d7b4fb4a | |
|
4b3aad7830 | |
|
8fbb9566fb | |
|
f6e1a2f599 |
|
@ -748,7 +748,7 @@ dependencies = [
|
||||||
"gix-trace",
|
"gix-trace",
|
||||||
"gix-traverse",
|
"gix-traverse",
|
||||||
"gix-worktree",
|
"gix-worktree",
|
||||||
"imara-diff",
|
"imara-diff 0.1.8",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1409,7 +1409,7 @@ dependencies = [
|
||||||
"helix-loader",
|
"helix-loader",
|
||||||
"helix-parsec",
|
"helix-parsec",
|
||||||
"helix-stdx",
|
"helix-stdx",
|
||||||
"imara-diff",
|
"imara-diff 0.2.0",
|
||||||
"indoc",
|
"indoc",
|
||||||
"log",
|
"log",
|
||||||
"nucleo",
|
"nucleo",
|
||||||
|
@ -1604,7 +1604,7 @@ dependencies = [
|
||||||
"gix",
|
"gix",
|
||||||
"helix-core",
|
"helix-core",
|
||||||
"helix-event",
|
"helix-event",
|
||||||
"imara-diff",
|
"imara-diff 0.2.0",
|
||||||
"log",
|
"log",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
@ -1848,6 +1848,16 @@ dependencies = [
|
||||||
"hashbrown 0.15.4",
|
"hashbrown 0.15.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "imara-diff"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f01d462f766df78ab820dd06f5eb700233c51f0f4c2e846520eaf4ba6aa5c5c"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.15.4",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.9.0"
|
version = "2.9.0"
|
||||||
|
|
|
@ -157,6 +157,7 @@ The following statusline elements can be configured:
|
||||||
| `display-progress-messages` | Display LSP progress messages below statusline[^1] | `false` |
|
| `display-progress-messages` | Display LSP progress messages below statusline[^1] | `false` |
|
||||||
| `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` |
|
| `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` |
|
||||||
| `display-inlay-hints` | Display inlay hints[^2] | `false` |
|
| `display-inlay-hints` | Display inlay hints[^2] | `false` |
|
||||||
|
| `inlay-hints-length-limit` | Maximum displayed length (non-zero number) of inlay hints | Unset by default |
|
||||||
| `display-color-swatches` | Show color swatches next to colors | `true` |
|
| `display-color-swatches` | Show color swatches next to colors | `true` |
|
||||||
| `display-signature-help-docs` | Display docs under signature help popup | `true` |
|
| `display-signature-help-docs` | Display docs under signature help popup | `true` |
|
||||||
| `snippets` | Enables snippet completions. Requires a server restart (`:lsp-restart`) to take effect after `:config-reload`/`:set`. | `true` |
|
| `snippets` | Enables snippet completions. Requires a server restart (`:lsp-restart`) to take effect after `:config-reload`/`:set`. | `true` |
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
| adl | ✓ | ✓ | ✓ | |
|
| adl | ✓ | ✓ | ✓ | |
|
||||||
| agda | ✓ | | | |
|
| agda | ✓ | | | |
|
||||||
| alloy | ✓ | | | |
|
| alloy | ✓ | | | |
|
||||||
| amber | ✓ | | | |
|
| amber | ✓ | | | `amber-lsp` |
|
||||||
| astro | ✓ | | | `astro-ls` |
|
| astro | ✓ | | | `astro-ls` |
|
||||||
| awk | ✓ | ✓ | | `awk-language-server` |
|
| awk | ✓ | ✓ | | `awk-language-server` |
|
||||||
| bash | ✓ | ✓ | ✓ | `bash-language-server` |
|
| bash | ✓ | ✓ | ✓ | `bash-language-server` |
|
||||||
|
|
|
@ -46,8 +46,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
|
|
||||||
imara-diff = "0.1.8"
|
imara-diff = "0.2.0"
|
||||||
|
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
|
|
||||||
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
|
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
|
||||||
|
|
|
@ -1,51 +1,22 @@
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use imara_diff::intern::InternedInput;
|
use imara_diff::{Algorithm, Diff, Hunk, IndentHeuristic, IndentLevel, InternedInput};
|
||||||
use imara_diff::Algorithm;
|
|
||||||
use ropey::RopeSlice;
|
use ropey::RopeSlice;
|
||||||
|
|
||||||
use crate::{ChangeSet, Rope, Tendril, Transaction};
|
use crate::{ChangeSet, Rope, Tendril, Transaction};
|
||||||
|
|
||||||
/// A `imara_diff::Sink` that builds a `ChangeSet` for a character diff of a hunk
|
struct ChangeSetBuilder<'a> {
|
||||||
struct CharChangeSetBuilder<'a> {
|
|
||||||
res: &'a mut ChangeSet,
|
|
||||||
hunk: &'a InternedInput<char>,
|
|
||||||
pos: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl imara_diff::Sink for CharChangeSetBuilder<'_> {
|
|
||||||
type Out = ();
|
|
||||||
fn process_change(&mut self, before: Range<u32>, after: Range<u32>) {
|
|
||||||
self.res.retain((before.start - self.pos) as usize);
|
|
||||||
self.res.delete(before.len());
|
|
||||||
self.pos = before.end;
|
|
||||||
|
|
||||||
let res = self.hunk.after[after.start as usize..after.end as usize]
|
|
||||||
.iter()
|
|
||||||
.map(|&token| self.hunk.interner[token])
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
self.res.insert(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish(self) -> Self::Out {
|
|
||||||
self.res.retain(self.hunk.before.len() - self.pos as usize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LineChangeSetBuilder<'a> {
|
|
||||||
res: ChangeSet,
|
res: ChangeSet,
|
||||||
after: RopeSlice<'a>,
|
after: RopeSlice<'a>,
|
||||||
file: &'a InternedInput<RopeSlice<'a>>,
|
file: &'a InternedInput<RopeSlice<'a>>,
|
||||||
current_hunk: InternedInput<char>,
|
current_hunk: InternedInput<char>,
|
||||||
|
char_diff: Diff,
|
||||||
pos: u32,
|
pos: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl imara_diff::Sink for LineChangeSetBuilder<'_> {
|
impl ChangeSetBuilder<'_> {
|
||||||
type Out = ChangeSet;
|
fn process_hunk(&mut self, before: Range<u32>, after: Range<u32>) {
|
||||||
|
|
||||||
fn process_change(&mut self, before: Range<u32>, after: Range<u32>) {
|
|
||||||
let len = self.file.before[self.pos as usize..before.start as usize]
|
let len = self.file.before[self.pos as usize..before.start as usize]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&it| self.file.interner[it].len_chars())
|
.map(|&it| self.file.interner[it].len_chars())
|
||||||
|
@ -109,25 +80,36 @@ impl imara_diff::Sink for LineChangeSetBuilder<'_> {
|
||||||
.flat_map(|&it| self.file.interner[it].chars());
|
.flat_map(|&it| self.file.interner[it].chars());
|
||||||
self.current_hunk.update_before(hunk_before);
|
self.current_hunk.update_before(hunk_before);
|
||||||
self.current_hunk.update_after(hunk_after);
|
self.current_hunk.update_after(hunk_after);
|
||||||
|
|
||||||
// the histogram heuristic does not work as well
|
// the histogram heuristic does not work as well
|
||||||
// for characters because the same characters often reoccur
|
// for characters because the same characters often reoccur
|
||||||
// use myer diff instead
|
// use myer diff instead
|
||||||
imara_diff::diff(
|
self.char_diff.compute_with(
|
||||||
Algorithm::Myers,
|
Algorithm::Myers,
|
||||||
&self.current_hunk,
|
&self.current_hunk.before,
|
||||||
CharChangeSetBuilder {
|
&self.current_hunk.after,
|
||||||
res: &mut self.res,
|
self.current_hunk.interner.num_tokens(),
|
||||||
hunk: &self.current_hunk,
|
|
||||||
pos: 0,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
let mut pos = 0;
|
||||||
|
for Hunk { before, after } in self.char_diff.hunks() {
|
||||||
|
self.res.retain((before.start - pos) as usize);
|
||||||
|
self.res.delete(before.len());
|
||||||
|
pos = before.end;
|
||||||
|
|
||||||
|
let res = self.current_hunk.after[after.start as usize..after.end as usize]
|
||||||
|
.iter()
|
||||||
|
.map(|&token| self.current_hunk.interner[token])
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
self.res.insert(res);
|
||||||
|
}
|
||||||
|
self.res
|
||||||
|
.retain(self.current_hunk.before.len() - pos as usize);
|
||||||
|
// reuse allocations
|
||||||
self.current_hunk.clear();
|
self.current_hunk.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(mut self) -> Self::Out {
|
fn finish(mut self) -> ChangeSet {
|
||||||
let len = self.file.before[self.pos as usize..]
|
let len = self.file.before[self.pos as usize..]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&it| self.file.interner[it].len_chars())
|
.map(|&it| self.file.interner[it].len_chars())
|
||||||
|
@ -140,7 +122,7 @@ impl imara_diff::Sink for LineChangeSetBuilder<'_> {
|
||||||
|
|
||||||
struct RopeLines<'a>(RopeSlice<'a>);
|
struct RopeLines<'a>(RopeSlice<'a>);
|
||||||
|
|
||||||
impl<'a> imara_diff::intern::TokenSource for RopeLines<'a> {
|
impl<'a> imara_diff::TokenSource for RopeLines<'a> {
|
||||||
type Token = RopeSlice<'a>;
|
type Token = RopeSlice<'a>;
|
||||||
type Tokenizer = ropey::iter::Lines<'a>;
|
type Tokenizer = ropey::iter::Lines<'a>;
|
||||||
|
|
||||||
|
@ -161,15 +143,23 @@ pub fn compare_ropes(before: &Rope, after: &Rope) -> Transaction {
|
||||||
let res = ChangeSet::with_capacity(32);
|
let res = ChangeSet::with_capacity(32);
|
||||||
let after = after.slice(..);
|
let after = after.slice(..);
|
||||||
let file = InternedInput::new(RopeLines(before.slice(..)), RopeLines(after));
|
let file = InternedInput::new(RopeLines(before.slice(..)), RopeLines(after));
|
||||||
let builder = LineChangeSetBuilder {
|
let mut builder = ChangeSetBuilder {
|
||||||
res,
|
res,
|
||||||
file: &file,
|
file: &file,
|
||||||
after,
|
after,
|
||||||
pos: 0,
|
pos: 0,
|
||||||
current_hunk: InternedInput::default(),
|
current_hunk: InternedInput::default(),
|
||||||
|
char_diff: Diff::default(),
|
||||||
};
|
};
|
||||||
|
let mut diff = Diff::compute(Algorithm::Histogram, &file);
|
||||||
let res = imara_diff::diff(Algorithm::Histogram, &file, builder).into();
|
diff.postprocess_with_heuristic(
|
||||||
|
&file,
|
||||||
|
IndentHeuristic::new(|token| IndentLevel::for_ascii_line(file.interner[token].bytes(), 4)),
|
||||||
|
);
|
||||||
|
for hunk in diff.hunks() {
|
||||||
|
builder.process_hunk(hunk.before, hunk.after)
|
||||||
|
}
|
||||||
|
let res = builder.finish().into();
|
||||||
|
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"rope diff took {}s",
|
"rope diff took {}s",
|
||||||
|
|
|
@ -25,38 +25,59 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
||||||
select_node_impl(
|
selection.transform(move |range| {
|
||||||
syntax,
|
let (from, to) = range.into_byte_range(text);
|
||||||
text,
|
let mut cursor = syntax.walk();
|
||||||
selection,
|
cursor.reset_to_byte_range(from, to);
|
||||||
|cursor| {
|
|
||||||
cursor.goto_first_child();
|
if let Some(node) = cursor
|
||||||
},
|
.into_iter()
|
||||||
None,
|
.find(|node| node.is_named() && node.is_contained_within(from..to))
|
||||||
)
|
{
|
||||||
|
return Range::from_node(node, text, range.direction());
|
||||||
|
}
|
||||||
|
|
||||||
|
range
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_next_sibling(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
pub fn select_next_sibling(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
||||||
select_node_impl(
|
selection.transform(move |range| {
|
||||||
syntax,
|
let (from, to) = range.into_byte_range(text);
|
||||||
text,
|
let mut cursor = syntax.walk();
|
||||||
selection,
|
cursor.reset_to_byte_range(from, to);
|
||||||
|cursor| {
|
|
||||||
while !cursor.goto_next_sibling() {
|
while !cursor.goto_next_sibling() {
|
||||||
if !cursor.goto_parent() {
|
if !cursor.goto_parent() {
|
||||||
break;
|
return range;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Some(Direction::Forward),
|
|
||||||
)
|
Range::from_node(cursor.node(), text, Direction::Forward)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_prev_sibling(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
||||||
|
selection.transform(move |range| {
|
||||||
|
let (from, to) = range.into_byte_range(text);
|
||||||
|
let mut cursor = syntax.walk();
|
||||||
|
cursor.reset_to_byte_range(from, to);
|
||||||
|
|
||||||
|
while !cursor.goto_previous_sibling() {
|
||||||
|
if !cursor.goto_parent() {
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Range::from_node(cursor.node(), text, Direction::Backward)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_all_siblings(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
pub fn select_all_siblings(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
||||||
let mut cursor = syntax.walk();
|
let mut cursor = syntax.walk();
|
||||||
selection.transform_iter(move |range| {
|
selection.transform_iter(move |range| {
|
||||||
let (from, to) = range.into_byte_range(text);
|
let (from, to) = range.into_byte_range(text);
|
||||||
cursor.reset_to_byte_range(from as u32, to as u32);
|
cursor.reset_to_byte_range(from, to);
|
||||||
|
|
||||||
if !cursor.goto_parent_with(|parent| parent.child_count() > 1) {
|
if !cursor.goto_parent_with(|parent| parent.child_count() > 1) {
|
||||||
return vec![range].into_iter();
|
return vec![range].into_iter();
|
||||||
|
@ -70,7 +91,7 @@ pub fn select_all_children(syntax: &Syntax, text: RopeSlice, selection: Selectio
|
||||||
let mut cursor = syntax.walk();
|
let mut cursor = syntax.walk();
|
||||||
selection.transform_iter(move |range| {
|
selection.transform_iter(move |range| {
|
||||||
let (from, to) = range.into_byte_range(text);
|
let (from, to) = range.into_byte_range(text);
|
||||||
cursor.reset_to_byte_range(from as u32, to as u32);
|
cursor.reset_to_byte_range(from, to);
|
||||||
select_children(&mut cursor, text, range).into_iter()
|
select_children(&mut cursor, text, range).into_iter()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -88,47 +109,3 @@ fn select_children(cursor: &mut TreeCursor, text: RopeSlice, range: Range) -> Ve
|
||||||
vec![range]
|
vec![range]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_prev_sibling(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
|
||||||
select_node_impl(
|
|
||||||
syntax,
|
|
||||||
text,
|
|
||||||
selection,
|
|
||||||
|cursor| {
|
|
||||||
while !cursor.goto_previous_sibling() {
|
|
||||||
if !cursor.goto_parent() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(Direction::Backward),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_node_impl<F>(
|
|
||||||
syntax: &Syntax,
|
|
||||||
text: RopeSlice,
|
|
||||||
selection: Selection,
|
|
||||||
motion: F,
|
|
||||||
direction: Option<Direction>,
|
|
||||||
) -> Selection
|
|
||||||
where
|
|
||||||
F: Fn(&mut TreeCursor),
|
|
||||||
{
|
|
||||||
let cursor = &mut syntax.walk();
|
|
||||||
|
|
||||||
selection.transform(|range| {
|
|
||||||
let from = text.char_to_byte(range.from()) as u32;
|
|
||||||
let to = text.char_to_byte(range.to()) as u32;
|
|
||||||
|
|
||||||
cursor.reset_to_byte_range(from, to);
|
|
||||||
|
|
||||||
motion(cursor);
|
|
||||||
|
|
||||||
let node = cursor.node();
|
|
||||||
let from = text.byte_to_char(node.start_byte() as usize);
|
|
||||||
let to = text.byte_to_char(node.end_byte() as usize);
|
|
||||||
|
|
||||||
Range::new(from, to).with_direction(direction.unwrap_or_else(|| range.direction()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -387,8 +387,11 @@ impl Range {
|
||||||
|
|
||||||
/// Converts this char range into an in order byte range, discarding
|
/// Converts this char range into an in order byte range, discarding
|
||||||
/// direction.
|
/// direction.
|
||||||
pub fn into_byte_range(&self, text: RopeSlice) -> (usize, usize) {
|
pub fn into_byte_range(&self, text: RopeSlice) -> (u32, u32) {
|
||||||
(text.char_to_byte(self.from()), text.char_to_byte(self.to()))
|
(
|
||||||
|
text.char_to_byte(self.from()) as u32,
|
||||||
|
text.char_to_byte(self.to()) as u32,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -700,22 +703,161 @@ impl Selection {
|
||||||
pub fn contains(&self, other: &Selection) -> bool {
|
pub fn contains(&self, other: &Selection) -> bool {
|
||||||
is_subset::<true>(self.range_bounds(), other.range_bounds())
|
is_subset::<true>(self.range_bounds(), other.range_bounds())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// returns true if self has at least one range that overlaps with at least one range from other
|
||||||
|
pub fn overlaps(&self, other: &Selection) -> bool {
|
||||||
|
let (mut iter_self, mut iter_other) = (self.iter(), other.iter());
|
||||||
|
let (mut ele_self, mut ele_other) = (iter_self.next(), iter_other.next());
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match (ele_self, ele_other) {
|
||||||
|
(Some(ra), Some(rb)) => {
|
||||||
|
if ra.overlaps(rb) {
|
||||||
|
return true;
|
||||||
|
} else if ra.from() > rb.from() {
|
||||||
|
ele_self = iter_self.next();
|
||||||
|
} else {
|
||||||
|
ele_other = iter_other.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the given selection with the overlapping portions of `other`
|
||||||
|
/// cut out. If one range from this selection is equal to one from `other`,
|
||||||
|
/// this range is removed. If this results in an entirely empty selection,
|
||||||
|
/// `None` is returned.
|
||||||
|
pub fn without(self, other: &Selection) -> Option<Self> {
|
||||||
|
if other.contains(&self) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut primary_index = self.primary_index;
|
||||||
|
let mut ranges = smallvec!();
|
||||||
|
let (mut iter_self, mut iter_other) = (self.into_iter(), other.iter());
|
||||||
|
let (mut ele_self, mut ele_other) = (iter_self.next(), iter_other.next());
|
||||||
|
let mut cur_index = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match (ele_self, ele_other) {
|
||||||
|
(Some(ra), Some(rb)) => {
|
||||||
|
if !ra.overlaps(rb) {
|
||||||
|
// there's no overlap and it's on the left of rb
|
||||||
|
if ra.to() <= rb.from() {
|
||||||
|
ranges.push(ra);
|
||||||
|
ele_self = iter_self.next();
|
||||||
|
cur_index += 1;
|
||||||
|
|
||||||
|
// otherwise it must be on the right, so move rb forward
|
||||||
|
} else {
|
||||||
|
ele_other = iter_other.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise there is overlap, so truncate or split
|
||||||
|
if rb.contains_range(&ra) {
|
||||||
|
ele_self = iter_self.next();
|
||||||
|
cur_index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [ ra ]
|
||||||
|
// [ rb ]
|
||||||
|
if ra.from() <= rb.from() && ra.to() <= rb.to() && ra.to() >= rb.from() {
|
||||||
|
let new = if ra.direction() == Direction::Backward {
|
||||||
|
Range::new(rb.from(), ra.head)
|
||||||
|
} else {
|
||||||
|
Range::new(ra.anchor, rb.from())
|
||||||
|
};
|
||||||
|
|
||||||
|
ranges.push(new);
|
||||||
|
ele_self = iter_self.next();
|
||||||
|
cur_index += 1;
|
||||||
|
|
||||||
|
// [ ra ]
|
||||||
|
// [ rb ]
|
||||||
|
} else if ra.from() >= rb.from() && ra.to() >= rb.to() && ra.from() <= rb.to() {
|
||||||
|
let new = if ra.direction() == Direction::Backward {
|
||||||
|
Range::new(ra.anchor, rb.to() + 1)
|
||||||
|
} else {
|
||||||
|
Range::new(rb.to(), ra.head)
|
||||||
|
};
|
||||||
|
|
||||||
|
// don't settle on the new range yet because the next
|
||||||
|
// rb could chop off the other end of ra
|
||||||
|
ele_self = Some(new);
|
||||||
|
ele_other = iter_other.next();
|
||||||
|
|
||||||
|
// [ ra ]
|
||||||
|
// [ rb ]
|
||||||
|
} else if ra.from() < rb.from() && ra.to() > rb.to() {
|
||||||
|
// we must split the range into two
|
||||||
|
let left = if ra.direction() == Direction::Backward {
|
||||||
|
Range::new(rb.from(), ra.head)
|
||||||
|
} else {
|
||||||
|
Range::new(ra.anchor, rb.from())
|
||||||
|
};
|
||||||
|
|
||||||
|
let right = if ra.direction() == Direction::Backward {
|
||||||
|
Range::new(ra.anchor, rb.to())
|
||||||
|
} else {
|
||||||
|
Range::new(rb.to(), ra.head)
|
||||||
|
};
|
||||||
|
|
||||||
|
// We do NOT push right onto the results right away.
|
||||||
|
// We must put it back into the iterator and check it
|
||||||
|
// again in case a further range splits it again.
|
||||||
|
ranges.push(left);
|
||||||
|
ele_other = iter_other.next();
|
||||||
|
|
||||||
|
// offset the primary index whenever we split
|
||||||
|
if cur_index < primary_index {
|
||||||
|
primary_index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_index += 1;
|
||||||
|
ele_self = Some(right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the rest just get included as is
|
||||||
|
(Some(range), None) => {
|
||||||
|
ranges.push(range);
|
||||||
|
ele_self = iter_self.next();
|
||||||
|
cur_index += 1;
|
||||||
|
}
|
||||||
|
// exhausted `self`, nothing left to do
|
||||||
|
(None, _) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ranges.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Selection::new(ranges, primary_index))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a Selection {
|
impl<'a> IntoIterator for &'a Selection {
|
||||||
type Item = &'a Range;
|
type Item = &'a Range;
|
||||||
type IntoIter = std::slice::Iter<'a, Range>;
|
type IntoIter = std::slice::Iter<'a, Range>;
|
||||||
|
|
||||||
fn into_iter(self) -> std::slice::Iter<'a, Range> {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.ranges().iter()
|
self.ranges().iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoIterator for Selection {
|
impl IntoIterator for Selection {
|
||||||
type Item = Range;
|
type Item = Range;
|
||||||
type IntoIter = smallvec::IntoIter<[Range; 1]>;
|
type IntoIter = smallvec::IntoIter<[Self::Item; 1]>;
|
||||||
|
|
||||||
fn into_iter(self) -> smallvec::IntoIter<[Range; 1]> {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.ranges.into_iter()
|
self.ranges.into_iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -882,6 +1024,7 @@ pub fn split_on_matches(text: RopeSlice, selection: &Selection, regex: &rope::Re
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::test;
|
||||||
use crate::Rope;
|
use crate::Rope;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -972,7 +1115,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_overlaps() {
|
fn test_range_overlaps() {
|
||||||
fn overlaps(a: (usize, usize), b: (usize, usize)) -> bool {
|
fn overlaps(a: (usize, usize), b: (usize, usize)) -> bool {
|
||||||
Range::new(a.0, a.1).overlaps(&Range::new(b.0, b.1))
|
Range::new(a.0, a.1).overlaps(&Range::new(b.0, b.1))
|
||||||
}
|
}
|
||||||
|
@ -1022,6 +1165,160 @@ mod test {
|
||||||
assert!(overlaps((1, 1), (1, 1)));
|
assert!(overlaps((1, 1), (1, 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_selection_overlaps() {
|
||||||
|
fn overlaps(a: &[(usize, usize)], b: &[(usize, usize)]) -> bool {
|
||||||
|
let a = Selection::new(
|
||||||
|
a.iter()
|
||||||
|
.map(|(anchor, head)| Range::new(*anchor, *head))
|
||||||
|
.collect(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
let b = Selection::new(
|
||||||
|
b.iter()
|
||||||
|
.map(|(anchor, head)| Range::new(*anchor, *head))
|
||||||
|
.collect(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
a.overlaps(&b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two non-zero-width ranges, no overlap.
|
||||||
|
assert!(!overlaps(&[(0, 3)], &[(3, 6)]));
|
||||||
|
assert!(!overlaps(&[(0, 3)], &[(6, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 0)], &[(3, 6)]));
|
||||||
|
assert!(!overlaps(&[(3, 0)], &[(6, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 6)], &[(0, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 6)], &[(3, 0)]));
|
||||||
|
assert!(!overlaps(&[(6, 3)], &[(0, 3)]));
|
||||||
|
assert!(!overlaps(&[(6, 3)], &[(3, 0)]));
|
||||||
|
|
||||||
|
// more ranges in one or the other, no overlap
|
||||||
|
assert!(!overlaps(&[(6, 3), (9, 6)], &[(3, 0)]));
|
||||||
|
assert!(!overlaps(&[(6, 3), (6, 9)], &[(3, 0)]));
|
||||||
|
assert!(!overlaps(&[(3, 6), (9, 6)], &[(3, 0)]));
|
||||||
|
assert!(!overlaps(&[(3, 6), (6, 9)], &[(3, 0)]));
|
||||||
|
assert!(!overlaps(&[(6, 3), (9, 6)], &[(0, 3)]));
|
||||||
|
assert!(!overlaps(&[(6, 3), (6, 9)], &[(0, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 6), (9, 6)], &[(0, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 6), (6, 9)], &[(0, 3)]));
|
||||||
|
|
||||||
|
assert!(!overlaps(&[(6, 3)], &[(3, 0), (9, 6)]));
|
||||||
|
assert!(!overlaps(&[(6, 3)], &[(3, 0), (6, 9)]));
|
||||||
|
assert!(!overlaps(&[(3, 6)], &[(3, 0), (9, 6)]));
|
||||||
|
assert!(!overlaps(&[(3, 6)], &[(3, 0), (6, 9)]));
|
||||||
|
assert!(!overlaps(&[(6, 3)], &[(0, 3), (9, 6)]));
|
||||||
|
assert!(!overlaps(&[(6, 3)], &[(0, 3), (6, 9)]));
|
||||||
|
assert!(!overlaps(&[(3, 6)], &[(0, 3), (9, 6)]));
|
||||||
|
assert!(!overlaps(&[(3, 6)], &[(0, 3), (6, 9)]));
|
||||||
|
|
||||||
|
// Two non-zero-width ranges, overlap.
|
||||||
|
assert!(overlaps(&[(0, 4)], &[(3, 6)]));
|
||||||
|
assert!(overlaps(&[(0, 4)], &[(6, 3)]));
|
||||||
|
assert!(overlaps(&[(4, 0)], &[(3, 6)]));
|
||||||
|
assert!(overlaps(&[(4, 0)], &[(6, 3)]));
|
||||||
|
assert!(overlaps(&[(3, 6)], &[(0, 4)]));
|
||||||
|
assert!(overlaps(&[(3, 6)], &[(4, 0)]));
|
||||||
|
assert!(overlaps(&[(6, 3)], &[(0, 4)]));
|
||||||
|
assert!(overlaps(&[(6, 3)], &[(4, 0)]));
|
||||||
|
|
||||||
|
// Two non-zero-width ranges, overlap, extra from one or the other
|
||||||
|
assert!(overlaps(&[(0, 4), (7, 10)], &[(3, 6)]));
|
||||||
|
assert!(overlaps(&[(0, 4), (7, 10)], &[(6, 3)]));
|
||||||
|
assert!(overlaps(&[(4, 0), (7, 10)], &[(3, 6)]));
|
||||||
|
assert!(overlaps(&[(4, 0), (7, 10)], &[(6, 3)]));
|
||||||
|
assert!(overlaps(&[(3, 6), (7, 10)], &[(0, 4)]));
|
||||||
|
assert!(overlaps(&[(3, 6), (7, 10)], &[(4, 0)]));
|
||||||
|
assert!(overlaps(&[(6, 3), (7, 10)], &[(0, 4)]));
|
||||||
|
assert!(overlaps(&[(6, 3), (7, 10)], &[(4, 0)]));
|
||||||
|
assert!(overlaps(&[(0, 4), (10, 7)], &[(3, 6)]));
|
||||||
|
assert!(overlaps(&[(0, 4), (10, 7)], &[(6, 3)]));
|
||||||
|
assert!(overlaps(&[(4, 0), (10, 7)], &[(3, 6)]));
|
||||||
|
assert!(overlaps(&[(4, 0), (10, 7)], &[(6, 3)]));
|
||||||
|
assert!(overlaps(&[(3, 6), (10, 7)], &[(0, 4)]));
|
||||||
|
assert!(overlaps(&[(3, 6), (10, 7)], &[(4, 0)]));
|
||||||
|
assert!(overlaps(&[(6, 3), (10, 7)], &[(0, 4)]));
|
||||||
|
assert!(overlaps(&[(6, 3), (10, 7)], &[(4, 0)]));
|
||||||
|
|
||||||
|
assert!(overlaps(&[(0, 4)], &[(3, 6), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(0, 4)], &[(6, 3), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(4, 0)], &[(3, 6), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(4, 0)], &[(6, 3), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(3, 6)], &[(0, 4), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(3, 6)], &[(4, 0), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(6, 3)], &[(0, 4), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(6, 3)], &[(4, 0), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(0, 4)], &[(3, 6), (10, 7)]));
|
||||||
|
assert!(overlaps(&[(0, 4)], &[(6, 3), (10, 7)]));
|
||||||
|
assert!(overlaps(&[(4, 0)], &[(3, 6), (10, 7)]));
|
||||||
|
assert!(overlaps(&[(4, 0)], &[(6, 3), (10, 7)]));
|
||||||
|
assert!(overlaps(&[(3, 6)], &[(0, 4), (10, 7)]));
|
||||||
|
assert!(overlaps(&[(3, 6)], &[(4, 0), (10, 7)]));
|
||||||
|
assert!(overlaps(&[(6, 3)], &[(0, 4), (10, 7)]));
|
||||||
|
assert!(overlaps(&[(6, 3)], &[(4, 0), (10, 7)]));
|
||||||
|
|
||||||
|
// Zero-width and non-zero-width range, no overlap.
|
||||||
|
assert!(!overlaps(&[(0, 3)], &[(3, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 0)], &[(3, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 3)], &[(0, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 3)], &[(3, 0)]));
|
||||||
|
|
||||||
|
assert!(!overlaps(&[(0, 3), (7, 10)], &[(3, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 0), (7, 10)], &[(3, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 3), (7, 10)], &[(0, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 3), (7, 10)], &[(3, 0)]));
|
||||||
|
|
||||||
|
assert!(!overlaps(&[(0, 3)], &[(3, 3), (7, 10)]));
|
||||||
|
assert!(!overlaps(&[(3, 0)], &[(3, 3), (7, 10)]));
|
||||||
|
assert!(!overlaps(&[(3, 3)], &[(0, 3), (7, 10)]));
|
||||||
|
assert!(!overlaps(&[(3, 3)], &[(3, 0), (7, 10)]));
|
||||||
|
|
||||||
|
// Zero-width and non-zero-width range, overlap.
|
||||||
|
assert!(overlaps(&[(1, 4)], &[(1, 1)]));
|
||||||
|
assert!(overlaps(&[(4, 1)], &[(1, 1)]));
|
||||||
|
assert!(overlaps(&[(1, 1)], &[(1, 4)]));
|
||||||
|
assert!(overlaps(&[(1, 1)], &[(4, 1)]));
|
||||||
|
|
||||||
|
assert!(overlaps(&[(1, 4)], &[(3, 3)]));
|
||||||
|
assert!(overlaps(&[(4, 1)], &[(3, 3)]));
|
||||||
|
assert!(overlaps(&[(3, 3)], &[(1, 4)]));
|
||||||
|
assert!(overlaps(&[(3, 3)], &[(4, 1)]));
|
||||||
|
|
||||||
|
assert!(overlaps(&[(1, 4), (7, 10)], &[(1, 1)]));
|
||||||
|
assert!(overlaps(&[(4, 1), (7, 10)], &[(1, 1)]));
|
||||||
|
assert!(overlaps(&[(1, 1), (7, 10)], &[(1, 4)]));
|
||||||
|
assert!(overlaps(&[(1, 1), (7, 10)], &[(4, 1)]));
|
||||||
|
|
||||||
|
assert!(overlaps(&[(1, 4), (7, 10)], &[(3, 3)]));
|
||||||
|
assert!(overlaps(&[(4, 1), (7, 10)], &[(3, 3)]));
|
||||||
|
assert!(overlaps(&[(3, 3), (7, 10)], &[(1, 4)]));
|
||||||
|
assert!(overlaps(&[(3, 3), (7, 10)], &[(4, 1)]));
|
||||||
|
|
||||||
|
assert!(overlaps(&[(1, 4)], &[(1, 1), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(4, 1)], &[(1, 1), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(1, 1)], &[(1, 4), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(1, 1)], &[(4, 1), (7, 10)]));
|
||||||
|
|
||||||
|
assert!(overlaps(&[(1, 4)], &[(3, 3), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(4, 1)], &[(3, 3), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(3, 3)], &[(1, 4), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(3, 3)], &[(4, 1), (7, 10)]));
|
||||||
|
|
||||||
|
// Two zero-width ranges, no overlap.
|
||||||
|
assert!(!overlaps(&[(0, 0)], &[(1, 1)]));
|
||||||
|
assert!(!overlaps(&[(1, 1)], &[(0, 0)]));
|
||||||
|
|
||||||
|
assert!(!overlaps(&[(0, 0), (2, 2)], &[(1, 1)]));
|
||||||
|
assert!(!overlaps(&[(0, 0), (2, 2)], &[(1, 1)]));
|
||||||
|
assert!(!overlaps(&[(1, 1)], &[(0, 0), (2, 2)]));
|
||||||
|
assert!(!overlaps(&[(1, 1)], &[(0, 0), (2, 2)]));
|
||||||
|
|
||||||
|
// Two zero-width ranges, overlap.
|
||||||
|
assert!(overlaps(&[(1, 1)], &[(1, 1)]));
|
||||||
|
assert!(overlaps(&[(1, 1), (2, 2)], &[(1, 1)]));
|
||||||
|
assert!(overlaps(&[(1, 1)], &[(1, 1), (2, 2)]));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_grapheme_aligned() {
|
fn test_grapheme_aligned() {
|
||||||
let r = Rope::from_str("\r\nHi\r\n");
|
let r = Rope::from_str("\r\nHi\r\n");
|
||||||
|
@ -1378,9 +1675,15 @@ mod test {
|
||||||
// multiple matches
|
// multiple matches
|
||||||
assert!(contains(vec!((1, 1), (2, 2)), vec!((1, 1), (2, 2))));
|
assert!(contains(vec!((1, 1), (2, 2)), vec!((1, 1), (2, 2))));
|
||||||
|
|
||||||
// smaller set can't contain bigger
|
// extra items out of range
|
||||||
assert!(!contains(vec!((1, 1)), vec!((1, 1), (2, 2))));
|
assert!(!contains(vec!((1, 1)), vec!((1, 1), (2, 2))));
|
||||||
|
|
||||||
|
// one big range can contain multiple smaller ranges
|
||||||
|
assert!(contains(
|
||||||
|
vec!((1, 10)),
|
||||||
|
vec!((1, 1), (2, 2), (3, 3), (3, 5), (7, 10))
|
||||||
|
));
|
||||||
|
|
||||||
assert!(contains(
|
assert!(contains(
|
||||||
vec!((1, 1), (2, 4), (5, 6), (7, 9), (10, 13)),
|
vec!((1, 1), (2, 4), (5, 6), (7, 9), (10, 13)),
|
||||||
vec!((3, 4), (7, 9))
|
vec!((3, 4), (7, 9))
|
||||||
|
@ -1393,4 +1696,143 @@ mod test {
|
||||||
vec!((1, 2), (3, 4), (7, 9))
|
vec!((1, 2), (3, 4), (7, 9))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_selection_without() {
|
||||||
|
let without = |one, two, expected: Option<_>| {
|
||||||
|
println!("one: {:?}", one);
|
||||||
|
println!("two: {:?}", two);
|
||||||
|
println!("expected: {:?}", expected);
|
||||||
|
|
||||||
|
let (one_text, one_sel) = test::print(one);
|
||||||
|
let (two_text, two_sel) = test::print(two);
|
||||||
|
assert_eq!(one_text, two_text); // sanity
|
||||||
|
let actual_sel = one_sel.without(&two_sel);
|
||||||
|
|
||||||
|
let expected_sel = expected.map(|exp| {
|
||||||
|
let (expected_text, expected_sel) = test::print(exp);
|
||||||
|
assert_eq!(two_text, expected_text);
|
||||||
|
expected_sel
|
||||||
|
});
|
||||||
|
let actual = actual_sel
|
||||||
|
.as_ref()
|
||||||
|
.map(|sel| test::plain(two_text.to_string(), sel));
|
||||||
|
|
||||||
|
println!("actual: {:?}\n\n", actual);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
expected_sel,
|
||||||
|
actual_sel,
|
||||||
|
"expected: {:?}, got: {:?}",
|
||||||
|
expected_sel
|
||||||
|
.as_ref()
|
||||||
|
.map(|sel| test::plain(two_text.to_string(), sel)),
|
||||||
|
actual,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
without(
|
||||||
|
"#[foo bar baz|]#",
|
||||||
|
"foo #[bar|]# baz",
|
||||||
|
Some("#[foo |]#bar#( baz|)#"),
|
||||||
|
);
|
||||||
|
|
||||||
|
without("#[foo bar baz|]#", "#[foo bar baz|]#", None);
|
||||||
|
without("#[foo bar|]# baz", "#[foo bar baz|]#", None);
|
||||||
|
without("foo #[bar|]# baz", "#[foo bar baz|]#", None);
|
||||||
|
|
||||||
|
// direction is preserved
|
||||||
|
without(
|
||||||
|
"#[|foo bar baz]#",
|
||||||
|
"foo #[|bar]# baz",
|
||||||
|
Some("#[|foo ]#bar#(| baz)#"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// preference for direction is given to the left
|
||||||
|
without(
|
||||||
|
"#[|foo bar baz]#",
|
||||||
|
"foo #[bar|]# baz",
|
||||||
|
Some("#[|foo ]#bar#(| baz)#"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// disjoint ranges on the right are ignored
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# baz",
|
||||||
|
"foo bar #[baz|]#",
|
||||||
|
Some("#[foo bar|]# baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# baz",
|
||||||
|
"foo bar#[ baz|]#",
|
||||||
|
Some("#[foo bar|]# baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#(foo|)# #[bar|]# baz",
|
||||||
|
"foo#[ b|]#ar ba#(z|)#",
|
||||||
|
Some("#(foo|)# b#[ar|]# baz"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ranges contained by those one on the right are removed
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# #(b|)#az",
|
||||||
|
"foo bar#[ b|]#a#(z|)#",
|
||||||
|
Some("#[foo bar|]# baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#[foo|]# bar #(baz|)#",
|
||||||
|
"foo bar#[ b|]#a#(z|)#",
|
||||||
|
Some("#[foo|]# bar b#(a|)#z"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# #(b|)#az",
|
||||||
|
"foo bar #[b|]#a#(z|)#",
|
||||||
|
Some("#[foo bar|]# baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# #(b|)#a#(z|)#",
|
||||||
|
"foo bar #[b|]#a#(z|)#",
|
||||||
|
Some("#[foo bar|]# baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# #(b|)#a#(z|)#",
|
||||||
|
"foo bar #[b|]#a#(z|)#",
|
||||||
|
Some("#[foo bar|]# baz"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// more than one range intersected by a single range on the right
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# #(baz|)#",
|
||||||
|
"foo b#[ar ba|]#z",
|
||||||
|
Some("#[foo b|]#ar ba#(z|)#"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// partial overlap
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# baz",
|
||||||
|
"foo #[bar baz|]#",
|
||||||
|
Some("#[foo |]#bar baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# baz",
|
||||||
|
"foo#[ bar baz|]#",
|
||||||
|
Some("#[foo|]# bar baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# baz",
|
||||||
|
"foo ba#[r baz|]#",
|
||||||
|
Some("#[foo ba|]#r baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"foo ba#[r baz|]#",
|
||||||
|
"#[foo bar|]# baz",
|
||||||
|
Some("foo bar#[ baz|]#"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// primary selection is moved - preference given to the left of a split
|
||||||
|
without(
|
||||||
|
"#(|foo)# #(|bar baz)# #[|quux]#",
|
||||||
|
"f#(o|)#o ba#[r b|]#az q#(uu|)#x",
|
||||||
|
Some("#(|f)#o#(|o)# #(|ba)#r b#(|az)# #[|q]#uu#(|x)#"),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -520,6 +520,7 @@ impl MappableCommand {
|
||||||
select_prev_sibling, "Select previous sibling the in syntax tree",
|
select_prev_sibling, "Select previous sibling the in syntax tree",
|
||||||
select_all_siblings, "Select all siblings of the current node",
|
select_all_siblings, "Select all siblings of the current node",
|
||||||
select_all_children, "Select all children of the current node",
|
select_all_children, "Select all children of the current node",
|
||||||
|
expand_selection_around, "Expand selection to parent syntax node, but exclude the selection you started with",
|
||||||
jump_forward, "Jump forward on jumplist",
|
jump_forward, "Jump forward on jumplist",
|
||||||
jump_backward, "Jump backward on jumplist",
|
jump_backward, "Jump backward on jumplist",
|
||||||
save_selection, "Save current selection to jumplist",
|
save_selection, "Save current selection to jumplist",
|
||||||
|
@ -3926,6 +3927,7 @@ fn goto_first_diag(cx: &mut Context) {
|
||||||
Some(diag) => Selection::single(diag.range.start, diag.range.end),
|
Some(diag) => Selection::single(diag.range.start, diag.range.end),
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
doc.set_selection(view.id, selection);
|
doc.set_selection(view.id, selection);
|
||||||
view.diagnostics_handler
|
view.diagnostics_handler
|
||||||
.immediately_show_diagnostic(doc, view.id);
|
.immediately_show_diagnostic(doc, view.id);
|
||||||
|
@ -3937,6 +3939,7 @@ fn goto_last_diag(cx: &mut Context) {
|
||||||
Some(diag) => Selection::single(diag.range.start, diag.range.end),
|
Some(diag) => Selection::single(diag.range.start, diag.range.end),
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
doc.set_selection(view.id, selection);
|
doc.set_selection(view.id, selection);
|
||||||
view.diagnostics_handler
|
view.diagnostics_handler
|
||||||
.immediately_show_diagnostic(doc, view.id);
|
.immediately_show_diagnostic(doc, view.id);
|
||||||
|
@ -3993,6 +3996,7 @@ fn goto_prev_diag(cx: &mut Context) {
|
||||||
view.diagnostics_handler
|
view.diagnostics_handler
|
||||||
.immediately_show_diagnostic(doc, view.id);
|
.immediately_show_diagnostic(doc, view.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.editor.apply_motion(motion)
|
cx.editor.apply_motion(motion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5364,6 +5368,10 @@ fn reverse_selection_contents(cx: &mut Context) {
|
||||||
|
|
||||||
// tree sitter node selection
|
// tree sitter node selection
|
||||||
|
|
||||||
|
const EXPAND_KEY: &str = "expand";
|
||||||
|
const EXPAND_AROUND_BASE_KEY: &str = "expand_around_base";
|
||||||
|
const PARENTS_KEY: &str = "parents";
|
||||||
|
|
||||||
fn expand_selection(cx: &mut Context) {
|
fn expand_selection(cx: &mut Context) {
|
||||||
let motion = |editor: &mut Editor| {
|
let motion = |editor: &mut Editor| {
|
||||||
let (view, doc) = current!(editor);
|
let (view, doc) = current!(editor);
|
||||||
|
@ -5371,42 +5379,154 @@ fn expand_selection(cx: &mut Context) {
|
||||||
if let Some(syntax) = doc.syntax() {
|
if let Some(syntax) = doc.syntax() {
|
||||||
let text = doc.text().slice(..);
|
let text = doc.text().slice(..);
|
||||||
|
|
||||||
let current_selection = doc.selection(view.id);
|
let current_selection = doc.selection(view.id).clone();
|
||||||
let selection = object::expand_selection(syntax, text, current_selection.clone());
|
let selection = object::expand_selection(syntax, text, current_selection.clone());
|
||||||
|
|
||||||
// check if selection is different from the last one
|
// check if selection is different from the last one
|
||||||
if *current_selection != selection {
|
if current_selection != selection {
|
||||||
// save current selection so it can be restored using shrink_selection
|
let prev_selections = doc
|
||||||
view.object_selections.push(current_selection.clone());
|
.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(EXPAND_KEY)
|
||||||
|
.or_default();
|
||||||
|
|
||||||
doc.set_selection(view.id, selection);
|
// save current selection so it can be restored using shrink_selection
|
||||||
|
prev_selections.push(current_selection);
|
||||||
|
doc.set_selection_clear(view.id, selection, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.editor.apply_motion(motion);
|
cx.editor.apply_motion(motion);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shrink_selection(cx: &mut Context) {
|
fn shrink_selection(cx: &mut Context) {
|
||||||
let motion = |editor: &mut Editor| {
|
let motion = |editor: &mut Editor| {
|
||||||
let (view, doc) = current!(editor);
|
let (view, doc) = current!(editor);
|
||||||
let current_selection = doc.selection(view.id);
|
let current_selection = doc.selection(view.id).clone();
|
||||||
|
let prev_expansions = doc
|
||||||
|
.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(EXPAND_KEY)
|
||||||
|
.or_default();
|
||||||
|
|
||||||
// try to restore previous selection
|
// try to restore previous selection
|
||||||
if let Some(prev_selection) = view.object_selections.pop() {
|
if let Some(prev_selection) = prev_expansions.pop() {
|
||||||
if current_selection.contains(&prev_selection) {
|
// allow shrinking the selection only if current selection contains the previous object selection
|
||||||
doc.set_selection(view.id, prev_selection);
|
doc.set_selection_clear(view.id, prev_selection, false);
|
||||||
return;
|
|
||||||
} else {
|
// Do a corresponding pop of the parents from `expand_selection_around`
|
||||||
// clear existing selection as they can't be shrunk to anyway
|
doc.view_data_mut(view.id)
|
||||||
view.object_selections.clear();
|
.object_selections
|
||||||
|
.entry(PARENTS_KEY)
|
||||||
|
.and_modify(|parents| {
|
||||||
|
parents.pop();
|
||||||
|
});
|
||||||
|
|
||||||
|
// need to do this again because borrowing
|
||||||
|
let prev_expansions = doc
|
||||||
|
.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(EXPAND_KEY)
|
||||||
|
.or_default();
|
||||||
|
|
||||||
|
// if we've emptied out the previous expansions, then clear out the
|
||||||
|
// base history as well so it doesn't get used again erroneously
|
||||||
|
if prev_expansions.is_empty() {
|
||||||
|
doc.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(EXPAND_AROUND_BASE_KEY)
|
||||||
|
.and_modify(|base| {
|
||||||
|
base.clear();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not previous selection, shrink to first child
|
// if not previous selection, shrink to first child
|
||||||
if let Some(syntax) = doc.syntax() {
|
if let Some(syntax) = doc.syntax() {
|
||||||
let text = doc.text().slice(..);
|
let text = doc.text().slice(..);
|
||||||
let selection = object::shrink_selection(syntax, text, current_selection.clone());
|
let selection = object::shrink_selection(syntax, text, current_selection);
|
||||||
doc.set_selection(view.id, selection);
|
doc.set_selection_clear(view.id, selection, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
cx.editor.apply_motion(motion);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_selection_around(cx: &mut Context) {
|
||||||
|
let motion = |editor: &mut Editor| {
|
||||||
|
let (view, doc) = current!(editor);
|
||||||
|
|
||||||
|
if doc.syntax().is_some() {
|
||||||
|
// [NOTE] we do this pop and push dance because if we don't take
|
||||||
|
// ownership of the objects, then we require multiple
|
||||||
|
// mutable references to the view's object selections
|
||||||
|
let mut parents_selection = doc
|
||||||
|
.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(PARENTS_KEY)
|
||||||
|
.or_default()
|
||||||
|
.pop();
|
||||||
|
|
||||||
|
let mut base_selection = doc
|
||||||
|
.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(EXPAND_AROUND_BASE_KEY)
|
||||||
|
.or_default()
|
||||||
|
.pop();
|
||||||
|
|
||||||
|
let current_selection = doc.selection(view.id).clone();
|
||||||
|
|
||||||
|
if parents_selection.is_none() || base_selection.is_none() {
|
||||||
|
parents_selection = Some(current_selection.clone());
|
||||||
|
base_selection = Some(current_selection.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = doc.text().slice(..);
|
||||||
|
let syntax = doc.syntax().unwrap();
|
||||||
|
|
||||||
|
let outside_selection =
|
||||||
|
object::expand_selection(syntax, text, parents_selection.clone().unwrap());
|
||||||
|
|
||||||
|
let target_selection = match outside_selection
|
||||||
|
.clone()
|
||||||
|
.without(&base_selection.clone().unwrap())
|
||||||
|
{
|
||||||
|
Some(sel) => sel,
|
||||||
|
None => outside_selection.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// check if selection is different from the last one
|
||||||
|
if target_selection != current_selection {
|
||||||
|
// save current selection so it can be restored using shrink_selection
|
||||||
|
doc.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(EXPAND_KEY)
|
||||||
|
.or_default()
|
||||||
|
.push(current_selection);
|
||||||
|
|
||||||
|
doc.set_selection_clear(view.id, target_selection, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let parents = doc
|
||||||
|
.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(PARENTS_KEY)
|
||||||
|
.or_default();
|
||||||
|
|
||||||
|
parents.push(parents_selection.unwrap());
|
||||||
|
parents.push(outside_selection);
|
||||||
|
|
||||||
|
doc.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(EXPAND_AROUND_BASE_KEY)
|
||||||
|
.or_default()
|
||||||
|
.push(base_selection.unwrap());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
cx.editor.apply_motion(motion);
|
cx.editor.apply_motion(motion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5424,6 +5544,7 @@ where
|
||||||
doc.set_selection(view.id, selection);
|
doc.set_selection(view.id, selection);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.editor.apply_motion(motion);
|
cx.editor.apply_motion(motion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5525,8 +5646,6 @@ fn match_brackets(cx: &mut Context) {
|
||||||
doc.set_selection(view.id, selection);
|
doc.set_selection(view.id, selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
fn jump_forward(cx: &mut Context) {
|
fn jump_forward(cx: &mut Context) {
|
||||||
let count = cx.count();
|
let count = cx.count();
|
||||||
let config = cx.editor.config();
|
let config = cx.editor.config();
|
||||||
|
|
|
@ -1357,6 +1357,7 @@ fn compute_inlay_hints_for_view(
|
||||||
let mut padding_after_inlay_hints = Vec::new();
|
let mut padding_after_inlay_hints = Vec::new();
|
||||||
|
|
||||||
let doc_text = doc.text();
|
let doc_text = doc.text();
|
||||||
|
let inlay_hints_length_limit = doc.config.load().lsp.inlay_hints_length_limit;
|
||||||
|
|
||||||
for hint in hints {
|
for hint in hints {
|
||||||
let char_idx =
|
let char_idx =
|
||||||
|
@ -1367,7 +1368,7 @@ fn compute_inlay_hints_for_view(
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let label = match hint.label {
|
let mut label = match hint.label {
|
||||||
lsp::InlayHintLabel::String(s) => s,
|
lsp::InlayHintLabel::String(s) => s,
|
||||||
lsp::InlayHintLabel::LabelParts(parts) => parts
|
lsp::InlayHintLabel::LabelParts(parts) => parts
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -1375,6 +1376,31 @@ fn compute_inlay_hints_for_view(
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(""),
|
.join(""),
|
||||||
};
|
};
|
||||||
|
// Truncate the hint if too long
|
||||||
|
if let Some(limit) = inlay_hints_length_limit {
|
||||||
|
// Limit on displayed width
|
||||||
|
use helix_core::unicode::{
|
||||||
|
segmentation::UnicodeSegmentation, width::UnicodeWidthStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
let width = label.width();
|
||||||
|
let limit = limit.get().into();
|
||||||
|
if width > limit {
|
||||||
|
let mut floor_boundary = 0;
|
||||||
|
let mut acc = 0;
|
||||||
|
for (i, grapheme_cluster) in label.grapheme_indices(true) {
|
||||||
|
acc += grapheme_cluster.width();
|
||||||
|
|
||||||
|
if acc > limit {
|
||||||
|
floor_boundary = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label.truncate(floor_boundary);
|
||||||
|
label.push('…');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let inlay_hints_vec = match hint.kind {
|
let inlay_hints_vec = match hint.kind {
|
||||||
Some(lsp::InlayHintKind::TYPE) => &mut type_inlay_hints,
|
Some(lsp::InlayHintKind::TYPE) => &mut type_inlay_hints,
|
||||||
|
|
|
@ -87,6 +87,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
|
||||||
";" => collapse_selection,
|
";" => collapse_selection,
|
||||||
"A-;" => flip_selections,
|
"A-;" => flip_selections,
|
||||||
"A-o" | "A-up" => expand_selection,
|
"A-o" | "A-up" => expand_selection,
|
||||||
|
"A-O" => expand_selection_around,
|
||||||
"A-i" | "A-down" => shrink_selection,
|
"A-i" | "A-down" => shrink_selection,
|
||||||
"A-I" | "A-S-down" => select_all_children,
|
"A-I" | "A-S-down" => select_all_children,
|
||||||
"A-p" | "A-left" => select_prev_sibling,
|
"A-p" | "A-left" => select_prev_sibling,
|
||||||
|
|
|
@ -585,8 +585,7 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
|
||||||
// retrieve the `Arc<Path>` key. The `path` in scope here is a `&Path` and
|
// retrieve the `Arc<Path>` key. The `path` in scope here is a `&Path` and
|
||||||
// we can cheaply clone the key for the preview highlight handler.
|
// we can cheaply clone the key for the preview highlight handler.
|
||||||
let (path, preview) = self.preview_cache.get_key_value(path).unwrap();
|
let (path, preview) = self.preview_cache.get_key_value(path).unwrap();
|
||||||
if matches!(preview, CachedPreview::Document(doc) if doc.language_config().is_none())
|
if matches!(preview, CachedPreview::Document(doc) if doc.syntax().is_none()) {
|
||||||
{
|
|
||||||
helix_event::send_blocking(&self.preview_highlight_handler, path.clone());
|
helix_event::send_blocking(&self.preview_highlight_handler, path.clone());
|
||||||
}
|
}
|
||||||
return Some((Preview::Cached(preview), range));
|
return Some((Preview::Cached(preview), range));
|
||||||
|
@ -624,27 +623,27 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
|
||||||
if content_type.is_binary() {
|
if content_type.is_binary() {
|
||||||
return Ok(CachedPreview::Binary);
|
return Ok(CachedPreview::Binary);
|
||||||
}
|
}
|
||||||
Document::open(
|
let mut doc = Document::open(
|
||||||
&path,
|
&path,
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
editor.config.clone(),
|
editor.config.clone(),
|
||||||
editor.syn_loader.clone(),
|
editor.syn_loader.clone(),
|
||||||
)
|
)
|
||||||
.map_or(
|
.or(Err(std::io::Error::new(
|
||||||
Err(std::io::Error::new(
|
std::io::ErrorKind::NotFound,
|
||||||
std::io::ErrorKind::NotFound,
|
"Cannot open document",
|
||||||
"Cannot open document",
|
)))?;
|
||||||
)),
|
let loader = editor.syn_loader.load();
|
||||||
|doc| {
|
if let Some(language_config) = doc.detect_language_config(&loader) {
|
||||||
// Asynchronously highlight the new document
|
doc.language = Some(language_config);
|
||||||
helix_event::send_blocking(
|
// Asynchronously highlight the new document
|
||||||
&self.preview_highlight_handler,
|
helix_event::send_blocking(
|
||||||
path.clone(),
|
&self.preview_highlight_handler,
|
||||||
);
|
path.clone(),
|
||||||
Ok(CachedPreview::Document(Box::new(doc)))
|
);
|
||||||
},
|
}
|
||||||
)
|
Ok(CachedPreview::Document(Box::new(doc)))
|
||||||
} else {
|
} else {
|
||||||
Err(std::io::Error::new(
|
Err(std::io::Error::new(
|
||||||
std::io::ErrorKind::NotFound,
|
std::io::ErrorKind::NotFound,
|
||||||
|
|
|
@ -66,16 +66,15 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> AsyncHook
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if doc.language_config().is_some() {
|
if doc.syntax().is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let loader = editor.syn_loader.load();
|
let Some(language) = doc.language_config().map(|config| config.language()) else {
|
||||||
let Some(language_config) = doc.detect_language_config(&loader) else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let language = language_config.language();
|
|
||||||
doc.language = Some(language_config);
|
let loader = editor.syn_loader.load();
|
||||||
let text = doc.text().clone();
|
let text = doc.text().clone();
|
||||||
|
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
|
|
|
@ -948,3 +948,198 @@ async fn match_bracket() -> anyhow::Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn expand_shrink_selection() -> anyhow::Result<()> {
|
||||||
|
let tests = vec![
|
||||||
|
// single range
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
Some(#[thing|]#)
|
||||||
|
"##},
|
||||||
|
"<A-o><A-o>",
|
||||||
|
indoc! {r##"
|
||||||
|
#[Some(thing)|]#
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
// multi range
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
Some(#[thing|]#)
|
||||||
|
Some(#(other_thing|)#)
|
||||||
|
"##},
|
||||||
|
"<A-o>",
|
||||||
|
indoc! {r##"
|
||||||
|
Some#[(thing)|]#
|
||||||
|
Some#((other_thing)|)#
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
// multi range collision merges
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
"<A-o><A-o><A-o>",
|
||||||
|
indoc! {r##"
|
||||||
|
#[(
|
||||||
|
Some(thing),
|
||||||
|
Some(other_thing),
|
||||||
|
)|]#
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
// multi range collision merges, then shrinks back to original
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
"<A-o><A-o><A-o><A-i>",
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
#[Some(thing)|]#,
|
||||||
|
#(Some(other_thing)|)#,
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
"<A-o><A-o><A-o><A-i><A-i>",
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some#[(thing)|]#,
|
||||||
|
Some#((other_thing)|)#,
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
"<A-o><A-o><A-o><A-i><A-i><A-i>",
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
// shrink with no expansion history defaults to first child
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
#[(
|
||||||
|
Some(thing),
|
||||||
|
Some(other_thing),
|
||||||
|
)|]#
|
||||||
|
"##},
|
||||||
|
"<A-i>",
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
#[Some(thing)|]#,
|
||||||
|
Some(other_thing),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
// any movement cancels selection history and falls back to first child
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
|
||||||
|
"##},
|
||||||
|
"<A-o><A-o><A-o>jkvkkk<A-i>",
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
#[|Some(thing)]#,
|
||||||
|
Some(other_thing),
|
||||||
|
)
|
||||||
|
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for test in tests {
|
||||||
|
test_with_config(AppBuilder::new().with_file("foo.rs", None), test).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn expand_selection_around() -> anyhow::Result<()> {
|
||||||
|
let tests = vec![
|
||||||
|
// single cursor stays single cursor, first goes to end of current
|
||||||
|
// node, then parent
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
Some(#[thing|]#)
|
||||||
|
"##},
|
||||||
|
"<A-O><A-O>",
|
||||||
|
indoc! {r##"
|
||||||
|
#[Some(|]#thing#()|)#
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
// shrinking restores previous selection
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
Some(#[thing|]#)
|
||||||
|
"##},
|
||||||
|
"<A-O><A-O><A-i><A-i>",
|
||||||
|
indoc! {r##"
|
||||||
|
Some(#[thing|]#)
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
// multi range collision merges expand as normal, except with the
|
||||||
|
// original selection removed from the result
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
"<A-O><A-O><A-O>",
|
||||||
|
indoc! {r##"
|
||||||
|
#[(
|
||||||
|
Some(|]#thing#(),
|
||||||
|
Some(|)#other_thing#(),
|
||||||
|
)|)#
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
"<A-O><A-O><A-O><A-i><A-i><A-i>",
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for test in tests {
|
||||||
|
test_with_config(AppBuilder::new().with_file("foo.rs", None), test).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ parking_lot.workspace = true
|
||||||
arc-swap = { version = "1.7.1" }
|
arc-swap = { version = "1.7.1" }
|
||||||
|
|
||||||
gix = { version = "0.72.1", features = ["attributes", "status"], default-features = false, optional = true }
|
gix = { version = "0.72.1", features = ["attributes", "status"], default-features = false, optional = true }
|
||||||
imara-diff = "0.1.8"
|
imara-diff = "0.2.0"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::ops::Range;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use helix_core::Rope;
|
use helix_core::Rope;
|
||||||
|
@ -12,6 +11,8 @@ use tokio::time::Instant;
|
||||||
|
|
||||||
use crate::diff::worker::DiffWorker;
|
use crate::diff::worker::DiffWorker;
|
||||||
|
|
||||||
|
pub use imara_diff::Hunk;
|
||||||
|
|
||||||
mod line_cache;
|
mod line_cache;
|
||||||
mod worker;
|
mod worker;
|
||||||
|
|
||||||
|
@ -52,8 +53,8 @@ impl DiffHandle {
|
||||||
let worker = DiffWorker {
|
let worker = DiffWorker {
|
||||||
channel: receiver,
|
channel: receiver,
|
||||||
diff: diff.clone(),
|
diff: diff.clone(),
|
||||||
new_hunks: Vec::default(),
|
|
||||||
diff_finished_notify: Arc::default(),
|
diff_finished_notify: Arc::default(),
|
||||||
|
diff_alloc: imara_diff::Diff::default(),
|
||||||
};
|
};
|
||||||
let handle = tokio::spawn(worker.run(diff_base, doc));
|
let handle = tokio::spawn(worker.run(diff_base, doc));
|
||||||
let differ = DiffHandle {
|
let differ = DiffHandle {
|
||||||
|
@ -118,48 +119,6 @@ const MAX_DIFF_LINES: usize = 64 * u16::MAX as usize;
|
||||||
// cap average line length to 128 for files with MAX_DIFF_LINES
|
// cap average line length to 128 for files with MAX_DIFF_LINES
|
||||||
const MAX_DIFF_BYTES: usize = MAX_DIFF_LINES * 128;
|
const MAX_DIFF_BYTES: usize = MAX_DIFF_LINES * 128;
|
||||||
|
|
||||||
/// A single change in a file potentially spanning multiple lines
|
|
||||||
/// Hunks produced by the differs are always ordered by their position
|
|
||||||
/// in the file and non-overlapping.
|
|
||||||
/// Specifically for any two hunks `x` and `y` the following properties hold:
|
|
||||||
///
|
|
||||||
/// ``` no_compile
|
|
||||||
/// assert!(x.before.end <= y.before.start);
|
|
||||||
/// assert!(x.after.end <= y.after.start);
|
|
||||||
/// ```
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
|
||||||
pub struct Hunk {
|
|
||||||
pub before: Range<u32>,
|
|
||||||
pub after: Range<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hunk {
|
|
||||||
/// Can be used instead of `Option::None` for better performance
|
|
||||||
/// because lines larger then `i32::MAX` are not supported by `imara-diff` anyways.
|
|
||||||
/// Has some nice properties where it usually is not necessary to check for `None` separately:
|
|
||||||
/// Empty ranges fail contains checks and also fails smaller then checks.
|
|
||||||
pub const NONE: Hunk = Hunk {
|
|
||||||
before: u32::MAX..u32::MAX,
|
|
||||||
after: u32::MAX..u32::MAX,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Inverts a change so that `before`
|
|
||||||
pub fn invert(&self) -> Hunk {
|
|
||||||
Hunk {
|
|
||||||
before: self.after.clone(),
|
|
||||||
after: self.before.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_pure_insertion(&self) -> bool {
|
|
||||||
self.before.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_pure_removal(&self) -> bool {
|
|
||||||
self.after.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A list of changes in a file sorted in ascending
|
/// A list of changes in a file sorted in ascending
|
||||||
/// non-overlapping order
|
/// non-overlapping order
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
use std::mem::transmute;
|
use std::mem::transmute;
|
||||||
|
|
||||||
use helix_core::{Rope, RopeSlice};
|
use helix_core::{Rope, RopeSlice};
|
||||||
use imara_diff::intern::{InternedInput, Interner};
|
use imara_diff::{InternedInput, Interner};
|
||||||
|
|
||||||
use super::{MAX_DIFF_BYTES, MAX_DIFF_LINES};
|
use super::{MAX_DIFF_BYTES, MAX_DIFF_LINES};
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
use std::mem::swap;
|
|
||||||
use std::ops::Range;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use helix_core::{Rope, RopeSlice};
|
use helix_core::{Rope, RopeSlice};
|
||||||
use imara_diff::intern::InternedInput;
|
use imara_diff::{IndentHeuristic, IndentLevel, InternedInput};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use tokio::sync::mpsc::UnboundedReceiver;
|
use tokio::sync::mpsc::UnboundedReceiver;
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
|
@ -14,7 +12,6 @@ use crate::diff::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::line_cache::InternedRopeLines;
|
use super::line_cache::InternedRopeLines;
|
||||||
use super::Hunk;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
@ -22,8 +19,8 @@ mod test;
|
||||||
pub(super) struct DiffWorker {
|
pub(super) struct DiffWorker {
|
||||||
pub channel: UnboundedReceiver<Event>,
|
pub channel: UnboundedReceiver<Event>,
|
||||||
pub diff: Arc<RwLock<DiffInner>>,
|
pub diff: Arc<RwLock<DiffInner>>,
|
||||||
pub new_hunks: Vec<Hunk>,
|
|
||||||
pub diff_finished_notify: Arc<Notify>,
|
pub diff_finished_notify: Arc<Notify>,
|
||||||
|
pub diff_alloc: imara_diff::Diff,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiffWorker {
|
impl DiffWorker {
|
||||||
|
@ -76,15 +73,26 @@ impl DiffWorker {
|
||||||
let mut diff = self.diff.write();
|
let mut diff = self.diff.write();
|
||||||
diff.diff_base = diff_base;
|
diff.diff_base = diff_base;
|
||||||
diff.doc = doc;
|
diff.doc = doc;
|
||||||
swap(&mut diff.hunks, &mut self.new_hunks);
|
diff.hunks.clear();
|
||||||
|
diff.hunks.extend(self.diff_alloc.hunks());
|
||||||
|
drop(diff);
|
||||||
self.diff_finished_notify.notify_waiters();
|
self.diff_finished_notify.notify_waiters();
|
||||||
self.new_hunks.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_diff(&mut self, input: &InternedInput<RopeSlice>) {
|
fn perform_diff(&mut self, input: &InternedInput<RopeSlice>) {
|
||||||
imara_diff::diff(ALGORITHM, input, |before: Range<u32>, after: Range<u32>| {
|
self.diff_alloc.compute_with(
|
||||||
self.new_hunks.push(Hunk { before, after })
|
ALGORITHM,
|
||||||
})
|
&input.before,
|
||||||
|
&input.after,
|
||||||
|
input.interner.num_tokens(),
|
||||||
|
);
|
||||||
|
self.diff_alloc.postprocess_with(
|
||||||
|
&input.before,
|
||||||
|
&input.after,
|
||||||
|
IndentHeuristic::new(|token| {
|
||||||
|
IndentLevel::for_ascii_line(input.interner[token].bytes(), 4)
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +102,7 @@ struct EventAccumulator {
|
||||||
render_lock: Option<RenderLock>,
|
render_lock: Option<RenderLock>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventAccumulator {
|
impl<'a> EventAccumulator {
|
||||||
fn new() -> EventAccumulator {
|
fn new() -> EventAccumulator {
|
||||||
EventAccumulator {
|
EventAccumulator {
|
||||||
diff_base: None,
|
diff_base: None,
|
||||||
|
|
|
@ -1319,15 +1319,27 @@ impl Document {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select text within the [`Document`].
|
/// Select text within the [`Document`], optionally clearing the previous selection state.
|
||||||
pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
|
pub fn set_selection_clear(&mut self, view_id: ViewId, selection: Selection, clear_prev: bool) {
|
||||||
// TODO: use a transaction?
|
// TODO: use a transaction?
|
||||||
self.selections
|
self.selections
|
||||||
.insert(view_id, selection.ensure_invariants(self.text().slice(..)));
|
.insert(view_id, selection.ensure_invariants(self.text().slice(..)));
|
||||||
|
|
||||||
helix_event::dispatch(SelectionDidChange {
|
helix_event::dispatch(SelectionDidChange {
|
||||||
doc: self,
|
doc: self,
|
||||||
view: view_id,
|
view: view_id,
|
||||||
})
|
});
|
||||||
|
|
||||||
|
if clear_prev {
|
||||||
|
self.view_data
|
||||||
|
.entry(view_id)
|
||||||
|
.and_modify(|view_data| view_data.object_selections.clear());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Select text within the [`Document`].
|
||||||
|
pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
|
||||||
|
self.set_selection_clear(view_id, selection, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the origin selection of the text in a document, i.e. where
|
/// Find the origin selection of the text in a document, i.e. where
|
||||||
|
@ -1519,6 +1531,12 @@ impl Document {
|
||||||
apply_inlay_hint_changes(padding_after_inlay_hints);
|
apply_inlay_hint_changes(padding_after_inlay_hints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clear out all associated view object selections, as they are no
|
||||||
|
// longer valid
|
||||||
|
self.view_data
|
||||||
|
.values_mut()
|
||||||
|
.for_each(|view_data| view_data.object_selections.clear());
|
||||||
|
|
||||||
helix_event::dispatch(DocumentDidChange {
|
helix_event::dispatch(DocumentDidChange {
|
||||||
doc: self,
|
doc: self,
|
||||||
view: view_id,
|
view: view_id,
|
||||||
|
@ -1955,13 +1973,13 @@ impl Document {
|
||||||
&self.selections
|
&self.selections
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_data(&self, view_id: ViewId) -> &ViewData {
|
pub fn view_data(&self, view_id: ViewId) -> &ViewData {
|
||||||
self.view_data
|
self.view_data
|
||||||
.get(&view_id)
|
.get(&view_id)
|
||||||
.expect("This should only be called after ensure_view_init")
|
.expect("This should only be called after ensure_view_init")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_data_mut(&mut self, view_id: ViewId) -> &mut ViewData {
|
pub fn view_data_mut(&mut self, view_id: ViewId) -> &mut ViewData {
|
||||||
self.view_data.entry(view_id).or_default()
|
self.view_data.entry(view_id).or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2279,9 +2297,13 @@ impl Document {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stores data needed for views that are tied to this specific Document.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct ViewData {
|
pub struct ViewData {
|
||||||
view_position: ViewPosition,
|
view_position: ViewPosition,
|
||||||
|
|
||||||
|
/// used to store previous selections of tree-sitter objects
|
||||||
|
pub object_selections: HashMap<&'static str, Vec<Selection>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -2332,6 +2354,7 @@ mod test {
|
||||||
Arc::new(ArcSwap::from_pointee(syntax::Loader::default())),
|
Arc::new(ArcSwap::from_pointee(syntax::Loader::default())),
|
||||||
);
|
);
|
||||||
let view = ViewId::default();
|
let view = ViewId::default();
|
||||||
|
doc.ensure_view_init(view);
|
||||||
doc.set_selection(view, Selection::single(0, 0));
|
doc.set_selection(view, Selection::single(0, 0));
|
||||||
|
|
||||||
let transaction =
|
let transaction =
|
||||||
|
@ -2370,7 +2393,9 @@ mod test {
|
||||||
Arc::new(ArcSwap::new(Arc::new(Config::default()))),
|
Arc::new(ArcSwap::new(Arc::new(Config::default()))),
|
||||||
Arc::new(ArcSwap::from_pointee(syntax::Loader::default())),
|
Arc::new(ArcSwap::from_pointee(syntax::Loader::default())),
|
||||||
);
|
);
|
||||||
|
|
||||||
let view = ViewId::default();
|
let view = ViewId::default();
|
||||||
|
doc.ensure_view_init(view);
|
||||||
doc.set_selection(view, Selection::single(5, 5));
|
doc.set_selection(view, Selection::single(5, 5));
|
||||||
|
|
||||||
// insert
|
// insert
|
||||||
|
|
|
@ -29,7 +29,7 @@ use std::{
|
||||||
collections::{BTreeMap, HashMap, HashSet},
|
collections::{BTreeMap, HashMap, HashSet},
|
||||||
fs,
|
fs,
|
||||||
io::{self, stdin},
|
io::{self, stdin},
|
||||||
num::NonZeroUsize,
|
num::{NonZeroU8, NonZeroUsize},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -459,6 +459,9 @@ pub struct LspConfig {
|
||||||
pub display_signature_help_docs: bool,
|
pub display_signature_help_docs: bool,
|
||||||
/// Display inlay hints
|
/// Display inlay hints
|
||||||
pub display_inlay_hints: bool,
|
pub display_inlay_hints: bool,
|
||||||
|
/// Maximum displayed length of inlay hints (excluding the added trailing `…`).
|
||||||
|
/// If it's `None`, there's no limit
|
||||||
|
pub inlay_hints_length_limit: Option<NonZeroU8>,
|
||||||
/// Display document color swatches
|
/// Display document color swatches
|
||||||
pub display_color_swatches: bool,
|
pub display_color_swatches: bool,
|
||||||
/// Whether to enable snippet support
|
/// Whether to enable snippet support
|
||||||
|
@ -476,6 +479,7 @@ impl Default for LspConfig {
|
||||||
auto_signature_help: true,
|
auto_signature_help: true,
|
||||||
display_signature_help_docs: true,
|
display_signature_help_docs: true,
|
||||||
display_inlay_hints: false,
|
display_inlay_hints: false,
|
||||||
|
inlay_hints_length_limit: None,
|
||||||
snippets: true,
|
snippets: true,
|
||||||
goto_reference_include_declaration: true,
|
goto_reference_include_declaration: true,
|
||||||
display_color_swatches: true,
|
display_color_swatches: true,
|
||||||
|
|
|
@ -137,8 +137,6 @@ pub struct View {
|
||||||
// uses two docs because we want to be able to swap between the
|
// uses two docs because we want to be able to swap between the
|
||||||
// two last modified docs which we need to manually keep track of
|
// two last modified docs which we need to manually keep track of
|
||||||
pub last_modified_docs: [Option<DocumentId>; 2],
|
pub last_modified_docs: [Option<DocumentId>; 2],
|
||||||
/// used to store previous selections of tree-sitter objects
|
|
||||||
pub object_selections: Vec<Selection>,
|
|
||||||
/// all gutter-related configuration settings, used primarily for gutter rendering
|
/// all gutter-related configuration settings, used primarily for gutter rendering
|
||||||
pub gutters: GutterConfig,
|
pub gutters: GutterConfig,
|
||||||
/// A mapping between documents and the last history revision the view was updated at.
|
/// A mapping between documents and the last history revision the view was updated at.
|
||||||
|
@ -175,7 +173,6 @@ impl View {
|
||||||
jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel
|
jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel
|
||||||
docs_access_history: Vec::new(),
|
docs_access_history: Vec::new(),
|
||||||
last_modified_docs: [None, None],
|
last_modified_docs: [None, None],
|
||||||
object_selections: Vec::new(),
|
|
||||||
gutters,
|
gutters,
|
||||||
doc_revisions: HashMap::new(),
|
doc_revisions: HashMap::new(),
|
||||||
diagnostics_handler: DiagnosticsHandler::new(),
|
diagnostics_handler: DiagnosticsHandler::new(),
|
||||||
|
|
|
@ -5,9 +5,10 @@ use-grammars = { except = [ "wren", "gemini" ] }
|
||||||
|
|
||||||
[language-server]
|
[language-server]
|
||||||
|
|
||||||
als = { command = "als" }
|
|
||||||
ada-language-server = { command = "ada_language_server" }
|
|
||||||
ada-gpr-language-server = {command = "ada_language_server", args = ["--language-gpr"]}
|
ada-gpr-language-server = {command = "ada_language_server", args = ["--language-gpr"]}
|
||||||
|
ada-language-server = { command = "ada_language_server" }
|
||||||
|
als = { command = "als" }
|
||||||
|
amber-lsp = { command = "amber-lsp" }
|
||||||
angular = {command = "ngserver", args = ["--stdio", "--tsProbeLocations", ".", "--ngProbeLocations", ".",]}
|
angular = {command = "ngserver", args = ["--stdio", "--tsProbeLocations", ".", "--ngProbeLocations", ".",]}
|
||||||
asm-lsp = { command = "asm-lsp" }
|
asm-lsp = { command = "asm-lsp" }
|
||||||
awk-language-server = { command = "awk-language-server" }
|
awk-language-server = { command = "awk-language-server" }
|
||||||
|
@ -648,6 +649,7 @@ comment-token = "#"
|
||||||
indent = { tab-width = 2, unit = " " }
|
indent = { tab-width = 2, unit = " " }
|
||||||
grammar = "ruby"
|
grammar = "ruby"
|
||||||
language-servers = [ "crystalline" ]
|
language-servers = [ "crystalline" ]
|
||||||
|
formatter = { command = "crystal", args = ["tool", "format", "-"] }
|
||||||
|
|
||||||
[[language]]
|
[[language]]
|
||||||
name = "c-sharp"
|
name = "c-sharp"
|
||||||
|
@ -4131,6 +4133,7 @@ scope = "source.ab"
|
||||||
file-types = ["ab"]
|
file-types = ["ab"]
|
||||||
comment-token = ["//", "///"]
|
comment-token = ["//", "///"]
|
||||||
indent = { tab-width = 4, unit = " " }
|
indent = { tab-width = 4, unit = " " }
|
||||||
|
language-servers = ["amber-lsp"]
|
||||||
|
|
||||||
[[grammar]]
|
[[grammar]]
|
||||||
name = "amber"
|
name = "amber"
|
||||||
|
|
|
@ -14,8 +14,6 @@
|
||||||
] @keyword.storage.type
|
] @keyword.storage.type
|
||||||
|
|
||||||
[
|
[
|
||||||
"extern"
|
|
||||||
"register"
|
|
||||||
(type_qualifier)
|
(type_qualifier)
|
||||||
(storage_class_specifier)
|
(storage_class_specifier)
|
||||||
] @keyword.storage.modifier
|
] @keyword.storage.modifier
|
||||||
|
@ -55,8 +53,11 @@
|
||||||
(preproc_directive)
|
(preproc_directive)
|
||||||
] @keyword.directive
|
] @keyword.directive
|
||||||
|
|
||||||
(pointer_declarator "*" @type.builtin)
|
"..." @punctuation
|
||||||
(abstract_pointer_declarator "*" @type.builtin)
|
|
||||||
|
["," "." ":" "::" ";" "->"] @punctuation.delimiter
|
||||||
|
|
||||||
|
["(" ")" "[" "]" "{" "}" "[[" "]]"] @punctuation.bracket
|
||||||
|
|
||||||
[
|
[
|
||||||
"+"
|
"+"
|
||||||
|
@ -95,13 +96,11 @@
|
||||||
"?"
|
"?"
|
||||||
] @operator
|
] @operator
|
||||||
|
|
||||||
(conditional_expression ":" @operator)
|
(conditional_expression ":" @operator) ; After punctuation
|
||||||
|
|
||||||
"..." @punctuation
|
(pointer_declarator "*" @type.builtin) ; After Operators
|
||||||
|
(abstract_pointer_declarator "*" @type.builtin)
|
||||||
|
|
||||||
["," "." ":" ";" "->" "::"] @punctuation.delimiter
|
|
||||||
|
|
||||||
["(" ")" "[" "]" "{" "}"] @punctuation.bracket
|
|
||||||
|
|
||||||
[(true) (false)] @constant.builtin.boolean
|
[(true) (false)] @constant.builtin.boolean
|
||||||
|
|
||||||
|
|
|
@ -23,11 +23,6 @@
|
||||||
|
|
||||||
; Functions
|
; Functions
|
||||||
|
|
||||||
; These casts are parsed as function calls, but are not.
|
|
||||||
((identifier) @keyword (#eq? @keyword "static_cast"))
|
|
||||||
((identifier) @keyword (#eq? @keyword "dynamic_cast"))
|
|
||||||
((identifier) @keyword (#eq? @keyword "reinterpret_cast"))
|
|
||||||
((identifier) @keyword (#eq? @keyword "const_cast"))
|
|
||||||
|
|
||||||
(call_expression
|
(call_expression
|
||||||
function: (qualified_identifier
|
function: (qualified_identifier
|
||||||
|
@ -39,6 +34,8 @@
|
||||||
(template_method
|
(template_method
|
||||||
name: (field_identifier) @function)
|
name: (field_identifier) @function)
|
||||||
|
|
||||||
|
; Support up to 3 levels of nesting of qualifiers
|
||||||
|
; i.e. a::b::c::func();
|
||||||
(function_declarator
|
(function_declarator
|
||||||
declarator: (qualified_identifier
|
declarator: (qualified_identifier
|
||||||
name: (identifier) @function))
|
name: (identifier) @function))
|
||||||
|
@ -48,6 +45,12 @@
|
||||||
name: (qualified_identifier
|
name: (qualified_identifier
|
||||||
name: (identifier) @function)))
|
name: (identifier) @function)))
|
||||||
|
|
||||||
|
(function_declarator
|
||||||
|
declarator: (qualified_identifier
|
||||||
|
name: (qualified_identifier
|
||||||
|
name: (qualified_identifier
|
||||||
|
name: (identifier) @function))))
|
||||||
|
|
||||||
(function_declarator
|
(function_declarator
|
||||||
declarator: (field_identifier) @function)
|
declarator: (field_identifier) @function)
|
||||||
|
|
||||||
|
@ -72,6 +75,13 @@
|
||||||
"()"
|
"()"
|
||||||
] @operator
|
] @operator
|
||||||
|
|
||||||
|
|
||||||
|
; These casts are parsed as function calls, but are not.
|
||||||
|
((identifier) @keyword (#eq? @keyword "static_cast"))
|
||||||
|
((identifier) @keyword (#eq? @keyword "dynamic_cast"))
|
||||||
|
((identifier) @keyword (#eq? @keyword "reinterpret_cast"))
|
||||||
|
((identifier) @keyword (#eq? @keyword "const_cast"))
|
||||||
|
|
||||||
[
|
[
|
||||||
"co_await"
|
"co_await"
|
||||||
"co_return"
|
"co_return"
|
||||||
|
|
|
@ -235,6 +235,8 @@
|
||||||
(template_string)
|
(template_string)
|
||||||
] @string
|
] @string
|
||||||
|
|
||||||
|
(escape_sequence) @constant.character.escape
|
||||||
|
|
||||||
(regex) @string.regexp
|
(regex) @string.regexp
|
||||||
(number) @constant.numeric.integer
|
(number) @constant.numeric.integer
|
||||||
|
|
||||||
|
|
|
@ -131,8 +131,8 @@
|
||||||
"try"
|
"try"
|
||||||
"except"
|
"except"
|
||||||
"finally"
|
"finally"
|
||||||
] @keyword.control.except
|
] @keyword.control.exception
|
||||||
(raise_statement "from" @keyword.control.except)
|
(raise_statement "from" @keyword.control.exception)
|
||||||
|
|
||||||
; Functions
|
; Functions
|
||||||
[
|
[
|
||||||
|
|
Loading…
Reference in New Issue