diff options
Diffstat (limited to 'helix-term/src/ui')
-rw-r--r-- | helix-term/src/ui/editor.rs | 23 | ||||
-rw-r--r-- | helix-term/src/ui/mod.rs | 2 | ||||
-rw-r--r-- | helix-term/src/ui/spinner.rs | 75 |
3 files changed, 98 insertions, 2 deletions
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index faede58c..44f331ff 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -3,7 +3,7 @@ use crate::{ compositor::{Component, Compositor, Context, EventResult}, key, keymap::{self, Keymaps}, - ui::Completion, + ui::{Completion, ProgressSpinners}, }; use helix_core::{ @@ -11,6 +11,7 @@ use helix_core::{ syntax::{self, HighlightEvent}, LineEnding, Position, Range, }; +use helix_lsp::LspProgressMap; use helix_view::{document::Mode, Document, Editor, Theme, View}; use std::borrow::Cow; @@ -31,6 +32,7 @@ pub struct EditorView { on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>, last_insert: (commands::Command, Vec<KeyEvent>), completion: Option<Completion>, + spinners: ProgressSpinners, } const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter @@ -48,9 +50,15 @@ impl EditorView { on_next_key: None, last_insert: (commands::Command::normal_mode, Vec::new()), completion: None, + spinners: ProgressSpinners::default(), } } + pub fn spinners_mut(&mut self) -> &mut ProgressSpinners { + &mut self.spinners + } + + #[allow(clippy::too_many_arguments)] pub fn render_view( &self, doc: &Document, @@ -458,6 +466,7 @@ impl EditorView { ); } + #[allow(clippy::too_many_arguments)] pub fn render_statusline( &self, doc: &Document, @@ -476,6 +485,15 @@ impl EditorView { Mode::Select => "SEL", Mode::Normal => "NOR", }; + let progress = doc + .language_server() + .and_then(|srv| { + self.spinners + .get(srv.id()) + .and_then(|spinner| spinner.frame()) + }) + .unwrap_or(""); + let style = if is_focused { theme.get("ui.statusline") } else { @@ -486,13 +504,14 @@ impl EditorView { if is_focused { surface.set_string(viewport.x + 1, viewport.y, mode, style); } + surface.set_string(viewport.x + 5, viewport.y, progress, style); if let Some(path) = doc.relative_path() { let path = path.to_string_lossy(); let title = format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" }); surface.set_stringn( - viewport.x + 6, + viewport.x + 8, viewport.y, title, viewport.width.saturating_sub(6) as usize, diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index e0177b7c..c062bffe 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -5,6 +5,7 @@ mod menu; mod picker; mod popup; mod prompt; +mod spinner; mod text; pub use completion::Completion; @@ -14,6 +15,7 @@ pub use menu::Menu; pub use picker::Picker; pub use popup::Popup; pub use prompt::{Prompt, PromptEvent}; +pub use spinner::{ProgressSpinners, Spinner}; pub use text::Text; pub use tui::layout::Rect; diff --git a/helix-term/src/ui/spinner.rs b/helix-term/src/ui/spinner.rs new file mode 100644 index 00000000..e8a43b48 --- /dev/null +++ b/helix-term/src/ui/spinner.rs @@ -0,0 +1,75 @@ +use std::{collections::HashMap, time::SystemTime}; + +#[derive(Default, Debug)] +pub struct ProgressSpinners { + inner: HashMap<usize, Spinner>, +} + +impl ProgressSpinners { + pub fn get(&self, id: usize) -> Option<&Spinner> { + self.inner.get(&id) + } + + pub fn get_or_create(&mut self, id: usize) -> &mut Spinner { + self.inner.entry(id).or_insert_with(Spinner::default) + } +} + +impl Default for Spinner { + fn default() -> Self { + Self::dots(80) + } +} + +#[derive(Debug)] +pub struct Spinner { + frames: Vec<&'static str>, + count: usize, + start: Option<SystemTime>, + interval: u64, +} + +impl Spinner { + /// Creates a new spinner with `frames` and `interval`. + /// Expects the frames count and interval to be greater than 0. + pub fn new(frames: Vec<&'static str>, interval: u64) -> Self { + let count = frames.len(); + assert!(count > 0); + assert!(interval > 0); + + Self { + frames, + count, + interval, + start: None, + } + } + + pub fn dots(interval: u64) -> Self { + Self::new(vec!["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"], interval) + } + + pub fn start(&mut self) { + self.start = Some(SystemTime::now()); + } + + pub fn frame(&self) -> Option<&str> { + let idx = (self + .start + .map(|time| SystemTime::now().duration_since(time))? + .ok()? + .as_millis() + / self.interval as u128) as usize + % self.count; + + self.frames.get(idx).copied() + } + + pub fn stop(&mut self) { + self.start = None; + } + + pub fn is_stopped(&self) -> bool { + self.start.is_none() + } +} |