aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-core/src/diagnostic.rs4
-rw-r--r--helix-core/src/transaction.rs87
-rw-r--r--helix-term/src/application.rs11
-rw-r--r--helix-view/src/document.rs38
4 files changed, 112 insertions, 28 deletions
diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs
index 0b75d2a5..52d77a9a 100644
--- a/helix-core/src/diagnostic.rs
+++ b/helix-core/src/diagnostic.rs
@@ -39,6 +39,10 @@ pub enum DiagnosticTag {
#[derive(Debug, Clone)]
pub struct Diagnostic {
pub range: Range,
+ // whether this diagnostic ends at the end of(or inside) a word
+ pub ends_at_word: bool,
+ pub starts_at_word: bool,
+ pub zero_width: bool,
pub line: usize,
pub message: String,
pub severity: Option<Severity>,
diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs
index 1cea6911..f5a49cc1 100644
--- a/helix-core/src/transaction.rs
+++ b/helix-core/src/transaction.rs
@@ -1,7 +1,7 @@
use ropey::RopeSlice;
use smallvec::SmallVec;
-use crate::{Range, Rope, Selection, Tendril};
+use crate::{chars::char_is_word, Range, Rope, Selection, Tendril};
use std::{borrow::Cow, iter::once};
/// (from, to, replacement)
@@ -23,6 +23,30 @@ pub enum Operation {
pub enum Assoc {
Before,
After,
+ /// Acts like `After` if a word character is inserted
+ /// after the position, otherwise acts like `Before`
+ AfterWord,
+ /// Acts like `Before` if a word character is inserted
+ /// before the position, otherwise acts like `After`
+ BeforeWord,
+}
+
+impl Assoc {
+ /// Whether to stick to gaps
+ fn stay_at_gaps(self) -> bool {
+ !matches!(self, Self::BeforeWord | Self::AfterWord)
+ }
+
+ fn insert_offset(self, s: &str) -> usize {
+ let chars = s.chars().count();
+ match self {
+ Assoc::After => chars,
+ Assoc::AfterWord => s.chars().take_while(|&c| char_is_word(c)).count(),
+ // return position before inserted text
+ Assoc::Before => 0,
+ Assoc::BeforeWord => chars - s.chars().rev().take_while(|&c| char_is_word(c)).count(),
+ }
+ }
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
@@ -415,8 +439,6 @@ impl ChangeSet {
map!(|pos, _| (old_end > pos).then_some(new_pos), i);
}
Insert(s) => {
- let ins = s.chars().count();
-
// a subsequent delete means a replace, consume it
if let Some((_, Delete(len))) = iter.peek() {
iter.next();
@@ -424,13 +446,13 @@ impl ChangeSet {
old_end = old_pos + len;
// in range of replaced text
map!(
- |pos, assoc| (old_end > pos).then(|| {
+ |pos, assoc: Assoc| (old_end > pos).then(|| {
// at point or tracking before
- if pos == old_pos || assoc == Assoc::Before {
+ if pos == old_pos && assoc.stay_at_gaps() {
new_pos
} else {
// place to end of insert
- new_pos + ins
+ new_pos + assoc.insert_offset(s)
}
}),
i
@@ -438,20 +460,15 @@ impl ChangeSet {
} else {
// at insert point
map!(
- |pos, assoc| (old_pos == pos).then(|| {
+ |pos, assoc: Assoc| (old_pos == pos).then(|| {
// return position before inserted text
- if assoc == Assoc::Before {
- new_pos
- } else {
- // after text
- new_pos + ins
- }
+ new_pos + assoc.insert_offset(s)
}),
i
);
}
- new_pos += ins;
+ new_pos += s.chars().count();
}
}
old_pos = old_end;
@@ -884,6 +901,48 @@ mod test {
let mut positions = [4, 2];
cs.update_positions(positions.iter_mut().map(|pos| (pos, Assoc::After)));
assert_eq!(positions, [4, 2]);
+ // stays at word boundary
+ let cs = ChangeSet {
+ changes: vec![
+ Retain(2), // <space><space>
+ Insert(" ab".into()),
+ Retain(2), // cd
+ Insert("de ".into()),
+ ],
+ len: 4,
+ len_after: 10,
+ };
+ assert_eq!(cs.map_pos(2, Assoc::BeforeWord), 3);
+ assert_eq!(cs.map_pos(4, Assoc::AfterWord), 9);
+ let cs = ChangeSet {
+ changes: vec![
+ Retain(1), // <space>
+ Insert(" b".into()),
+ Delete(1), // c
+ Retain(1), // d
+ Insert("e ".into()),
+ Delete(1), // <space>
+ ],
+ len: 5,
+ len_after: 7,
+ };
+ assert_eq!(cs.map_pos(1, Assoc::BeforeWord), 2);
+ assert_eq!(cs.map_pos(3, Assoc::AfterWord), 5);
+ let cs = ChangeSet {
+ changes: vec![
+ Retain(1), // <space>
+ Insert("a".into()),
+ Delete(2), // <space>b
+ Retain(1), // d
+ Insert("e".into()),
+ Delete(1), // f
+ Retain(1), // <space>
+ ],
+ len: 5,
+ len_after: 7,
+ };
+ assert_eq!(cs.map_pos(2, Assoc::BeforeWord), 1);
+ assert_eq!(cs.map_pos(4, Assoc::AfterWord), 4);
}
#[test]
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 35111ca5..2dd97b42 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -1,6 +1,7 @@
use arc_swap::{access::Map, ArcSwap};
use futures_util::Stream;
use helix_core::{
+ chars::char_is_word,
diagnostic::{DiagnosticTag, NumberOrString},
path::get_relative_path,
pos_at_coords, syntax, Selection,
@@ -811,7 +812,6 @@ impl Application {
log::warn!("lsp position out of bounds - {:?}", diagnostic);
return None;
};
-
let severity =
diagnostic.severity.map(|severity| match severity {
DiagnosticSeverity::ERROR => Error,
@@ -863,8 +863,17 @@ impl Application {
Vec::new()
};
+ let ends_at_word = start != end
+ && end != 0
+ && text.get_char(end - 1).map_or(false, char_is_word);
+ let starts_at_word = start != end
+ && text.get_char(start).map_or(false, char_is_word);
+
Some(Diagnostic {
range: Range { start, end },
+ ends_at_word,
+ starts_at_word,
+ zero_width: start == end,
line: diagnostic.range.start.line as usize,
message: diagnostic.message.clone(),
severity,
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index 37b8a0bf..b4971c8a 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -1213,20 +1213,32 @@ impl Document {
let changes = transaction.changes();
- changes.update_positions(
- self.diagnostics
- .iter_mut()
- .map(|diagnostic| (&mut diagnostic.range.start, Assoc::After)),
- );
- changes.update_positions(
- self.diagnostics
- .iter_mut()
- .map(|diagnostic| (&mut diagnostic.range.end, Assoc::After)),
- );
- // map state.diagnostics over changes::map_pos too
- for diagnostic in &mut self.diagnostics {
+ // map diagnostics over changes too
+ changes.update_positions(self.diagnostics.iter_mut().map(|diagnostic| {
+ let assoc = if diagnostic.starts_at_word {
+ Assoc::BeforeWord
+ } else {
+ Assoc::After
+ };
+ (&mut diagnostic.range.start, assoc)
+ }));
+ changes.update_positions(self.diagnostics.iter_mut().map(|diagnostic| {
+ let assoc = if diagnostic.ends_at_word {
+ Assoc::AfterWord
+ } else {
+ Assoc::Before
+ };
+ (&mut diagnostic.range.end, assoc)
+ }));
+ self.diagnostics.retain_mut(|diagnostic| {
+ if diagnostic.range.start > diagnostic.range.end
+ || (!diagnostic.zero_width && diagnostic.range.start == diagnostic.range.end)
+ {
+ return false;
+ }
diagnostic.line = self.text.char_to_line(diagnostic.range.start);
- }
+ true
+ });
self.diagnostics
.sort_unstable_by_key(|diagnostic| diagnostic.range);