Skip to content

Commit 78c0cdc

Browse files
authored
Merge pull request #2267 from dead10ck/fix-write-fail
Write path fixes
2 parents 8c9bb23 + 756253b commit 78c0cdc

23 files changed

+995
-298
lines changed

docs/CONTRIBUTING.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ to `cargo install` anything either).
3535
Integration tests for helix-term can be run with `cargo integration-test`. Code
3636
contributors are strongly encouraged to write integration tests for their code.
3737
Existing tests can be used as examples. Helpers can be found in
38-
[helpers.rs][helpers.rs]
38+
[helpers.rs][helpers.rs]. The log level can be set with the `HELIX_LOG_LEVEL`
39+
environment variable, e.g. `HELIX_LOG_LEVEL=debug cargo integration-test`.
3940

4041
## Minimum Stable Rust Version (MSRV) Policy
4142

helix-core/src/auto_pairs.rs

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::collections::HashMap;
77
use smallvec::SmallVec;
88

99
// Heavily based on https://github.com/codemirror/closebrackets/
10-
1110
pub const DEFAULT_PAIRS: &[(char, char)] = &[
1211
('(', ')'),
1312
('{', '}'),

helix-core/src/syntax.rs

+6
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ pub struct Configuration {
6161
pub language: Vec<LanguageConfiguration>,
6262
}
6363

64+
impl Default for Configuration {
65+
fn default() -> Self {
66+
crate::config::default_syntax_loader()
67+
}
68+
}
69+
6470
// largely based on tree-sitter/cli/src/loader.rs
6571
#[derive(Debug, Serialize, Deserialize)]
6672
#[serde(rename_all = "kebab-case", deny_unknown_fields)]

helix-term/src/application.rs

+156-52
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
use arc_swap::{access::Map, ArcSwap};
22
use futures_util::Stream;
33
use helix_core::{
4-
config::{default_syntax_loader, user_syntax_loader},
54
diagnostic::{DiagnosticTag, NumberOrString},
5+
path::get_relative_path,
66
pos_at_coords, syntax, Selection,
77
};
88
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
9-
use helix_view::{align_view, editor::ConfigEvent, theme, tree::Layout, Align, Editor};
9+
use helix_view::{
10+
align_view,
11+
document::DocumentSavedEventResult,
12+
editor::{ConfigEvent, EditorEvent},
13+
theme,
14+
tree::Layout,
15+
Align, Editor,
16+
};
1017
use serde_json::json;
1118

1219
use crate::{
@@ -19,7 +26,7 @@ use crate::{
1926
ui::{self, overlay::overlayed},
2027
};
2128

22-
use log::{error, warn};
29+
use log::{debug, error, warn};
2330
use std::{
2431
io::{stdin, stdout, Write},
2532
sync::Arc,
@@ -102,7 +109,11 @@ fn restore_term() -> Result<(), Error> {
102109
}
103110

104111
impl Application {
105-
pub fn new(args: Args, config: Config) -> Result<Self, Error> {
112+
pub fn new(
113+
args: Args,
114+
config: Config,
115+
syn_loader_conf: syntax::Configuration,
116+
) -> Result<Self, Error> {
106117
#[cfg(feature = "integration")]
107118
setup_integration_logging();
108119

@@ -129,14 +140,6 @@ impl Application {
129140
})
130141
.unwrap_or_else(|| theme_loader.default_theme(true_color));
131142

132-
let syn_loader_conf = user_syntax_loader().unwrap_or_else(|err| {
133-
eprintln!("Bad language config: {}", err);
134-
eprintln!("Press <ENTER> to continue with default language config");
135-
use std::io::Read;
136-
// This waits for an enter press.
137-
let _ = std::io::stdin().read(&mut []);
138-
default_syntax_loader()
139-
});
140143
let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf));
141144

142145
let mut compositor = Compositor::new().context("build compositor")?;
@@ -245,6 +248,10 @@ impl Application {
245248
Ok(app)
246249
}
247250

251+
#[cfg(feature = "integration")]
252+
fn render(&mut self) {}
253+
254+
#[cfg(not(feature = "integration"))]
248255
fn render(&mut self) {
249256
let compositor = &mut self.compositor;
250257

@@ -275,9 +282,6 @@ impl Application {
275282
where
276283
S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
277284
{
278-
#[cfg(feature = "integration")]
279-
let mut idle_handled = false;
280-
281285
loop {
282286
if self.editor.should_close() {
283287
return false;
@@ -294,26 +298,6 @@ impl Application {
294298
Some(signal) = self.signals.next() => {
295299
self.handle_signals(signal).await;
296300
}
297-
Some((id, call)) = self.editor.language_servers.incoming.next() => {
298-
self.handle_language_server_message(call, id).await;
299-
// limit render calls for fast language server messages
300-
let last = self.editor.language_servers.incoming.is_empty();
301-
302-
if last || self.last_render.elapsed() > LSP_DEADLINE {
303-
self.render();
304-
self.last_render = Instant::now();
305-
}
306-
}
307-
Some(payload) = self.editor.debugger_events.next() => {
308-
let needs_render = self.editor.handle_debugger_message(payload).await;
309-
if needs_render {
310-
self.render();
311-
}
312-
}
313-
Some(config_event) = self.editor.config_events.1.recv() => {
314-
self.handle_config_events(config_event);
315-
self.render();
316-
}
317301
Some(callback) = self.jobs.futures.next() => {
318302
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
319303
self.render();
@@ -322,26 +306,22 @@ impl Application {
322306
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
323307
self.render();
324308
}
325-
_ = &mut self.editor.idle_timer => {
326-
// idle timeout
327-
self.editor.clear_idle_timer();
328-
self.handle_idle_timeout();
309+
event = self.editor.wait_event() => {
310+
let _idle_handled = self.handle_editor_event(event).await;
329311

330312
#[cfg(feature = "integration")]
331313
{
332-
idle_handled = true;
314+
if _idle_handled {
315+
return true;
316+
}
333317
}
334318
}
335319
}
336320

337321
// for integration tests only, reset the idle timer after every
338-
// event to make a signal when test events are done processing
322+
// event to signal when test events are done processing
339323
#[cfg(feature = "integration")]
340324
{
341-
if idle_handled {
342-
return true;
343-
}
344-
345325
self.editor.reset_idle_timer();
346326
}
347327
}
@@ -446,6 +426,111 @@ impl Application {
446426
}
447427
}
448428

429+
pub fn handle_document_write(&mut self, doc_save_event: DocumentSavedEventResult) {
430+
let doc_save_event = match doc_save_event {
431+
Ok(event) => event,
432+
Err(err) => {
433+
self.editor.set_error(err.to_string());
434+
return;
435+
}
436+
};
437+
438+
let doc = match self.editor.document_mut(doc_save_event.doc_id) {
439+
None => {
440+
warn!(
441+
"received document saved event for non-existent doc id: {}",
442+
doc_save_event.doc_id
443+
);
444+
445+
return;
446+
}
447+
Some(doc) => doc,
448+
};
449+
450+
debug!(
451+
"document {:?} saved with revision {}",
452+
doc.path(),
453+
doc_save_event.revision
454+
);
455+
456+
doc.set_last_saved_revision(doc_save_event.revision);
457+
458+
let lines = doc_save_event.text.len_lines();
459+
let bytes = doc_save_event.text.len_bytes();
460+
461+
if doc.path() != Some(&doc_save_event.path) {
462+
if let Err(err) = doc.set_path(Some(&doc_save_event.path)) {
463+
log::error!(
464+
"error setting path for doc '{:?}': {}",
465+
doc.path(),
466+
err.to_string(),
467+
);
468+
469+
self.editor.set_error(err.to_string());
470+
return;
471+
}
472+
473+
let loader = self.editor.syn_loader.clone();
474+
475+
// borrowing the same doc again to get around the borrow checker
476+
let doc = doc_mut!(self.editor, &doc_save_event.doc_id);
477+
let id = doc.id();
478+
doc.detect_language(loader);
479+
let _ = self.editor.refresh_language_server(id);
480+
}
481+
482+
// TODO: fix being overwritten by lsp
483+
self.editor.set_status(format!(
484+
"'{}' written, {}L {}B",
485+
get_relative_path(&doc_save_event.path).to_string_lossy(),
486+
lines,
487+
bytes
488+
));
489+
}
490+
491+
#[inline(always)]
492+
pub async fn handle_editor_event(&mut self, event: EditorEvent) -> bool {
493+
log::debug!("received editor event: {:?}", event);
494+
495+
match event {
496+
EditorEvent::DocumentSaved(event) => {
497+
self.handle_document_write(event);
498+
self.render();
499+
}
500+
EditorEvent::ConfigEvent(event) => {
501+
self.handle_config_events(event);
502+
self.render();
503+
}
504+
EditorEvent::LanguageServerMessage((id, call)) => {
505+
self.handle_language_server_message(call, id).await;
506+
// limit render calls for fast language server messages
507+
let last = self.editor.language_servers.incoming.is_empty();
508+
509+
if last || self.last_render.elapsed() > LSP_DEADLINE {
510+
self.render();
511+
self.last_render = Instant::now();
512+
}
513+
}
514+
EditorEvent::DebuggerEvent(payload) => {
515+
let needs_render = self.editor.handle_debugger_message(payload).await;
516+
if needs_render {
517+
self.render();
518+
}
519+
}
520+
EditorEvent::IdleTimer => {
521+
self.editor.clear_idle_timer();
522+
self.handle_idle_timeout();
523+
524+
#[cfg(feature = "integration")]
525+
{
526+
return true;
527+
}
528+
}
529+
}
530+
531+
false
532+
}
533+
449534
pub fn handle_terminal_events(&mut self, event: Result<CrosstermEvent, crossterm::ErrorKind>) {
450535
let mut cx = crate::compositor::Context {
451536
editor: &mut self.editor,
@@ -866,25 +951,44 @@ impl Application {
866951

867952
self.event_loop(input_stream).await;
868953

869-
let err = self.close().await.err();
870-
954+
let close_errs = self.close().await;
871955
restore_term()?;
872956

873-
if let Some(err) = err {
957+
for err in close_errs {
874958
self.editor.exit_code = 1;
875959
eprintln!("Error: {}", err);
876960
}
877961

878962
Ok(self.editor.exit_code)
879963
}
880964

881-
pub async fn close(&mut self) -> anyhow::Result<()> {
882-
self.jobs.finish().await?;
965+
pub async fn close(&mut self) -> Vec<anyhow::Error> {
966+
// [NOTE] we intentionally do not return early for errors because we
967+
// want to try to run as much cleanup as we can, regardless of
968+
// errors along the way
969+
let mut errs = Vec::new();
970+
971+
if let Err(err) = self
972+
.jobs
973+
.finish(&mut self.editor, Some(&mut self.compositor))
974+
.await
975+
{
976+
log::error!("Error executing job: {}", err);
977+
errs.push(err);
978+
};
979+
980+
if let Err(err) = self.editor.flush_writes().await {
981+
log::error!("Error writing: {}", err);
982+
errs.push(err);
983+
}
883984

884985
if self.editor.close_language_servers(None).await.is_err() {
885986
log::error!("Timed out waiting for language servers to shutdown");
886-
};
987+
errs.push(anyhow::format_err!(
988+
"Timed out waiting for language servers to shutdown"
989+
));
990+
}
887991

888-
Ok(())
992+
errs
889993
}
890994
}

0 commit comments

Comments
 (0)