on master help sign in
Fix initial selection of Document in new view
0f3c10aSkyler Hawthorne
.gitignore−1
@@ -1,6 +1,5 @@
1target
1target
2.direnv
2.direnv
3helix-term/rustfmt.toml
3helix-term/rustfmt.toml
4−helix-syntax/languages/
5result
4result
6runtime/grammars
5runtime/grammars
helix-core/src/auto_pairs.rs+2−12
@@ -1,9 +1,7 @@
1//! When typing the opening character of one of the possible pairs defined below,
1//! When typing the opening character of one of the possible pairs defined below,
2//! this module provides the functionality to insert the paired closing character.
2//! this module provides the functionality to insert the paired closing character.
3
3
4−use crate::{
5− graphemes, movement::Direction, Range, Rope, RopeGraphemes, Selection, Tendril, Transaction,
4+use crate::{graphemes, movement::Direction, Range, Rope, Selection, Tendril, Transaction};
6−};
7use std::collections::HashMap;
5use std::collections::HashMap;
8
6
9use log::debug;
7use log::debug;
@@ -149,14 +147,6 @@ fn prev_char(doc: &Rope, pos: usize) -> Option<char> {
149 doc.get_char(pos - 1)
147 doc.get_char(pos - 1)
150}
148}
151
149
152−fn is_single_grapheme(doc: &Rope, range: &Range) -> bool {
153− let mut graphemes = RopeGraphemes::new(doc.slice(range.from()..range.to()));
154− let first = graphemes.next();
155− let second = graphemes.next();
156− debug!("first: {:#?}, second: {:#?}", first, second);
157− first.is_some() && second.is_none()
158−}
159−
160/// calculate what the resulting range should be for an auto pair insertion
150/// calculate what the resulting range should be for an auto pair insertion
161fn get_next_range(
151fn get_next_range(
162 doc: &Rope,
152 doc: &Rope,
@@ -189,8 +179,8 @@ fn get_next_range(
189 );
179 );
190 }
180 }
191
181
192− let single_grapheme = is_single_grapheme(doc, start_range);
193 let doc_slice = doc.slice(..);
182 let doc_slice = doc.slice(..);
183+ let single_grapheme = start_range.is_single_grapheme(doc_slice);
194
184
195 // just skip over graphemes
185 // just skip over graphemes
196 if len_inserted == 0 {
186 if len_inserted == 0 {
helix-core/src/selection.rs+9−1
@@ -8,7 +8,7 @@ use crate::{
8 prev_grapheme_boundary,
8 prev_grapheme_boundary,
9 },
9 },
10 movement::Direction,
10 movement::Direction,
11− Assoc, ChangeSet, RopeSlice,
11+ Assoc, ChangeSet, RopeGraphemes, RopeSlice,
12};
12};
13use smallvec::{smallvec, SmallVec};
13use smallvec::{smallvec, SmallVec};
14use std::borrow::Cow;
14use std::borrow::Cow;
@@ -339,6 +339,14 @@ impl Range {
339 pub fn cursor_line(&self, text: RopeSlice) -> usize {
339 pub fn cursor_line(&self, text: RopeSlice) -> usize {
340 text.char_to_line(self.cursor(text))
340 text.char_to_line(self.cursor(text))
341 }
341 }
342+
343+ /// Returns true if this Range covers a single grapheme in the given text
344+ pub fn is_single_grapheme(&self, doc: RopeSlice) -> bool {
345+ let mut graphemes = RopeGraphemes::new(doc.slice(self.from()..self.to()));
346+ let first = graphemes.next();
347+ let second = graphemes.next();
348+ first.is_some() && second.is_none()
349+ }
342}
350}
343
351
344impl From<(usize, usize)> for Range {
352impl From<(usize, usize)> for Range {
helix-loader/Cargo.toml−1
@@ -20,7 +20,6 @@ toml = "0.5"
20etcetera = "0.4"
20etcetera = "0.4"
21tree-sitter = "0.20"
21tree-sitter = "0.20"
22once_cell = "1.12"
22once_cell = "1.12"
23−
24log = "0.4"
23log = "0.4"
25
24
26# TODO: these two should be on !wasm32 only
25# TODO: these two should be on !wasm32 only
helix-loader/src/lib.rs+3−1
@@ -13,7 +13,9 @@ pub fn runtime_dir() -> std::path::PathBuf {
13
13
14 if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") {
14 if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") {
15 // this is the directory of the crate being run by cargo, we need the workspace path so we take the parent
15 // this is the directory of the crate being run by cargo, we need the workspace path so we take the parent
16− return std::path::PathBuf::from(dir).parent().unwrap().join(RT_DIR);
16+ let path = std::path::PathBuf::from(dir).parent().unwrap().join(RT_DIR);
17+ log::debug!("runtime dir: {}", path.to_string_lossy());
18+ return path;
17 }
19 }
18
20
19 const RT_DIR: &str = "runtime";
21 const RT_DIR: &str = "runtime";
helix-term/src/application.rs+21
@@ -55,8 +55,29 @@ pub struct Application {
55 lsp_progress: LspProgressMap,
55 lsp_progress: LspProgressMap,
56}
56}
57
57
58+#[cfg(feature = "integration")]
59+fn setup_integration_logging() {
60+ // Separate file config so we can include year, month and day in file logs
61+ let _ = fern::Dispatch::new()
62+ .format(|out, message, record| {
63+ out.finish(format_args!(
64+ "{} {} [{}] {}",
65+ chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%.3f"),
66+ record.target(),
67+ record.level(),
68+ message
69+ ))
70+ })
71+ .level(log::LevelFilter::Debug)
72+ .chain(std::io::stdout())
73+ .apply();
74+}
75+
58impl Application {
76impl Application {
59 pub fn new(args: Args, config: Config) -> Result<Self, Error> {
77 pub fn new(args: Args, config: Config) -> Result<Self, Error> {
78+ #[cfg(feature = "integration")]
79+ setup_integration_logging();
80+
60 use helix_view::editor::Action;
81 use helix_view::editor::Action;
61
82
62 let config_dir = helix_loader::config_dir();
83 let config_dir = helix_loader::config_dir();
helix-term/src/commands.rs+12−5
@@ -2094,10 +2094,17 @@ fn insert_mode(cx: &mut Context) {
2094 let (view, doc) = current!(cx.editor);
2094 let (view, doc) = current!(cx.editor);
2095 enter_insert_mode(doc);
2095 enter_insert_mode(doc);
2096
2096
2097− let selection = doc
2097+ log::trace!(
2098+ "entering insert mode with sel: {:?}, text: {:?}",
2098− .selection(view.id)
2099+ doc.selection(view.id),
2099− .clone()
2100+ doc.text().to_string()
2101+ );
2102+
2103+ let selection = doc.selection(view.id).clone().transform(|range| {
2100− .transform(|range| Range::new(range.to(), range.from()));
2104+ let new_range = Range::new(range.to(), range.from());
2105+ new_range
2106+ });
2107+
2101 doc.set_selection(view.id, selection);
2108 doc.set_selection(view.id, selection);
2102}
2109}
2103
2110
@@ -2444,8 +2451,8 @@ fn normal_mode(cx: &mut Context) {
2444 graphemes::prev_grapheme_boundary(text, range.to()),
2451 graphemes::prev_grapheme_boundary(text, range.to()),
2445 )
2452 )
2446 });
2453 });
2447− doc.set_selection(view.id, selection);
2448
2454
2455+ doc.set_selection(view.id, selection);
2449 doc.restore_cursor = false;
2456 doc.restore_cursor = false;
2450 }
2457 }
2451}
2458}
helix-term/tests/integration.rs+86−26
@@ -2,9 +2,9 @@
2mod integration {
2mod integration {
3 use std::path::PathBuf;
3 use std::path::PathBuf;
4
4
5− use helix_core::{syntax::AutoPairConfig, Position, Selection, Tendril, Transaction};
5+ use helix_core::{syntax::AutoPairConfig, Position, Selection, Transaction};
6 use helix_term::{application::Application, args::Args, config::Config};
6 use helix_term::{application::Application, args::Args, config::Config};
7− use helix_view::{current, doc, input::parse_macro};
7+ use helix_view::{doc, input::parse_macro};
8
8
9 use crossterm::event::{Event, KeyEvent};
9 use crossterm::event::{Event, KeyEvent};
10 use indoc::indoc;
10 use indoc::indoc;
@@ -25,14 +25,14 @@ mod integration {
25 let mut app =
25 let mut app =
26 app.unwrap_or_else(|| Application::new(Args::default(), Config::default()).unwrap());
26 app.unwrap_or_else(|| Application::new(Args::default(), Config::default()).unwrap());
27
27
28− let (view, doc) = current!(app.editor);
28+ let (view, doc) = helix_view::current!(app.editor);
29+ let sel = doc.selection(view.id).clone();
29
30
31+ // replace the initial text with the input text
30 doc.apply(
32 doc.apply(
31− &Transaction::insert(
33+ &Transaction::change_by_selection(&doc.text(), &sel, |_| {
32− doc.text(),
33− &Selection::single(1, 0),
34− Tendril::from(&test_case.in_text),
34+ (0, doc.text().len_chars(), Some((&test_case.in_text).into()))
35− )
35+ })
36 .with_selection(test_case.in_selection.clone()),
36 .with_selection(test_case.in_selection.clone()),
37 view.id,
37 view.id,
38 );
38 );
@@ -80,12 +80,12 @@ mod integration {
80 Args::default(),
80 Args::default(),
81 Config::default(),
81 Config::default(),
82 TestCase {
82 TestCase {
83− in_text: String::new(),
83+ in_text: "\n".into(),
84 in_selection: Selection::single(0, 1),
84 in_selection: Selection::single(0, 1),
85 // TODO: fix incorrect selection on new doc
85 // TODO: fix incorrect selection on new doc
86− in_keys: String::from("ihello world<esc>hl"),
86+ in_keys: "ihello world<esc>".into(),
87− out_text: String::from("hello world\n"),
87+ out_text: "hello world\n".into(),
88− out_selection: Selection::single(11, 12),
88+ out_selection: Selection::single(12, 11),
89 },
89 },
90 )?;
90 )?;
91
91
@@ -93,16 +93,74 @@ mod integration {
93 }
93 }
94
94
95 #[tokio::test]
95 #[tokio::test]
96− async fn auto_pairs_basic() -> anyhow::Result<()> {
96+ async fn insert_mode_cursor_position() -> anyhow::Result<()> {
97 test_key_sequence_text_result(
97 test_key_sequence_text_result(
98 Args::default(),
98 Args::default(),
99 Config::default(),
99 Config::default(),
100 TestCase {
100 TestCase {
101 in_text: String::new(),
101 in_text: String::new(),
102+ in_selection: Selection::single(0, 0),
103+ in_keys: "i".into(),
104+ out_text: String::new(),
105+ out_selection: Selection::single(0, 0),
106+ },
107+ )?;
108+
109+ test_key_sequence_text_result(
110+ Args::default(),
111+ Config::default(),
112+ TestCase {
113+ in_text: "\n".into(),
114+ in_selection: Selection::single(0, 1),
115+ in_keys: "i".into(),
116+ out_text: "\n".into(),
117+ out_selection: Selection::single(1, 0),
118+ },
119+ )?;
120+
121+ test_key_sequence_text_result(
122+ Args::default(),
123+ Config::default(),
124+ TestCase {
125+ in_text: "\n".into(),
102 in_selection: Selection::single(0, 1),
126 in_selection: Selection::single(0, 1),
103− in_keys: String::from("i(<esc>hl"),
127+ in_keys: "i<esc>i".into(),
104− out_text: String::from("()\n"),
128+ out_text: "\n".into(),
129+ out_selection: Selection::single(1, 0),
130+ },
131+ )?;
132+
133+ Ok(())
134+ }
135+
136+ #[tokio::test]
137+ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
138+ test_key_sequence_text_result(
139+ Args::default(),
140+ Config::default(),
141+ TestCase {
142+ in_text: "\n".into(),
143+ in_selection: Selection::single(0, 1),
144+ in_keys: "i".into(),
145+ out_text: "\n".into(),
105− out_selection: Selection::single(1, 2),
146+ out_selection: Selection::single(1, 0),
147+ },
148+ )?;
149+
150+ Ok(())
151+ }
152+
153+ #[tokio::test]
154+ async fn auto_pairs_basic() -> anyhow::Result<()> {
155+ test_key_sequence_text_result(
156+ Args::default(),
157+ Config::default(),
158+ TestCase {
159+ in_text: "\n".into(),
160+ in_selection: Selection::single(0, 1),
161+ in_keys: "i(<esc>".into(),
162+ out_text: "()\n".into(),
163+ out_selection: Selection::single(2, 1),
106 },
164 },
107 )?;
165 )?;
108
166
@@ -116,11 +174,11 @@ mod integration {
116 ..Default::default()
174 ..Default::default()
117 },
175 },
118 TestCase {
176 TestCase {
119− in_text: String::new(),
177+ in_text: "\n".into(),
120 in_selection: Selection::single(0, 1),
178 in_selection: Selection::single(0, 1),
121− in_keys: String::from("i(<esc>hl"),
179+ in_keys: "i(<esc>".into(),
122− out_text: String::from("(\n"),
180+ out_text: "(\n".into(),
123− out_selection: Selection::single(1, 2),
181+ out_selection: Selection::single(2, 1),
124 },
182 },
125 )?;
183 )?;
126
184
@@ -136,15 +194,17 @@ mod integration {
136 },
194 },
137 Config::default(),
195 Config::default(),
138 TestCase {
196 TestCase {
139− in_text: String::from("void foo() {}"),
197+ in_text: "void foo() {}\n".into(),
140− in_selection: Selection::single(12, 13),
198+ in_selection: Selection::single(13, 12),
141− in_keys: String::from("i<ret><esc>"),
199+ in_keys: "i<ret><esc>".into(),
142− out_text: String::from(indoc! {r#"
200+ out_text: indoc! {r#"
143 void foo() {
201 void foo() {
144
202
145 }
203 }
146− "#}),
204+ "#}
205+ .trim_start()
206+ .into(),
147− out_selection: Selection::single(15, 16),
207+ out_selection: Selection::single(16, 15),
148 },
208 },
149 )?;
209 )?;
150
210
helix-view/src/document.rs+33−1
@@ -1,5 +1,6 @@
1use anyhow::{anyhow, bail, Context, Error};
1use anyhow::{anyhow, bail, Context, Error};
2use helix_core::auto_pairs::AutoPairs;
2use helix_core::auto_pairs::AutoPairs;
3+use helix_core::Range;
3use serde::de::{self, Deserialize, Deserializer};
4use serde::de::{self, Deserialize, Deserializer};
4use serde::Serialize;
5use serde::Serialize;
5use std::cell::Cell;
6use std::cell::Cell;
@@ -83,7 +84,7 @@ impl Serialize for Mode {
83pub struct Document {
84pub struct Document {
84 pub(crate) id: DocumentId,
85 pub(crate) id: DocumentId,
85 text: Rope,
86 text: Rope,
86− pub(crate) selections: HashMap<ViewId, Selection>,
87+ selections: HashMap<ViewId, Selection>,
87
88
88 path: Option<PathBuf>,
89 path: Option<PathBuf>,
89 encoding: &'static encoding::Encoding,
90 encoding: &'static encoding::Encoding,
@@ -637,6 +638,37 @@ impl Document {
637 .insert(view_id, selection.ensure_invariants(self.text().slice(..)));
638 .insert(view_id, selection.ensure_invariants(self.text().slice(..)));
638 }
639 }
639
640
641+ /// Find the origin selection of the text in a document, i.e. where
642+ /// a single cursor would go if it were on the first grapheme. If
643+ /// the text is empty, returns (0, 0).
644+ pub fn origin(&self) -> Range {
645+ if self.text().len_chars() == 0 {
646+ return Range::new(0, 0);
647+ }
648+
649+ Range::new(0, 1).grapheme_aligned(self.text().slice(..))
650+ }
651+
652+ /// Reset the view's selection on this document to the
653+ /// [origin](Document::origin) cursor.
654+ pub fn reset_selection(&mut self, view_id: ViewId) {
655+ let origin = self.origin();
656+ self.set_selection(view_id, Selection::single(origin.anchor, origin.head));
657+ }
658+
659+ /// Initializes a new selection for the given view if it does not
660+ /// already have one.
661+ pub fn ensure_view_init(&mut self, view_id: ViewId) {
662+ if self.selections.get(&view_id).is_none() {
663+ self.reset_selection(view_id);
664+ }
665+ }
666+
667+ /// Remove a view's selection from this document.
668+ pub fn remove_view(&mut self, view_id: ViewId) {
669+ self.selections.remove(&view_id);
670+ }
671+
640 /// Apply a [`Transaction`] to the [`Document`] to change its text.
672 /// Apply a [`Transaction`] to the [`Document`] to change its text.
641 fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
673 fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
642 let old_doc = self.text().clone();
674 let old_doc = self.text().clone();
helix-view/src/editor.rs+7−17
@@ -32,12 +32,12 @@ use anyhow::{bail, Error};
32
32
33pub use helix_core::diagnostic::Severity;
33pub use helix_core::diagnostic::Severity;
34pub use helix_core::register::Registers;
34pub use helix_core::register::Registers;
35+use helix_core::Position;
35use helix_core::{
36use helix_core::{
36 auto_pairs::AutoPairs,
37 auto_pairs::AutoPairs,
37 syntax::{self, AutoPairConfig},
38 syntax::{self, AutoPairConfig},
38 Change,
39 Change,
39};
40};
40−use helix_core::{Position, Selection};
41use helix_dap as dap;
41use helix_dap as dap;
42
42
43use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
43use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
@@ -645,11 +645,8 @@ impl Editor {
645 view.offset = Position::default();
645 view.offset = Position::default();
646
646
647 let doc = self.documents.get_mut(&doc_id).unwrap();
647 let doc = self.documents.get_mut(&doc_id).unwrap();
648+ doc.ensure_view_init(view.id);
648
649
649− // initialize selection for view
650− doc.selections
651− .entry(view.id)
652− .or_insert_with(|| Selection::point(0));
653 // TODO: reuse align_view
650 // TODO: reuse align_view
654 let pos = doc
651 let pos = doc
655 .selection(view.id)
652 .selection(view.id)
@@ -719,9 +716,7 @@ impl Editor {
719 Action::Load => {
716 Action::Load => {
720 let view_id = view!(self).id;
717 let view_id = view!(self).id;
721 let doc = self.documents.get_mut(&id).unwrap();
718 let doc = self.documents.get_mut(&id).unwrap();
722− if doc.selections().is_empty() {
719+ doc.ensure_view_init(view_id);
723− doc.set_selection(view_id, Selection::point(0));
724− }
725 return;
720 return;
726 }
721 }
727 Action::HorizontalSplit | Action::VerticalSplit => {
722 Action::HorizontalSplit | Action::VerticalSplit => {
@@ -736,7 +731,7 @@ impl Editor {
736 );
731 );
737 // initialize selection for view
732 // initialize selection for view
738 let doc = self.documents.get_mut(&id).unwrap();
733 let doc = self.documents.get_mut(&id).unwrap();
739− doc.set_selection(view_id, Selection::point(0));
734+ doc.ensure_view_init(view_id);
740 }
735 }
741 }
736 }
742
737
@@ -769,7 +764,7 @@ impl Editor {
769 Ok(self.new_file_from_document(action, Document::from(rope, Some(encoding))))
764 Ok(self.new_file_from_document(action, Document::from(rope, Some(encoding))))
770 }
765 }
771
766
772− // ???
767+ // ??? possible use for integration tests
773 pub fn open(&mut self, path: PathBuf, action: Action) -> Result<DocumentId, Error> {
768 pub fn open(&mut self, path: PathBuf, action: Action) -> Result<DocumentId, Error> {
774 let path = helix_core::path::get_canonicalized_path(&path)?;
769 let path = helix_core::path::get_canonicalized_path(&path)?;
775 let id = self.document_by_path(&path).map(|doc| doc.id);
770 let id = self.document_by_path(&path).map(|doc| doc.id);
@@ -791,12 +786,7 @@ impl Editor {
791 pub fn close(&mut self, id: ViewId) {
786 pub fn close(&mut self, id: ViewId) {
792 let view = self.tree.get(self.tree.focus);
787 let view = self.tree.get(self.tree.focus);
793 // remove selection
788 // remove selection
794− self.documents
795− .get_mut(&view.doc)
789+ self.documents.get_mut(&view.doc).unwrap().remove_view(id);
796− .unwrap()
797− .selections
798− .remove(&id);
799−
800 self.tree.remove(id);
790 self.tree.remove(id);
801 self._refresh();
791 self._refresh();
802 }
792 }
@@ -871,7 +861,7 @@ impl Editor {
871 let view = View::new(doc_id, self.config().gutters.clone());
861 let view = View::new(doc_id, self.config().gutters.clone());
872 let view_id = self.tree.insert(view);
862 let view_id = self.tree.insert(view);
873 let doc = self.documents.get_mut(&doc_id).unwrap();
863 let doc = self.documents.get_mut(&doc_id).unwrap();
874− doc.set_selection(view_id, Selection::point(0));
864+ doc.ensure_view_init(view_id);
875 }
865 }
876
866
877 self._refresh();
867 self._refresh();