From cc357d50964ecd5760f1367a8a7b63fd45a0f1ce Mon Sep 17 00:00:00 2001 From: wojciechkepka Date: Sun, 20 Jun 2021 21:31:45 +0200 Subject: [PATCH] Add progress spinners to status line --- helix-lsp/src/lib.rs | 4 +++ helix-term/src/application.rs | 51 ++++++++++++++++++++++++++++++----- helix-term/src/commands.rs | 2 +- helix-term/src/compositor.rs | 3 ++- helix-term/src/config.rs | 33 +++++++++-------------- helix-term/src/ui/editor.rs | 23 ++++++++++++++-- 6 files changed, 85 insertions(+), 31 deletions(-) diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 49d5527f7..b25a7aca5 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -347,6 +347,10 @@ impl LspProgressMap { self.0.get(&id) } + pub fn is_progressing(&self, id: usize) -> bool { + self.0.get(&id).map(|it| !it.is_empty()).unwrap_or_default() + } + /// Returns last progress status for a given server with `id` and `token`. pub fn progress(&self, id: usize, token: &lsp::ProgressToken) -> Option<&ProgressStatus> { self.0.get(&id).and_then(|values| values.get(token)) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 08853ed01..b67bb73d4 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -2,11 +2,18 @@ use helix_core::syntax; use helix_lsp::{lsp, LspProgressMap}; use helix_view::{document::Mode, theme, Document, Editor, Theme, View}; -use crate::{args::Args, compositor::Compositor, config::Config, keymap::Keymaps, ui}; +use crate::{ + args::Args, + compositor::Compositor, + config::Config, + keymap::Keymaps, + ui::{self, Spinner}, +}; use log::{error, info}; use std::{ + collections::HashMap, future::Future, io::{self, stdout, Stdout, Write}, path::PathBuf, @@ -42,7 +49,7 @@ pub struct Application { callbacks: LspCallbacks, lsp_progress: LspProgressMap, - lsp_progress_enabled: bool, + lsp_display_messages: bool, } impl Application { @@ -62,7 +69,7 @@ impl Application { .as_deref() .unwrap_or(include_bytes!("../../languages.toml")); - let theme = if let Some(theme) = &config.global.theme { + let theme = if let Some(theme) = &config.theme { match theme_loader.load(theme) { Ok(theme) => theme, Err(e) => { @@ -112,7 +119,7 @@ impl Application { syn_loader, callbacks: FuturesUnordered::new(), lsp_progress: LspProgressMap::new(), - lsp_progress_enabled: config.global.lsp_progress, + lsp_display_messages: config.lsp.display_messages, }; Ok(app) @@ -305,11 +312,23 @@ impl Application { (None, message, &None) } else { self.lsp_progress.end_progress(server_id, &token); + if !self.lsp_progress.is_progressing(server_id) { + let ui = self + .compositor + .find(std::any::type_name::()) + .unwrap(); + if let Some(ui) = + ui.as_any_mut().downcast_mut::() + { + ui.spinners_mut().get_or_create(server_id).stop(); + }; + } self.editor.clear_status(); return; } } }; + let token_d: &dyn std::fmt::Display = match &token { lsp::NumberOrString::Number(n) => n, lsp::NumberOrString::String(s) => s, @@ -342,14 +361,23 @@ impl Application { if let lsp::WorkDoneProgress::End(_) = work { self.lsp_progress.end_progress(server_id, &token); + if !self.lsp_progress.is_progressing(server_id) { + let ui = self + .compositor + .find(std::any::type_name::()) + .unwrap(); + if let Some(ui) = ui.as_any_mut().downcast_mut::() { + ui.spinners_mut().get_or_create(server_id).stop(); + }; + } } else { self.lsp_progress.update(server_id, token, work); } - if self.lsp_progress_enabled { + if self.lsp_display_messages { self.editor.set_status(status); - self.render(); } + self.render(); } _ => unreachable!(), } @@ -372,6 +400,17 @@ impl Application { MethodCall::WorkDoneProgressCreate(params) => { self.lsp_progress.create(server_id, params.token); + let ui = self + .compositor + .find(std::any::type_name::()) + .unwrap(); + if let Some(ui) = ui.as_any_mut().downcast_mut::() { + let spinner = ui.spinners_mut().get_or_create(server_id); + if spinner.is_stopped() { + spinner.start(); + } + }; + let doc = self.editor.documents().find(|doc| { doc.language_server() .map(|server| server.id() == server_id) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index bcf946b78..f87a440da 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -19,7 +19,7 @@ use anyhow::anyhow; use helix_lsp::{ lsp, util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range}, - OffsetEncoding, + LspProgressMap, OffsetEncoding, }; use insert::*; use movement::Movement; diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index 0e6a313d2..b04d4588b 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -1,9 +1,10 @@ // Each component declares it's own size constraints and gets fitted based on it's parent. // Q: how does this work with popups? // cursive does compositor.screen_mut().add_layer_at(pos::absolute(x, y), ) +use helix_core::Position; +use helix_lsp::LspProgressMap; use crossterm::event::Event; -use helix_core::Position; use tui::{buffer::Buffer as Surface, layout::Rect, terminal::CursorKind}; pub type Callback = Box; diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 2c95fae30..839235f13 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,35 +1,28 @@ use anyhow::{Error, Result}; -use std::{collections::HashMap, str::FromStr}; +use std::collections::HashMap; use serde::{de::Error as SerdeError, Deserialize, Serialize}; use crate::keymap::{parse_keymaps, Keymaps}; -pub struct GlobalConfig { - pub theme: Option, - pub lsp_progress: bool, -} - -impl Default for GlobalConfig { - fn default() -> Self { - Self { - lsp_progress: true, - theme: None, - } - } -} - #[derive(Default)] pub struct Config { - pub global: GlobalConfig, + pub theme: Option, + pub lsp: LspConfig, pub keymaps: Keymaps, } +#[derive(Default, Serialize, Deserialize)] +pub struct LspConfig { + pub display_messages: bool, +} + #[derive(Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] struct TomlConfig { theme: Option, - lsp_progress: Option, + #[serde(default)] + lsp: LspConfig, keys: Option>>, } @@ -40,10 +33,8 @@ impl<'de> Deserialize<'de> for Config { { let config = TomlConfig::deserialize(deserializer)?; Ok(Self { - global: GlobalConfig { - lsp_progress: config.lsp_progress.unwrap_or(true), - theme: config.theme, - }, + theme: config.theme, + lsp: config.lsp, keymaps: config .keys .map(|r| parse_keymaps(&r)) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 7f0d06e98..fcd6270ec 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}, 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>, last_insert: (commands::Command, Vec), completion: Option, + 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,