aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term/src/ui')
-rw-r--r--helix-term/src/ui/editor.rs23
-rw-r--r--helix-term/src/ui/mod.rs2
-rw-r--r--helix-term/src/ui/spinner.rs75
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()
+ }
+}