mirror of
https://github.com/ilyakooo0/helix.git
synced 2024-09-11 16:07:00 +03:00
use main application event loop
Use the Application's main event loop to allow LSP, file writes, etc
This commit is contained in:
parent
36e5809f63
commit
ee705dcb33
@ -4,7 +4,6 @@
|
||||
use crate::{graphemes, movement::Direction, Range, Rope, Selection, Tendril, Transaction};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use log::debug;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
// Heavily based on https://github.com/codemirror/closebrackets/
|
||||
@ -123,7 +122,7 @@ fn default() -> Self {
|
||||
|
||||
#[must_use]
|
||||
pub fn hook(doc: &Rope, selection: &Selection, ch: char, pairs: &AutoPairs) -> Option<Transaction> {
|
||||
debug!("autopairs hook selection: {:#?}", selection);
|
||||
log::trace!("autopairs hook selection: {:#?}", selection);
|
||||
|
||||
if let Some(pair) = pairs.get(ch) {
|
||||
if pair.same() {
|
||||
@ -225,9 +224,11 @@ fn get_next_range(
|
||||
// other end of the grapheme to get to where the new characters
|
||||
// are inserted, then move the head to where it should be
|
||||
let prev_bound = graphemes::prev_grapheme_boundary(doc_slice, start_range.head);
|
||||
debug!(
|
||||
log::trace!(
|
||||
"prev_bound: {}, offset: {}, len_inserted: {}",
|
||||
prev_bound, offset, len_inserted
|
||||
prev_bound,
|
||||
offset,
|
||||
len_inserted
|
||||
);
|
||||
prev_bound + offset + len_inserted
|
||||
};
|
||||
@ -302,7 +303,7 @@ fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
||||
});
|
||||
|
||||
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
|
||||
debug!("auto pair transaction: {:#?}", t);
|
||||
log::debug!("auto pair transaction: {:#?}", t);
|
||||
t
|
||||
}
|
||||
|
||||
@ -334,7 +335,7 @@ fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
||||
});
|
||||
|
||||
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
|
||||
debug!("auto pair transaction: {:#?}", t);
|
||||
log::debug!("auto pair transaction: {:#?}", t);
|
||||
t
|
||||
}
|
||||
|
||||
@ -374,7 +375,7 @@ fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
||||
});
|
||||
|
||||
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
|
||||
debug!("auto pair transaction: {:#?}", t);
|
||||
log::debug!("auto pair transaction: {:#?}", t);
|
||||
t
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use arc_swap::{access::Map, ArcSwap};
|
||||
use futures_util::Stream;
|
||||
use helix_core::{
|
||||
config::{default_syntax_loader, user_syntax_loader},
|
||||
pos_at_coords, syntax, Selection,
|
||||
@ -27,7 +28,7 @@
|
||||
use anyhow::Error;
|
||||
|
||||
use crossterm::{
|
||||
event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream},
|
||||
event::{DisableMouseCapture, EnableMouseCapture, Event},
|
||||
execute, terminal,
|
||||
tty::IsTty,
|
||||
};
|
||||
@ -68,7 +69,7 @@ fn setup_integration_logging() {
|
||||
message
|
||||
))
|
||||
})
|
||||
.level(log::LevelFilter::Info)
|
||||
.level(log::LevelFilter::Debug)
|
||||
.chain(std::io::stdout())
|
||||
.apply();
|
||||
}
|
||||
@ -225,8 +226,10 @@ fn render(&mut self) {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn event_loop(&mut self) {
|
||||
let mut reader = EventStream::new();
|
||||
pub async fn event_loop<S>(&mut self, input_stream: &mut S)
|
||||
where
|
||||
S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
|
||||
{
|
||||
let mut last_render = Instant::now();
|
||||
let deadline = Duration::from_secs(1) / 60;
|
||||
|
||||
@ -242,7 +245,7 @@ pub async fn event_loop(&mut self) {
|
||||
tokio::select! {
|
||||
biased;
|
||||
|
||||
Some(event) = reader.next() => {
|
||||
Some(event) = input_stream.next() => {
|
||||
self.handle_terminal_events(event)
|
||||
}
|
||||
Some(signal) = self.signals.next() => {
|
||||
@ -749,7 +752,10 @@ fn restore_term(&mut self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<i32, Error> {
|
||||
pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
|
||||
where
|
||||
S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
|
||||
{
|
||||
self.claim_term().await?;
|
||||
|
||||
// Exit the alternate screen and disable raw mode before panicking
|
||||
@ -764,16 +770,20 @@ pub async fn run(&mut self) -> Result<i32, Error> {
|
||||
hook(info);
|
||||
}));
|
||||
|
||||
self.event_loop().await;
|
||||
self.event_loop(input_stream).await;
|
||||
self.close().await?;
|
||||
self.restore_term()?;
|
||||
|
||||
Ok(self.editor.exit_code)
|
||||
}
|
||||
|
||||
pub async fn close(&mut self) -> anyhow::Result<()> {
|
||||
self.jobs.finish().await;
|
||||
|
||||
if self.editor.close_language_servers(None).await.is_err() {
|
||||
log::error!("Timed out waiting for language servers to shutdown");
|
||||
};
|
||||
|
||||
self.restore_term()?;
|
||||
|
||||
Ok(self.editor.exit_code)
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +95,7 @@ pub fn add(&self, j: Job) {
|
||||
/// Blocks until all the jobs that need to be waited on are done.
|
||||
pub async fn finish(&mut self) {
|
||||
let wait_futures = std::mem::take(&mut self.wait_futures);
|
||||
log::debug!("waiting on jobs...");
|
||||
wait_futures.for_each(|_| future::ready(())).await
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use anyhow::{Context, Error, Result};
|
||||
use crossterm::event::EventStream;
|
||||
use helix_term::application::Application;
|
||||
use helix_term::args::Args;
|
||||
use helix_term::config::Config;
|
||||
@ -134,7 +135,7 @@ async fn main_impl() -> Result<i32> {
|
||||
// TODO: use the thread local executor to spawn the application task separately from the work pool
|
||||
let mut app = Application::new(args, config).context("unable to create new application")?;
|
||||
|
||||
let exit_code = app.run().await?;
|
||||
let exit_code = app.run(&mut EventStream::new()).await?;
|
||||
|
||||
Ok(exit_code)
|
||||
}
|
||||
|
@ -17,7 +17,8 @@ async fn hello_world() -> anyhow::Result<()> {
|
||||
Args::default(),
|
||||
Config::default(),
|
||||
("#[\n|]#", "ihello world<esc>", "hello world#[|\n]#"),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -18,7 +18,8 @@ async fn auto_indent_c() -> anyhow::Result<()> {
|
||||
}
|
||||
"},
|
||||
),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -6,7 +6,8 @@ async fn auto_pairs_basic() -> anyhow::Result<()> {
|
||||
Args::default(),
|
||||
Config::default(),
|
||||
("#[\n|]#", "i(<esc>", "(#[|)]#\n"),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
test_key_sequence_text_result(
|
||||
Args::default(),
|
||||
@ -18,7 +19,8 @@ async fn auto_pairs_basic() -> anyhow::Result<()> {
|
||||
..Default::default()
|
||||
},
|
||||
("#[\n|]#", "i(<esc>", "(#[|\n]#"),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
use std::io::Write;
|
||||
use std::{io::Write, time::Duration};
|
||||
|
||||
use anyhow::bail;
|
||||
use crossterm::event::{Event, KeyEvent};
|
||||
use helix_core::{test, Selection, Transaction};
|
||||
use helix_term::{application::Application, args::Args, config::Config};
|
||||
use helix_view::{doc, input::parse_macro};
|
||||
use tokio::time::timeout;
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TestCase {
|
||||
@ -29,10 +32,44 @@ fn from((input, keys, output): (S, S, S)) -> Self {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_key_sequence<T: Into<TestCase>>(
|
||||
pub async fn test_key_sequence(
|
||||
app: &mut Application,
|
||||
in_keys: &str,
|
||||
test_fn: Option<&dyn Fn(&Application)>,
|
||||
) -> anyhow::Result<()> {
|
||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
for key_event in parse_macro(&in_keys)?.into_iter() {
|
||||
tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?;
|
||||
}
|
||||
|
||||
let mut rx_stream = UnboundedReceiverStream::new(rx);
|
||||
let event_loop = app.event_loop(&mut rx_stream);
|
||||
let result = timeout(Duration::from_millis(500), event_loop).await;
|
||||
|
||||
if result.is_ok() {
|
||||
bail!("application exited before test function could run");
|
||||
}
|
||||
|
||||
if let Some(test) = test_fn {
|
||||
test(app);
|
||||
};
|
||||
|
||||
for key_event in parse_macro("<esc>:q!<ret>")?.into_iter() {
|
||||
tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?;
|
||||
}
|
||||
|
||||
let event_loop = app.event_loop(&mut rx_stream);
|
||||
timeout(Duration::from_millis(5000), event_loop).await?;
|
||||
app.close().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>(
|
||||
app: Option<Application>,
|
||||
test_case: T,
|
||||
test_fn: &dyn Fn(&mut Application),
|
||||
test_fn: &dyn Fn(&Application),
|
||||
) -> anyhow::Result<()> {
|
||||
let test_case = test_case.into();
|
||||
let mut app =
|
||||
@ -50,23 +87,13 @@ pub fn test_key_sequence<T: Into<TestCase>>(
|
||||
view.id,
|
||||
);
|
||||
|
||||
let input_keys = parse_macro(&test_case.in_keys)?
|
||||
.into_iter()
|
||||
.map(|key_event| Event::Key(KeyEvent::from(key_event)));
|
||||
|
||||
for key in input_keys {
|
||||
app.handle_terminal_events(Ok(key));
|
||||
}
|
||||
|
||||
test_fn(&mut app);
|
||||
|
||||
Ok(())
|
||||
test_key_sequence(&mut app, &test_case.in_keys, Some(test_fn)).await
|
||||
}
|
||||
|
||||
/// Use this for very simple test cases where there is one input
|
||||
/// document, selection, and sequence of key presses, and you just
|
||||
/// want to verify the resulting document and selection.
|
||||
pub fn test_key_sequence_text_result<T: Into<TestCase>>(
|
||||
pub async fn test_key_sequence_text_result<T: Into<TestCase>>(
|
||||
args: Args,
|
||||
config: Config,
|
||||
test_case: T,
|
||||
@ -74,7 +101,7 @@ pub fn test_key_sequence_text_result<T: Into<TestCase>>(
|
||||
let test_case = test_case.into();
|
||||
let app = Application::new(args, config).unwrap();
|
||||
|
||||
test_key_sequence(Some(app), test_case.clone(), &|app| {
|
||||
test_key_sequence_with_input_text(Some(app), test_case.clone(), &|app| {
|
||||
let doc = doc!(app.editor);
|
||||
assert_eq!(&test_case.out_text, doc.text());
|
||||
|
||||
@ -83,9 +110,8 @@ pub fn test_key_sequence_text_result<T: Into<TestCase>>(
|
||||
|
||||
let sel = selections.pop().unwrap();
|
||||
assert_eq!(test_case.out_selection, sel);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn temp_file_with_contents<S: AsRef<str>>(content: S) -> tempfile::NamedTempFile {
|
||||
|
@ -14,25 +14,29 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> {
|
||||
out_text: String::new(),
|
||||
out_selection: Selection::single(0, 0),
|
||||
},
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
test_key_sequence_text_result(
|
||||
Args::default(),
|
||||
Config::default(),
|
||||
("#[\n|]#", "i", "#[|\n]#"),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
test_key_sequence_text_result(
|
||||
Args::default(),
|
||||
Config::default(),
|
||||
("#[\n|]#", "i<esc>", "#[|\n]#"),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
test_key_sequence_text_result(
|
||||
Args::default(),
|
||||
Config::default(),
|
||||
("#[\n|]#", "i<esc>i", "#[|\n]#"),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -44,7 +48,8 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
|
||||
Args::default(),
|
||||
Config::default(),
|
||||
("#[f|]#oo\n", "vll<A-;><esc>", "#[|foo]#\n"),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
test_key_sequence_text_result(
|
||||
Args::default(),
|
||||
@ -60,7 +65,8 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
|
||||
#(|bar)#"
|
||||
},
|
||||
),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
test_key_sequence_text_result(
|
||||
Args::default(),
|
||||
@ -76,7 +82,8 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
|
||||
#(ba|)#r"
|
||||
},
|
||||
),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
test_key_sequence_text_result(
|
||||
Args::default(),
|
||||
@ -92,7 +99,8 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
|
||||
#(b|)#ar"
|
||||
},
|
||||
),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
69
helix-term/tests/integration/write.rs
Normal file
69
helix-term/tests/integration/write.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
ops::RangeInclusive,
|
||||
};
|
||||
|
||||
use helix_term::application::Application;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_write() -> anyhow::Result<()> {
|
||||
let mut file = tempfile::NamedTempFile::new().unwrap();
|
||||
|
||||
test_key_sequence(
|
||||
&mut Application::new(
|
||||
Args {
|
||||
files: vec![(file.path().to_path_buf(), Position::default())],
|
||||
..Default::default()
|
||||
},
|
||||
Config::default(),
|
||||
)?,
|
||||
"ii can eat glass, it will not hurt me<ret><esc>:w<ret>",
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
file.as_file_mut().flush()?;
|
||||
file.as_file_mut().sync_all()?;
|
||||
|
||||
let mut file_content = String::new();
|
||||
file.as_file_mut().read_to_string(&mut file_content)?;
|
||||
assert_eq!("i can eat glass, it will not hurt me\n", file_content);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_write_concurrent() -> anyhow::Result<()> {
|
||||
let mut file = tempfile::NamedTempFile::new().unwrap();
|
||||
let mut command = String::new();
|
||||
const RANGE: RangeInclusive<i32> = 1..=1000;
|
||||
|
||||
for i in RANGE {
|
||||
let cmd = format!("%c{}<esc>:w<ret>", i);
|
||||
command.push_str(&cmd);
|
||||
}
|
||||
|
||||
test_key_sequence(
|
||||
&mut Application::new(
|
||||
Args {
|
||||
files: vec![(file.path().to_path_buf(), Position::default())],
|
||||
..Default::default()
|
||||
},
|
||||
Config::default(),
|
||||
)?,
|
||||
&command,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
file.as_file_mut().flush()?;
|
||||
file.as_file_mut().sync_all()?;
|
||||
|
||||
let mut file_content = String::new();
|
||||
file.as_file_mut().read_to_string(&mut file_content)?;
|
||||
assert_eq!(RANGE.end().to_string(), file_content);
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user