input: Fix line column by use chars offset. (#1239)
- Add autocorrect to markdown example for test `markers`. <img width="1104" height="590" alt="image" src="https://github.com/user-attachments/assets/03103088-e828-4039-82b0-c816d5f36141" />
This commit is contained in:
parent
2a918bf4de
commit
3fa996c331
10 changed files with 185 additions and 67 deletions
97
Cargo.lock
generated
97
Cargo.lock
generated
|
|
@ -615,6 +615,36 @@ version = "1.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "autocorrect"
|
||||
version = "2.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3199cc73b9b6af61f5034dcdb28c21e6050ffdc1229a20abfc71c244083ab9dc"
|
||||
dependencies = [
|
||||
"autocorrect-derive",
|
||||
"diff",
|
||||
"ignore",
|
||||
"lazy_static",
|
||||
"owo-colors",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"serde_yaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocorrect-derive"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b80889323facc446add06be71b9c4c000acd083b12563d9c914b86424bf2f17a"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "av1-grain"
|
||||
version = "0.2.4"
|
||||
|
|
@ -1884,6 +1914,12 @@ version = "1.6.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
|
|
@ -3625,7 +3661,7 @@ dependencies = [
|
|||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.58.0",
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4902,7 +4938,7 @@ version = "0.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro-crate 3.3.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.105",
|
||||
|
|
@ -5414,6 +5450,12 @@ dependencies = [
|
|||
"ttf-parser 0.25.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "4.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e"
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.18.3"
|
||||
|
|
@ -5515,6 +5557,50 @@ version = "2.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror 2.0.14",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.105",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.8.0"
|
||||
|
|
@ -7630,6 +7716,7 @@ name = "story"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"autocorrect",
|
||||
"chrono",
|
||||
"fake",
|
||||
"gpui",
|
||||
|
|
@ -8872,6 +8959,12 @@ version = "1.18.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||
|
||||
[[package]]
|
||||
name = "uds_windows"
|
||||
version = "1.1.0"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ tracing.workspace = true
|
|||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
tree-sitter-navi = "0.2.2"
|
||||
itertools = "0.14.0"
|
||||
autocorrect = "2.14.2"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
gtk = { version = "0.18" }
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ impl Example {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_markers(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn set_markers(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.language.name() != "rust" {
|
||||
return;
|
||||
}
|
||||
|
|
@ -177,7 +177,6 @@ impl Example {
|
|||
Marker::new("info", (25, 10), (25, 20), "This is a info message, this is a very long message, with **Markdown** support."),
|
||||
Marker::new("hint", (36, 9), (40, 10), "This is a hint message."),
|
||||
],
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use tokio::time;
|
|||
const VERSION: &str = "1.0.0";
|
||||
|
||||
/// HelloWorld struct provides greeting functionality with configuration options
|
||||
/// This is CJK 中文🎊 for test line, column.
|
||||
///
|
||||
/// # Features
|
||||
/// - Async greetings with customizable names
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use gpui::*;
|
||||
use gpui_component::{
|
||||
highlighter::{HighlightTheme, Language},
|
||||
input::{InputEvent, InputState, TabSize, TextInput},
|
||||
input::{InputEvent, InputState, Marker, MarkerSeverity, TabSize, TextInput},
|
||||
resizable::{h_resizable, resizable_panel, ResizableState},
|
||||
text::{TextView, TextViewStyle},
|
||||
ActiveTheme as _,
|
||||
|
|
@ -11,6 +11,7 @@ use story::Assets;
|
|||
pub struct Example {
|
||||
input_state: Entity<InputState>,
|
||||
resizable_state: Entity<ResizableState>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
const EXAMPLE: &str = include_str!("./fixtures/test.md");
|
||||
|
|
@ -30,13 +31,37 @@ impl Example {
|
|||
});
|
||||
let resizable_state = ResizableState::new(cx);
|
||||
|
||||
let _subscribe = cx.subscribe(&input_state, |_, _, _: &InputEvent, cx| {
|
||||
let _subscriptions = vec![cx.subscribe(&input_state, |_, input, _: &InputEvent, cx| {
|
||||
// Subscribe to input changes and perform linting with AutoCorrect for markers example.
|
||||
let value = input.read(cx).value().clone();
|
||||
let result = autocorrect::lint_for(value.as_str(), "md");
|
||||
|
||||
let mut markets = vec![];
|
||||
for item in result.lines.iter() {
|
||||
let severity = match item.severity {
|
||||
autocorrect::Severity::Error => MarkerSeverity::Warning,
|
||||
autocorrect::Severity::Warning => MarkerSeverity::Hint,
|
||||
autocorrect::Severity::Pass => MarkerSeverity::Info,
|
||||
};
|
||||
|
||||
let start = (item.line, item.col);
|
||||
let end = (item.line, item.col + item.old.chars().count());
|
||||
let message = format!("AutoCorrect: {}", item.new);
|
||||
let market = Marker::new(severity, start, end, message);
|
||||
markets.push(market);
|
||||
}
|
||||
|
||||
input.update(cx, |state, cx| {
|
||||
state.set_markers(markets, cx);
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
});
|
||||
})];
|
||||
|
||||
Self {
|
||||
resizable_state,
|
||||
input_state,
|
||||
_subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,33 +61,6 @@ impl From<(usize, usize)> for LineColumn {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<rope::Point> for LineColumn {
|
||||
fn from(value: rope::Point) -> Self {
|
||||
Self {
|
||||
line: value.row as usize + 1,
|
||||
column: value.column as usize + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LineColumn> for rope::Point {
|
||||
fn from(value: LineColumn) -> Self {
|
||||
Self {
|
||||
row: value.line.saturating_sub(1) as u32,
|
||||
column: value.column.saturating_sub(1) as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LineColumn> for tree_sitter::Point {
|
||||
fn from(value: LineColumn) -> Self {
|
||||
Self {
|
||||
row: value.line.saturating_sub(1),
|
||||
column: value.column.saturating_sub(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LineColumn {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.line, self.column)
|
||||
|
|
@ -111,18 +84,6 @@ mod tests {
|
|||
}
|
||||
);
|
||||
assert_eq!(LineColumn::from((0, 0)), LineColumn { line: 1, column: 1 });
|
||||
|
||||
assert_eq!(
|
||||
LineColumn::from(rope::Point::new(0, 1)),
|
||||
LineColumn { line: 1, column: 2 }
|
||||
);
|
||||
assert_eq!(
|
||||
LineColumn::from(rope::Point::new(10, 9)),
|
||||
LineColumn {
|
||||
line: 11,
|
||||
column: 10
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
highlighter::HighlightTheme,
|
||||
input::{InputState, LineColumn},
|
||||
input::{InputState, LineColumn, RopeExt},
|
||||
};
|
||||
use gpui::{px, App, HighlightStyle, Hsla, SharedString, UnderlineStyle};
|
||||
use std::ops::Range;
|
||||
|
|
@ -35,17 +35,8 @@ impl Marker {
|
|||
|
||||
/// Prepare the marker to convert line, column to byte offsets.
|
||||
pub(super) fn prepare(&mut self, state: &InputState) {
|
||||
let mut start_point: rope::Point = self.start.into();
|
||||
let mut end_point: rope::Point = self.end.into();
|
||||
|
||||
// limit column avoid overflow
|
||||
let start_line_len = state.text.line_len(start_point.row);
|
||||
start_point.column = start_point.column.min(start_line_len);
|
||||
let end_line_len = state.text.line_len(end_point.row);
|
||||
end_point.column = end_point.column.min(end_line_len);
|
||||
|
||||
let start = state.text.point_to_offset(start_point);
|
||||
let end = state.text.point_to_offset(end_point);
|
||||
let start = state.text.line_column_to_offset(&self.start);
|
||||
let end = state.text.line_column_to_offset(&self.end);
|
||||
|
||||
self.range = Some(start..end);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ mod text_input;
|
|||
mod text_wrapper;
|
||||
|
||||
pub(crate) use clear_button::*;
|
||||
pub(super) use cursor::*;
|
||||
pub use cursor::*;
|
||||
pub use marker::*;
|
||||
pub use mask_pattern::MaskPattern;
|
||||
pub use mode::TabSize;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use rope::{Point, Rope};
|
||||
|
||||
use crate::input::LineColumn;
|
||||
|
||||
/// An extension trait for `Rope` to provide additional utility methods.
|
||||
pub trait RopeExt {
|
||||
/// Get the line at the given row (0-based) index, including the `\r` at the end, but not `\n`.
|
||||
|
|
@ -35,6 +37,12 @@ pub trait RopeExt {
|
|||
///
|
||||
/// If the offset is out of bounds, return None.
|
||||
fn char_at(&self, offset: usize) -> Option<char>;
|
||||
|
||||
/// Get the byte offset from the given `LineColumn` (1-based).
|
||||
fn line_column_to_offset(&self, line_col: &LineColumn) -> usize;
|
||||
|
||||
/// Get the `LineColumn` (1-based) from the given byte offset.
|
||||
fn offset_to_line_column(&self, offset: usize) -> LineColumn;
|
||||
}
|
||||
|
||||
/// An iterator over the lines of a `Rope`.
|
||||
|
|
@ -98,6 +106,24 @@ impl RopeExt for Rope {
|
|||
self.point_to_offset(Point::new(row, 0))
|
||||
}
|
||||
|
||||
fn line_column_to_offset(&self, line_col: &LineColumn) -> usize {
|
||||
let row = line_col.line.saturating_sub(1);
|
||||
let col = line_col.column.saturating_sub(1);
|
||||
|
||||
let line = self.line(row);
|
||||
self.line_start_offset(row) + line.chars().take(col).map(|c| c.len_utf8()).sum::<usize>()
|
||||
}
|
||||
|
||||
fn offset_to_line_column(&self, offset: usize) -> LineColumn {
|
||||
let point = self.offset_to_point(offset);
|
||||
let line = self.line(point.row as usize);
|
||||
let column = line.slice(0..point.column as usize).chars().count();
|
||||
LineColumn {
|
||||
line: point.row as usize + 1,
|
||||
column: column + 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn line_end_offset(&self, row: usize) -> usize {
|
||||
if row > self.max_point().row as usize {
|
||||
return self.len();
|
||||
|
|
@ -135,7 +161,7 @@ impl RopeExt for Rope {
|
|||
mod tests {
|
||||
use rope::Rope;
|
||||
|
||||
use crate::input::RopeExt as _;
|
||||
use crate::input::{LineColumn, RopeExt as _};
|
||||
|
||||
#[test]
|
||||
fn test_line() {
|
||||
|
|
@ -208,6 +234,32 @@ mod tests {
|
|||
assert_eq!(rope.chars_count(), 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_line_column() {
|
||||
let rope = Rope::from("a 中文🎉 test\nRope");
|
||||
assert_eq!(
|
||||
rope.line_column_to_offset(&LineColumn::new(1, 4)),
|
||||
"a 中".len()
|
||||
);
|
||||
assert_eq!(
|
||||
rope.line_column_to_offset(&LineColumn::new(1, 6)),
|
||||
"a 中文🎉".len()
|
||||
);
|
||||
assert_eq!(
|
||||
rope.line_column_to_offset(&LineColumn::new(2, 2)),
|
||||
"a 中文🎉 test\nR".len()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
rope.offset_to_line_column("a 中文🎉 test\nR".len()),
|
||||
LineColumn::new(2, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
rope.offset_to_line_column("a 中文🎉".len()),
|
||||
LineColumn::new(1, 6)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_char_at() {
|
||||
let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文🎉\nRope");
|
||||
|
|
|
|||
|
|
@ -493,7 +493,7 @@ impl InputState {
|
|||
/// Set markers, only for [`InputMode::CodeEditor`] mode.
|
||||
///
|
||||
/// For example to set the diagnostic markers in the code editor.
|
||||
pub fn set_markers(&mut self, markers: Vec<Marker>, _: &mut Window, _: &mut Context<Self>) {
|
||||
pub fn set_markers(&mut self, markers: Vec<Marker>, _: &mut Context<Self>) {
|
||||
let mut markers = markers;
|
||||
for marker in &mut markers {
|
||||
marker.prepare(self);
|
||||
|
|
@ -747,12 +747,7 @@ impl InputState {
|
|||
/// Return the (1-based) line and column of the cursor.
|
||||
pub fn line_column(&self) -> LineColumn {
|
||||
let offset = self.cursor();
|
||||
let point = self.text.offset_to_point(offset);
|
||||
|
||||
LineColumn {
|
||||
line: point.row as usize + 1,
|
||||
column: point.column as usize + 1,
|
||||
}
|
||||
self.text.offset_to_line_column(offset)
|
||||
}
|
||||
|
||||
/// Set (1-based) line and column of the cursor.
|
||||
|
|
|
|||
Loading…
Reference in a new issue