on master help sign in
use main application event loop

Use the Application's main event loop to allow LSP, file writes, etc

commitee705dcb3363aeb197f6125ab2f8285782333010 authorSkyler Hawthorne <skyler@dead10ck.com> dateTue 19 Apr 2022, 01:21 (5 years ago) parent36e5809
10 files changed+169−49
helix-core/src/auto_pairs.rs+8−7 helix-term/src/application.rs+20−10 helix-term/src/job.rs+1 helix-term/src/main.rs+2−1 helix-term/tests/integration.rs+2−1 helix-term/tests/integration/auto_indent.rs+2−1 helix-term/tests/integration/auto_pairs.rs+4−2 helix-term/tests/integration/helpers.rs+45−19 helix-term/tests/integration/movement.rs+16−8 helix-term/tests/integration/write.rs+69
helix-core/src/auto_pairs.rs+8−7
@@ -4,7 +4,6 @@
44use crate::{graphemes, movement::Direction, Range, Rope, Selection, Tendril, Transaction};
55use std::collections::HashMap;
66
7−use log::debug;
87use smallvec::SmallVec;
98
109// Heavily based on https://github.com/codemirror/closebrackets/
@@ -123,7 +122,7 @@ impl Default for AutoPairs {
123122
124123#[must_use]
125124pub fn hook(doc: &Rope, selection: &Selection, ch: char, pairs: &AutoPairs) -> Option<Transaction> {
126− debug!("autopairs hook selection: {:#?}", selection);
125+ log::trace!("autopairs hook selection: {:#?}", selection);
127126
128127 if let Some(pair) = pairs.get(ch) {
129128 if pair.same() {
@@ -225,9 +224,11 @@ fn get_next_range(
225224 // other end of the grapheme to get to where the new characters
226225 // are inserted, then move the head to where it should be
227226 let prev_bound = graphemes::prev_grapheme_boundary(doc_slice, start_range.head);
228− debug!(
227+ log::trace!(
229228 "prev_bound: {}, offset: {}, len_inserted: {}",
230− prev_bound, offset, len_inserted
229+ prev_bound,
230+ offset,
231+ len_inserted
231232 );
232233 prev_bound + offset + len_inserted
233234 };
@@ -302,7 +303,7 @@ fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
302303 });
303304
304305 let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
305− debug!("auto pair transaction: {:#?}", t);
306+ log::debug!("auto pair transaction: {:#?}", t);
306307 t
307308}
308309
@@ -334,7 +335,7 @@ fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
334335 });
335336
336337 let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
337− debug!("auto pair transaction: {:#?}", t);
338+ log::debug!("auto pair transaction: {:#?}", t);
338339 t
339340}
340341
@@ -374,7 +375,7 @@ fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
374375 });
375376
376377 let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
377− debug!("auto pair transaction: {:#?}", t);
378+ log::debug!("auto pair transaction: {:#?}", t);
378379 t
379380}
380381
helix-term/src/application.rs+20−10
@@ -1,4 +1,5 @@
11use arc_swap::{access::Map, ArcSwap};
2+use futures_util::Stream;
23use helix_core::{
34 config::{default_syntax_loader, user_syntax_loader},
45 pos_at_coords, syntax, Selection,
@@ -27,7 +28,7 @@ use std::{
2728use anyhow::Error;
2829
2930use crossterm::{
30− event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream},
31+ event::{DisableMouseCapture, EnableMouseCapture, Event},
3132 execute, terminal,
3233 tty::IsTty,
3334};
@@ -68,7 +69,7 @@ fn setup_integration_logging() {
6869 message
6970 ))
7071 })
71− .level(log::LevelFilter::Info)
72+ .level(log::LevelFilter::Debug)
7273 .chain(std::io::stdout())
7374 .apply();
7475}
@@ -225,8 +226,10 @@ impl Application {
225226 }
226227 }
227228
228− pub async fn event_loop(&mut self) {
229− let mut reader = EventStream::new();
229+ pub async fn event_loop<S>(&mut self, input_stream: &mut S)
230+ where
231+ S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
232+ {
230233 let mut last_render = Instant::now();
231234 let deadline = Duration::from_secs(1) / 60;
232235
@@ -242,7 +245,7 @@ impl Application {
242245 tokio::select! {
243246 biased;
244247
245− Some(event) = reader.next() => {
248+ Some(event) = input_stream.next() => {
246249 self.handle_terminal_events(event)
247250 }
248251 Some(signal) = self.signals.next() => {
@@ -749,7 +752,10 @@ impl Application {
749752 Ok(())
750753 }
751754
752− pub async fn run(&mut self) -> Result<i32, Error> {
755+ pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
756+ where
757+ S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
758+ {
753759 self.claim_term().await?;
754760
755761 // Exit the alternate screen and disable raw mode before panicking
@@ -764,16 +770,20 @@ impl Application {
764770 hook(info);
765771 }));
766772
767− self.event_loop().await;
773+ self.event_loop(input_stream).await;
774+ self.close().await?;
775+ self.restore_term()?;
776+
777+ Ok(self.editor.exit_code)
778+ }
768779
780+ pub async fn close(&mut self) -> anyhow::Result<()> {
769781 self.jobs.finish().await;
770782
771783 if self.editor.close_language_servers(None).await.is_err() {
772784 log::error!("Timed out waiting for language servers to shutdown");
773785 };
774786
775− self.restore_term()?;
776−
777− Ok(self.editor.exit_code)
787+ Ok(())
778788 }
779789}
helix-term/src/job.rs+1
@@ -95,6 +95,7 @@ impl Jobs {
9595 /// Blocks until all the jobs that need to be waited on are done.
9696 pub async fn finish(&mut self) {
9797 let wait_futures = std::mem::take(&mut self.wait_futures);
98+ log::debug!("waiting on jobs...");
9899 wait_futures.for_each(|_| future::ready(())).await
99100 }
100101}
helix-term/src/main.rs+2−1
@@ -1,4 +1,5 @@
11use anyhow::{Context, Error, Result};
2+use crossterm::event::EventStream;
23use helix_term::application::Application;
34use helix_term::args::Args;
45use helix_term::config::Config;
@@ -134,7 +135,7 @@ FLAGS:
134135 // TODO: use the thread local executor to spawn the application task separately from the work pool
135136 let mut app = Application::new(args, config).context("unable to create new application")?;
136137
137− let exit_code = app.run().await?;
138+ let exit_code = app.run(&mut EventStream::new()).await?;
138139
139140 Ok(exit_code)
140141}
helix-term/tests/integration.rs+2−1
@@ -17,7 +17,8 @@ mod integration {
1717 Args::default(),
1818 Config::default(),
1919 ("#[\n|]#", "ihello world<esc>", "hello world#[|\n]#"),
20− )?;
20+ )
21+ .await?;
2122
2223 Ok(())
2324 }
helix-term/tests/integration/auto_indent.rs+2−1
@@ -18,7 +18,8 @@ async fn auto_indent_c() -> anyhow::Result<()> {
1818 }
1919 "},
2020 ),
21− )?;
21+ )
22+ .await?;
2223
2324 Ok(())
2425}
helix-term/tests/integration/auto_pairs.rs+4−2
@@ -6,7 +6,8 @@ async fn auto_pairs_basic() -> anyhow::Result<()> {
66 Args::default(),
77 Config::default(),
88 ("#[\n|]#", "i(<esc>", "(#[|)]#\n"),
9− )?;
9+ )
10+ .await?;
1011
1112 test_key_sequence_text_result(
1213 Args::default(),
@@ -18,7 +19,8 @@ async fn auto_pairs_basic() -> anyhow::Result<()> {
1819 ..Default::default()
1920 },
2021 ("#[\n|]#", "i(<esc>", "(#[|\n]#"),
21− )?;
22+ )
23+ .await?;
2224
2325 Ok(())
2426}
helix-term/tests/integration/helpers.rs+45−19
@@ -1,9 +1,12 @@
1−use std::io::Write;
1+use std::{io::Write, time::Duration};
22
3+use anyhow::bail;
34use crossterm::event::{Event, KeyEvent};
45use helix_core::{test, Selection, Transaction};
56use helix_term::{application::Application, args::Args, config::Config};
67use helix_view::{doc, input::parse_macro};
8+use tokio::time::timeout;
9+use tokio_stream::wrappers::UnboundedReceiverStream;
710
811#[derive(Clone, Debug)]
912pub struct TestCase {
@@ -29,10 +32,44 @@ impl<S: Into<String>> From<(S, S, S)> for TestCase {
2932 }
3033}
3134
32−pub fn test_key_sequence<T: Into<TestCase>>(
35+pub async fn test_key_sequence(
36+ app: &mut Application,
37+ in_keys: &str,
38+ test_fn: Option<&dyn Fn(&Application)>,
39+) -> anyhow::Result<()> {
40+ let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
41+
42+ for key_event in parse_macro(&in_keys)?.into_iter() {
43+ tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?;
44+ }
45+
46+ let mut rx_stream = UnboundedReceiverStream::new(rx);
47+ let event_loop = app.event_loop(&mut rx_stream);
48+ let result = timeout(Duration::from_millis(500), event_loop).await;
49+
50+ if result.is_ok() {
51+ bail!("application exited before test function could run");
52+ }
53+
54+ if let Some(test) = test_fn {
55+ test(app);
56+ };
57+
58+ for key_event in parse_macro("<esc>:q!<ret>")?.into_iter() {
59+ tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?;
60+ }
61+
62+ let event_loop = app.event_loop(&mut rx_stream);
63+ timeout(Duration::from_millis(5000), event_loop).await?;
64+ app.close().await?;
65+
66+ Ok(())
67+}
68+
69+pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>(
3370 app: Option<Application>,
3471 test_case: T,
35− test_fn: &dyn Fn(&mut Application),
72+ test_fn: &dyn Fn(&Application),
3673) -> anyhow::Result<()> {
3774 let test_case = test_case.into();
3875 let mut app =
@@ -50,23 +87,13 @@ pub fn test_key_sequence<T: Into<TestCase>>(
5087 view.id,
5188 );
5289
53− let input_keys = parse_macro(&test_case.in_keys)?
54− .into_iter()
55− .map(|key_event| Event::Key(KeyEvent::from(key_event)));
56−
57− for key in input_keys {
58− app.handle_terminal_events(Ok(key));
59− }
60−
61− test_fn(&mut app);
62−
63− Ok(())
90+ test_key_sequence(&mut app, &test_case.in_keys, Some(test_fn)).await
6491}
6592
6693/// Use this for very simple test cases where there is one input
6794/// document, selection, and sequence of key presses, and you just
6895/// want to verify the resulting document and selection.
69−pub fn test_key_sequence_text_result<T: Into<TestCase>>(
96+pub async fn test_key_sequence_text_result<T: Into<TestCase>>(
7097 args: Args,
7198 config: Config,
7299 test_case: T,
@@ -74,7 +101,7 @@ pub fn test_key_sequence_text_result<T: Into<TestCase>>(
74101 let test_case = test_case.into();
75102 let app = Application::new(args, config).unwrap();
76103
77− test_key_sequence(Some(app), test_case.clone(), &|app| {
104+ test_key_sequence_with_input_text(Some(app), test_case.clone(), &|app| {
78105 let doc = doc!(app.editor);
79106 assert_eq!(&test_case.out_text, doc.text());
80107
@@ -83,9 +110,8 @@ pub fn test_key_sequence_text_result<T: Into<TestCase>>(
83110
84111 let sel = selections.pop().unwrap();
85112 assert_eq!(test_case.out_selection, sel);
86− })?;
87−
88− Ok(())
113+ })
114+ .await
89115}
90116
91117pub fn temp_file_with_contents<S: AsRef<str>>(content: S) -> tempfile::NamedTempFile {
helix-term/tests/integration/movement.rs+16−8
@@ -14,25 +14,29 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> {
1414 out_text: String::new(),
1515 out_selection: Selection::single(0, 0),
1616 },
17− )?;
17+ )
18+ .await?;
1819
1920 test_key_sequence_text_result(
2021 Args::default(),
2122 Config::default(),
2223 ("#[\n|]#", "i", "#[|\n]#"),
23− )?;
24+ )
25+ .await?;
2426
2527 test_key_sequence_text_result(
2628 Args::default(),
2729 Config::default(),
2830 ("#[\n|]#", "i<esc>", "#[|\n]#"),
29− )?;
31+ )
32+ .await?;
3033
3134 test_key_sequence_text_result(
3235 Args::default(),
3336 Config::default(),
3437 ("#[\n|]#", "i<esc>i", "#[|\n]#"),
35− )?;
38+ )
39+ .await?;
3640
3741 Ok(())
3842}
@@ -44,7 +48,8 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
4448 Args::default(),
4549 Config::default(),
4650 ("#[f|]#oo\n", "vll<A-;><esc>", "#[|foo]#\n"),
47− )?;
51+ )
52+ .await?;
4853
4954 test_key_sequence_text_result(
5055 Args::default(),
@@ -60,7 +65,8 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
6065 #(|bar)#"
6166 },
6267 ),
63− )?;
68+ )
69+ .await?;
6470
6571 test_key_sequence_text_result(
6672 Args::default(),
@@ -76,7 +82,8 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
7682 #(ba|)#r"
7783 },
7884 ),
79− )?;
85+ )
86+ .await?;
8087
8188 test_key_sequence_text_result(
8289 Args::default(),
@@ -92,7 +99,8 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
9299 #(b|)#ar"
93100 },
94101 ),
95− )?;
102+ )
103+ .await?;
96104
97105 Ok(())
98106}
helix-term/tests/integration/write.rsnew file+69
@@ -0,0 +1,69 @@
1+use std::{
2+ io::{Read, Write},
3+ ops::RangeInclusive,
4+};
5+
6+use helix_term::application::Application;
7+
8+use super::*;
9+
10+#[tokio::test]
11+async fn test_write() -> anyhow::Result<()> {
12+ let mut file = tempfile::NamedTempFile::new().unwrap();
13+
14+ test_key_sequence(
15+ &mut Application::new(
16+ Args {
17+ files: vec![(file.path().to_path_buf(), Position::default())],
18+ ..Default::default()
19+ },
20+ Config::default(),
21+ )?,
22+ "ii can eat glass, it will not hurt me<ret><esc>:w<ret>",
23+ None,
24+ )
25+ .await?;
26+
27+ file.as_file_mut().flush()?;
28+ file.as_file_mut().sync_all()?;
29+
30+ let mut file_content = String::new();
31+ file.as_file_mut().read_to_string(&mut file_content)?;
32+ assert_eq!("i can eat glass, it will not hurt me\n", file_content);
33+
34+ Ok(())
35+}
36+
37+#[tokio::test]
38+async fn test_write_concurrent() -> anyhow::Result<()> {
39+ let mut file = tempfile::NamedTempFile::new().unwrap();
40+ let mut command = String::new();
41+ const RANGE: RangeInclusive<i32> = 1..=1000;
42+
43+ for i in RANGE {
44+ let cmd = format!("%c{}<esc>:w<ret>", i);
45+ command.push_str(&cmd);
46+ }
47+
48+ test_key_sequence(
49+ &mut Application::new(
50+ Args {
51+ files: vec![(file.path().to_path_buf(), Position::default())],
52+ ..Default::default()
53+ },
54+ Config::default(),
55+ )?,
56+ &command,
57+ None,
58+ )
59+ .await?;
60+
61+ file.as_file_mut().flush()?;
62+ file.as_file_mut().sync_all()?;
63+
64+ let mut file_content = String::new();
65+ file.as_file_mut().read_to_string(&mut file_content)?;
66+ assert_eq!(RANGE.end().to_string(), file_content);
67+
68+ Ok(())
69+}