Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow a custom delay before a command popup shows up #2715

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ on unix operating systems.
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` |
| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
| `auto-info` | Whether to display infoboxes | `true` |
| `auto-info-delay` | Delay in milliseconds before infoboxes are displayed | `0` |
| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. | `false` |
| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file. | `[]` |
| `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` |
Expand Down
59 changes: 55 additions & 4 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
commands,
compositor::{Component, Context, Event, EventResult},
job, key,
keymap::{KeymapResult, Keymaps},
keymap::{KeyTrieNode, KeymapResult, Keymaps},
ui::{Completion, ProgressSpinners},
};

Expand All @@ -20,6 +20,7 @@ use helix_view::{
document::{Mode, SCRATCH_BUFFER_NAME},
editor::{CompleteAction, CursorShapeConfig},
graphics::{Color, CursorKind, Modifier, Rect, Style},
info::Delay,
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
keyboard::{KeyCode, KeyModifiers},
Document, Editor, Theme, View,
Expand Down Expand Up @@ -921,7 +922,11 @@ impl EditorView {
let mut last_mode = mode;
self.pseudo_pending.extend(self.keymaps.pending());
let key_result = self.keymaps.get(mode, event);
cxt.editor.autoinfo = self.keymaps.sticky().map(|node| node.infobox());
let is_pending = matches!(key_result, KeymapResult::Pending(_));
if !is_pending {
Comment on lines +925 to +926
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it would be too unreadable to have the matches! in the if expression here. I don't feel strongly about it though and I think you did this for readability so it's up to you if you want to keep it as-is

// if there is no pending popup, reset autoinfo immediately
cxt.editor.autoinfo = self.keymaps.sticky().map(|node| node.infobox());
}

let mut execute_command = |command: &commands::MappableCommand| {
command.execute(cxt);
Expand Down Expand Up @@ -959,7 +964,7 @@ impl EditorView {
KeymapResult::Matched(command) => {
execute_command(command);
}
KeymapResult::Pending(node) => cxt.editor.autoinfo = Some(node.infobox()),
KeymapResult::Pending(node) => self.handle_pending_node(cxt, node),
KeymapResult::MatchedSequence(commands) => {
for command in commands {
execute_command(command);
Expand Down Expand Up @@ -1057,6 +1062,50 @@ impl EditorView {
}
}

fn handle_pending_node(&mut self, cxt: &mut commands::Context, node: &KeyTrieNode) {
let infobox = node.infobox();

let auto_info_delay = cxt.editor.config().auto_info_delay;
if auto_info_delay.is_zero() || node.is_sticky {
cxt.editor.autoinfo = Some(infobox);
return;
}

// if autoinfo popup already opened
if cxt
.editor
.autoinfo
.as_ref()
.map(|info| !info.is_delayed())
.unwrap_or(false)
{
// replace it without any delay
cxt.editor.autoinfo = Some(infobox);
return;
}

let (cancel_tx, cancel_rx) = tokio::sync::oneshot::channel::<()>();

let infobox_delayed = infobox.with_delay(Delay { cancel_tx });
cxt.editor.autoinfo = Some(infobox_delayed);

let callback = async move {
let call: job::Callback = tokio::select! {
// delay timed out
_ = tokio::time::sleep(auto_info_delay) => Box::new(move |editor, _compositor| {
if let Some(autoinfo) = &mut editor.autoinfo {
autoinfo.delay = None
}
}),
// delay cancelled
_ = cancel_rx => Box::new(move |_editor, _compositor| {}),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah this is clever - this doesn't need to be triggered explicitly because it's marked as completed in the Drop implementation for Sender, eh?

It's probably worth a comment here on how this triggers since it's kinda hidden in the implementation details

};
Ok(call)
};

cxt.jobs.callback(callback);
}

pub fn set_completion(
&mut self,
editor: &mut Editor,
Expand Down Expand Up @@ -1461,7 +1510,9 @@ impl Component for EditorView {

if config.auto_info {
if let Some(mut info) = cx.editor.autoinfo.take() {
info.render(area, surface, cx);
if !info.is_delayed() {
info.render(area, surface, cx);
}
cx.editor.autoinfo = Some(info)
}
}
Expand Down
7 changes: 7 additions & 0 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ pub struct Config {
pub completion_trigger_len: u8,
/// Whether to display infoboxes. Defaults to true.
pub auto_info: bool,
/// Delay in milliseconds before infoboxes are displayed. Defaults to 0 (disabled).
#[serde(
serialize_with = "serialize_duration_millis",
deserialize_with = "deserialize_duration_millis"
)]
pub auto_info_delay: Duration,
pub file_picker: FilePickerConfig,
/// Configuration of the statusline elements
pub statusline: StatusLineConfig,
Expand Down Expand Up @@ -593,6 +599,7 @@ impl Default for Config {
idle_timeout: Duration::from_millis(400),
completion_trigger_len: 2,
auto_info: true,
auto_info_delay: Duration::default(),
file_picker: FilePickerConfig::default(),
statusline: StatusLineConfig::default(),
cursor_shape: CursorShapeConfig::default(),
Expand Down
20 changes: 20 additions & 0 deletions helix-view/src/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ use crate::input::KeyEvent;
use helix_core::{register::Registers, unicode::width::UnicodeWidthStr};
use std::{collections::BTreeSet, fmt::Write};

#[derive(Debug)]
pub struct Delay {
pub cancel_tx: tokio::sync::oneshot::Sender<()>,
}

#[derive(Debug)]
/// Info box used in editor. Rendering logic will be in other crate.
pub struct Info {
Expand All @@ -13,6 +18,8 @@ pub struct Info {
pub width: u16,
/// Body height.
pub height: u16,
/// Popup delay.
pub delay: Option<Delay>,
}

impl Info {
Expand All @@ -27,6 +34,7 @@ impl Info {
height: 1,
width: title.len() as u16,
text: "".to_string(),
delay: None,
};
}

Expand All @@ -52,6 +60,7 @@ impl Info {
width: text.lines().map(|l| l.width()).max().unwrap() as u16,
height: body.len() as u16,
text,
delay: None,
}
}

Expand Down Expand Up @@ -86,3 +95,14 @@ impl Info {
infobox
}
}

impl Info {
pub fn with_delay(mut self, delay: Delay) -> Info {
self.delay = Some(delay);
self
}

pub fn is_delayed(&self) -> bool {
self.delay.is_some()
}
}