on master help sign in
use idle timer instead of fixed timeout
2386c81Skyler Hawthorne
helix-term/src/application.rs+31−6
@@ -40,6 +40,8 @@ use {
40#[cfg(windows)]
40#[cfg(windows)]
41type Signals = futures_util::stream::Empty<()>;
41type Signals = futures_util::stream::Empty<()>;
42
42
43+const LSP_DEADLINE: Duration = Duration::from_millis(16);
44+
43pub struct Application {
45pub struct Application {
44 compositor: Compositor,
46 compositor: Compositor,
45 pub editor: Editor,
47 pub editor: Editor,
@@ -54,6 +56,7 @@ pub struct Application {
54 signals: Signals,
56 signals: Signals,
55 jobs: Jobs,
57 jobs: Jobs,
56 lsp_progress: LspProgressMap,
58 lsp_progress: LspProgressMap,
59+ last_render: Instant,
57}
60}
58
61
59#[cfg(feature = "integration")]
62#[cfg(feature = "integration")]
@@ -203,6 +206,7 @@ impl Application {
203 signals,
206 signals,
204 jobs: Jobs::new(),
207 jobs: Jobs::new(),
205 lsp_progress: LspProgressMap::new(),
208 lsp_progress: LspProgressMap::new(),
209+ last_render: Instant::now(),
206 };
210 };
207
211
208 Ok(app)
212 Ok(app)
@@ -230,58 +234,79 @@ impl Application {
230 where
234 where
231 S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
235 S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
232 {
236 {
233− let mut last_render = Instant::now();
234− let deadline = Duration::from_secs(1) / 60;
235−
236 self.render();
237 self.render();
238+ self.last_render = Instant::now();
237
239
238 loop {
240 loop {
239 if self.editor.should_close() {
241 if self.editor.should_close() {
240 break;
242 break;
241 }
243 }
242
244
245+ self.event_loop_until_idle(input_stream).await;
246+ }
247+ }
248+
249+ pub async fn event_loop_until_idle<S>(&mut self, input_stream: &mut S) -> bool
250+ where
251+ S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
252+ {
253+ loop {
254+ if self.editor.should_close() {
255+ return false;
256+ }
257+
243 use futures_util::StreamExt;
258 use futures_util::StreamExt;
244
259
245 tokio::select! {
260 tokio::select! {
246 biased;
261 biased;
247
262
248 Some(event) = input_stream.next() => {
263 Some(event) = input_stream.next() => {
249− self.handle_terminal_events(event)
264+ self.handle_terminal_events(event);
265+ self.editor.reset_idle_timer();
250 }
266 }
251 Some(signal) = self.signals.next() => {
267 Some(signal) = self.signals.next() => {
252 self.handle_signals(signal).await;
268 self.handle_signals(signal).await;
269+ self.editor.reset_idle_timer();
253 }
270 }
254 Some((id, call)) = self.editor.language_servers.incoming.next() => {
271 Some((id, call)) = self.editor.language_servers.incoming.next() => {
255 self.handle_language_server_message(call, id).await;
272 self.handle_language_server_message(call, id).await;
256 // limit render calls for fast language server messages
273 // limit render calls for fast language server messages
257 let last = self.editor.language_servers.incoming.is_empty();
274 let last = self.editor.language_servers.incoming.is_empty();
275+
258− if last || last_render.elapsed() > deadline {
276+ if last || self.last_render.elapsed() > LSP_DEADLINE {
259 self.render();
277 self.render();
260− last_render = Instant::now();
278+ self.last_render = Instant::now();
261 }
279 }
280+
281+ self.editor.reset_idle_timer();
262 }
282 }
263 Some(payload) = self.editor.debugger_events.next() => {
283 Some(payload) = self.editor.debugger_events.next() => {
264 let needs_render = self.editor.handle_debugger_message(payload).await;
284 let needs_render = self.editor.handle_debugger_message(payload).await;
265 if needs_render {
285 if needs_render {
266 self.render();
286 self.render();
267 }
287 }
288+ self.editor.reset_idle_timer();
268 }
289 }
269 Some(config_event) = self.editor.config_events.1.recv() => {
290 Some(config_event) = self.editor.config_events.1.recv() => {
270 self.handle_config_events(config_event);
291 self.handle_config_events(config_event);
271 self.render();
292 self.render();
293+ self.editor.reset_idle_timer();
272 }
294 }
273 Some(callback) = self.jobs.futures.next() => {
295 Some(callback) = self.jobs.futures.next() => {
274 self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
296 self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
275 self.render();
297 self.render();
298+ self.editor.reset_idle_timer();
276 }
299 }
277 Some(callback) = self.jobs.wait_futures.next() => {
300 Some(callback) = self.jobs.wait_futures.next() => {
278 self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
301 self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
279 self.render();
302 self.render();
303+ self.editor.reset_idle_timer();
280 }
304 }
281 _ = &mut self.editor.idle_timer => {
305 _ = &mut self.editor.idle_timer => {
282 // idle timeout
306 // idle timeout
283 self.editor.clear_idle_timer();
307 self.editor.clear_idle_timer();
284 self.handle_idle_timeout();
308 self.handle_idle_timeout();
309+ return true;
285 }
310 }
286 }
311 }
287 }
312 }
helix-term/tests/integration.rs−1
@@ -17,7 +17,6 @@ mod integration {
17 Args::default(),
17 Args::default(),
18 Config::default(),
18 Config::default(),
19 ("#[\n|]#", "ihello world<esc>", "hello world#[|\n]#"),
19 ("#[\n|]#", "ihello world<esc>", "hello world#[|\n]#"),
20− None,
21 )
20 )
22 .await?;
21 .await?;
23
22
helix-term/tests/integration/auto_indent.rs−1
@@ -18,7 +18,6 @@ async fn auto_indent_c() -> anyhow::Result<()> {
18 }
18 }
19 "},
19 "},
20 ),
20 ),
21− None,
22 )
21 )
23 .await?;
22 .await?;
24
23
helix-term/tests/integration/auto_pairs.rs−2
@@ -6,7 +6,6 @@ async fn auto_pairs_basic() -> anyhow::Result<()> {
6 Args::default(),
6 Args::default(),
7 Config::default(),
7 Config::default(),
8 ("#[\n|]#", "i(<esc>", "(#[|)]#\n"),
8 ("#[\n|]#", "i(<esc>", "(#[|)]#\n"),
9− None,
10 )
9 )
11 .await?;
10 .await?;
12
11
@@ -20,7 +19,6 @@ async fn auto_pairs_basic() -> anyhow::Result<()> {
20 ..Default::default()
19 ..Default::default()
21 },
20 },
22 ("#[\n|]#", "i(<esc>", "(#[|\n]#"),
21 ("#[\n|]#", "i(<esc>", "(#[|\n]#"),
23− None,
24 )
22 )
25 .await?;
23 .await?;
26
24
helix-term/tests/integration/commands.rs−4
@@ -1,7 +1,6 @@
1use std::{
1use std::{
2 io::{Read, Write},
2 io::{Read, Write},
3 ops::RangeInclusive,
3 ops::RangeInclusive,
4− time::Duration,
5};
4};
6
5
7use helix_core::diagnostic::Severity;
6use helix_core::diagnostic::Severity;
@@ -23,7 +22,6 @@ async fn test_write_quit_fail() -> anyhow::Result<()> {
23 Some(&|app| {
22 Some(&|app| {
24 assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1);
23 assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1);
25 }),
24 }),
26− None,
27 )
25 )
28 .await?;
26 .await?;
29
27
@@ -57,7 +55,6 @@ async fn test_buffer_close() -> anyhow::Result<()> {
57 }),
55 }),
58 ),
56 ),
59 ],
57 ],
60− None,
61 )
58 )
62 .await?;
59 .await?;
63
60
@@ -88,7 +85,6 @@ async fn test_buffer_close() -> anyhow::Result<()> {
88 let doc = app.editor.document_by_path(file.path());
85 let doc = app.editor.document_by_path(file.path());
89 assert!(doc.is_none(), "found doc: {:?}", doc);
86 assert!(doc.is_none(), "found doc: {:?}", doc);
90 }),
87 }),
91− Some(Duration::from_millis(5000)),
92 )
88 )
93 .await?;
89 .await?;
94
90
helix-term/tests/integration/helpers.rs+15−27
@@ -36,17 +36,15 @@ pub async fn test_key_sequence(
36 app: &mut Application,
36 app: &mut Application,
37 in_keys: Option<&str>,
37 in_keys: Option<&str>,
38 test_fn: Option<&dyn Fn(&Application)>,
38 test_fn: Option<&dyn Fn(&Application)>,
39− timeout: Option<Duration>,
40) -> anyhow::Result<()> {
39) -> anyhow::Result<()> {
41− test_key_sequences(app, vec![(in_keys, test_fn)], timeout).await
40+ test_key_sequences(app, vec![(in_keys, test_fn)]).await
42}
41}
43
42
44pub async fn test_key_sequences(
43pub async fn test_key_sequences(
45 app: &mut Application,
44 app: &mut Application,
46 inputs: Vec<(Option<&str>, Option<&dyn Fn(&Application)>)>,
45 inputs: Vec<(Option<&str>, Option<&dyn Fn(&Application)>)>,
47− timeout: Option<Duration>,
48) -> anyhow::Result<()> {
46) -> anyhow::Result<()> {
49− let timeout = timeout.unwrap_or(Duration::from_millis(500));
47+ const TIMEOUT: Duration = Duration::from_millis(500);
50 let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
48 let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
51 let mut rx_stream = UnboundedReceiverStream::new(rx);
49 let mut rx_stream = UnboundedReceiverStream::new(rx);
52
50
@@ -57,10 +55,7 @@ pub async fn test_key_sequences(
57 }
55 }
58 }
56 }
59
57
60− let event_loop = app.event_loop(&mut rx_stream);
58+ if !app.event_loop_until_idle(&mut rx_stream).await {
61− let result = tokio::time::timeout(timeout, event_loop).await;
62−
63− if result.is_ok() {
64 bail!("application exited before test function could run");
59 bail!("application exited before test function could run");
65 }
60 }
66
61
@@ -74,7 +69,7 @@ pub async fn test_key_sequences(
74 }
69 }
75
70
76 let event_loop = app.event_loop(&mut rx_stream);
71 let event_loop = app.event_loop(&mut rx_stream);
77− tokio::time::timeout(timeout, event_loop).await?;
72+ tokio::time::timeout(TIMEOUT, event_loop).await?;
78 app.close().await?;
73 app.close().await?;
79
74
80 Ok(())
75 Ok(())
@@ -84,7 +79,6 @@ pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>(
84 app: Option<Application>,
79 app: Option<Application>,
85 test_case: T,
80 test_case: T,
86 test_fn: &dyn Fn(&Application),
81 test_fn: &dyn Fn(&Application),
87− timeout: Option<Duration>,
88) -> anyhow::Result<()> {
82) -> anyhow::Result<()> {
89 let test_case = test_case.into();
83 let test_case = test_case.into();
90 let mut app =
84 let mut app =
@@ -102,7 +96,7 @@ pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>(
102 view.id,
96 view.id,
103 );
97 );
104
98
105− test_key_sequence(&mut app, Some(&test_case.in_keys), Some(test_fn), timeout).await
99+ test_key_sequence(&mut app, Some(&test_case.in_keys), Some(test_fn)).await
106}
100}
107
101
108/// Use this for very simple test cases where there is one input
102/// Use this for very simple test cases where there is one input
@@ -112,26 +106,20 @@ pub async fn test_key_sequence_text_result<T: Into<TestCase>>(
112 args: Args,
106 args: Args,
113 config: Config,
107 config: Config,
114 test_case: T,
108 test_case: T,
115− timeout: Option<Duration>,
116) -> anyhow::Result<()> {
109) -> anyhow::Result<()> {
117 let test_case = test_case.into();
110 let test_case = test_case.into();
118 let app = Application::new(args, config).unwrap();
111 let app = Application::new(args, config).unwrap();
119
112
120− test_key_sequence_with_input_text(
113+ test_key_sequence_with_input_text(Some(app), test_case.clone(), &|app| {
121− Some(app),
122− test_case.clone(),
123− &|app| {
124− let doc = doc!(app.editor);
114+ let doc = doc!(app.editor);
125− assert_eq!(&test_case.out_text, doc.text());
115+ assert_eq!(&test_case.out_text, doc.text());
126−
116+
127− let mut selections: Vec<_> = doc.selections().values().cloned().collect();
117+ let mut selections: Vec<_> = doc.selections().values().cloned().collect();
128− assert_eq!(1, selections.len());
118+ assert_eq!(1, selections.len());
129−
119+
130− let sel = selections.pop().unwrap();
120+ let sel = selections.pop().unwrap();
131− assert_eq!(test_case.out_selection, sel);
121+ assert_eq!(test_case.out_selection, sel);
132− },
133− timeout,
134− )
122+ })
135 .await
123 .await
136}
124}
137
125
helix-term/tests/integration/movement.rs−8
@@ -14,7 +14,6 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> {
14 out_text: String::new(),
14 out_text: String::new(),
15 out_selection: Selection::single(0, 0),
15 out_selection: Selection::single(0, 0),
16 },
16 },
17− None,
18 )
17 )
19 .await?;
18 .await?;
20
19
@@ -22,7 +21,6 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> {
22 Args::default(),
21 Args::default(),
23 Config::default(),
22 Config::default(),
24 ("#[\n|]#", "i", "#[|\n]#"),
23 ("#[\n|]#", "i", "#[|\n]#"),
25− None,
26 )
24 )
27 .await?;
25 .await?;
28
26
@@ -30,7 +28,6 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> {
30 Args::default(),
28 Args::default(),
31 Config::default(),
29 Config::default(),
32 ("#[\n|]#", "i<esc>", "#[|\n]#"),
30 ("#[\n|]#", "i<esc>", "#[|\n]#"),
33− None,
34 )
31 )
35 .await?;
32 .await?;
36
33
@@ -38,7 +35,6 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> {
38 Args::default(),
35 Args::default(),
39 Config::default(),
36 Config::default(),
40 ("#[\n|]#", "i<esc>i", "#[|\n]#"),
37 ("#[\n|]#", "i<esc>i", "#[|\n]#"),
41− None,
42 )
38 )
43 .await?;
39 .await?;
44
40
@@ -52,7 +48,6 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
52 Args::default(),
48 Args::default(),
53 Config::default(),
49 Config::default(),
54 ("#[f|]#oo\n", "vll<A-;><esc>", "#[|foo]#\n"),
50 ("#[f|]#oo\n", "vll<A-;><esc>", "#[|foo]#\n"),
55− None,
56 )
51 )
57 .await?;
52 .await?;
58
53
@@ -70,7 +65,6 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
70 #(|bar)#"
65 #(|bar)#"
71 },
66 },
72 ),
67 ),
73− None,
74 )
68 )
75 .await?;
69 .await?;
76
70
@@ -88,7 +82,6 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
88 #(ba|)#r"
82 #(ba|)#r"
89 },
83 },
90 ),
84 ),
91− None,
92 )
85 )
93 .await?;
86 .await?;
94
87
@@ -106,7 +99,6 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
106 #(b|)#ar"
99 #(b|)#ar"
107 },
100 },
108 ),
101 ),
109− None,
110 )
102 )
111 .await?;
103 .await?;
112
104
helix-term/tests/integration/write.rs−5
@@ -1,7 +1,6 @@
1use std::{
1use std::{
2 io::{Read, Write},
2 io::{Read, Write},
3 ops::RangeInclusive,
3 ops::RangeInclusive,
4− time::Duration,
5};
4};
6
5
7use helix_core::diagnostic::Severity;
6use helix_core::diagnostic::Severity;
@@ -24,7 +23,6 @@ async fn test_write() -> anyhow::Result<()> {
24 )?,
23 )?,
25 Some("ii can eat glass, it will not hurt me<ret><esc>:w<ret>"),
24 Some("ii can eat glass, it will not hurt me<ret><esc>:w<ret>"),
26 None,
25 None,
27− Some(Duration::from_millis(1000)),
28 )
26 )
29 .await?;
27 .await?;
30
28
@@ -59,7 +57,6 @@ async fn test_write_concurrent() -> anyhow::Result<()> {
59 )?,
57 )?,
60 Some(&command),
58 Some(&command),
61 None,
59 None,
62− Some(Duration::from_millis(10000)),
63 )
60 )
64 .await?;
61 .await?;
65
62
@@ -108,7 +105,6 @@ async fn test_write_fail_mod_flag() -> anyhow::Result<()> {
108 }),
105 }),
109 ),
106 ),
110 ],
107 ],
111− None,
112 )
108 )
113 .await?;
109 .await?;
114
110
@@ -138,7 +134,6 @@ async fn test_write_fail_new_path() -> anyhow::Result<()> {
138 }),
134 }),
139 ),
135 ),
140 ],
136 ],
141− Some(Duration::from_millis(1000)),
142 )
137 )
143 .await?;
138 .await?;
144
139