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

Change cursor shape on mode change #1154

Merged
merged 8 commits into from
Jan 23, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
43 changes: 41 additions & 2 deletions book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,27 @@ To override global configuration parameters, create a `config.toml` file located
* Linux and Mac: `~/.config/helix/config.toml`
* Windows: `%AppData%\helix\config.toml`

Example config:

```toml
theme = "onedark"

[editor]
line-number = "relative"
mouse = false

[editor.cursor-shape]
insert = "bar"
normal = "block"
select = "underline"

[editor.file-picker]
hidden = false
```

## Editor

`[editor]` section of the config.
### `[editor]` Section

| Key | Description | Default |
|--|--|---------|
Expand All @@ -25,7 +43,28 @@ To override global configuration parameters, create a `config.toml` file located
| `auto-info` | Whether to display infoboxes | `true` |
| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. | `false` |

`[editor.filepicker]` section of the config. Sets options for file picker and global search. All but the last key listed in the default file-picker configuration below are IgnoreOptions: whether hidden files and files listed within ignore files are ignored by (not visible in) the helix file picker and global search. There is also one other key, `max-depth` available, which is not defined by default.
### `[editor.cursor-shape]` Section

Defines the shape of cursor in each mode. Note that due to limitations
of the terminal environment, only the primary cursor can change shape.

| Key | Description | Default |
| --- | ----------- | ------- |
| `normal` | Cursor shape in [normal mode][normal mode] | `block` |
| `insert` | Cursor shape in [insert mode][insert mode] | `block` |
| `select` | Cursor shape in [select mode][select mode] | `block` |

[normal mode]: ./keymap.md#normal-mode
[insert mode]: ./keymap.md#insert-mode
[select mode]: ./keymap.md#select--extend-mode

### `[editor.filepicker]` Section

Sets options for file picker and global search. All but the last key listed in
the default file-picker configuration below are IgnoreOptions: whether hidden
files and files listed within ignore files are ignored by (not visible in) the
helix file picker and global search. There is also one other key, `max-depth`
available, which is not defined by default.

| Key | Description | Default |
|--|--|---------|
Expand Down
1 change: 0 additions & 1 deletion book/src/themes.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,6 @@ These scopes are used for theming the editor interface.
| `ui.cursor.insert` | |
| `ui.cursor.select` | |
| `ui.cursor.match` | Matching bracket etc. |
| `ui.cursor.primary` | Cursor with primary selection |
| `ui.linenr` | |
| `ui.linenr.selected` | |
| `ui.statusline` | Statusline |
Expand Down
23 changes: 14 additions & 9 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,24 +232,25 @@ impl EditorView {
}
.unwrap_or(base_cursor_scope);

let primary_cursor_scope = theme
.find_scope_index("ui.cursor.primary")
.unwrap_or(cursor_scope);
let primary_selection_scope = theme
.find_scope_index("ui.selection.primary")
.unwrap_or(selection_scope);

let mut spans: Vec<(usize, std::ops::Range<usize>)> = Vec::new();
for (i, range) in selection.iter().enumerate() {
let (cursor_scope, selection_scope) = if i == primary_idx {
(primary_cursor_scope, primary_selection_scope)
let selection_is_primary = i == primary_idx;
let selection_scope = if selection_is_primary {
primary_selection_scope
} else {
(cursor_scope, selection_scope)
selection_scope
};

// Special-case: cursor at end of the rope.
if range.head == range.anchor && range.head == text.len_chars() {
spans.push((cursor_scope, range.head..range.head + 1));
if !selection_is_primary {
// Terminal cursor acts as the primary cursor
spans.push((cursor_scope, range.head..range.head + 1));
}
continue;
}

Expand All @@ -258,11 +259,15 @@ impl EditorView {
// Standard case.
let cursor_start = prev_grapheme_boundary(text, range.head);
spans.push((selection_scope, range.anchor..cursor_start));
spans.push((cursor_scope, cursor_start..range.head));
if !selection_is_primary {
spans.push((cursor_scope, cursor_start..range.head));
}
} else {
// Reverse case.
let cursor_end = next_grapheme_boundary(text, range.head);
spans.push((cursor_scope, range.head..cursor_end));
if !selection_is_primary {
spans.push((cursor_scope, range.head..cursor_end));
}
spans.push((selection_scope, cursor_end..range.anchor));
}
}
Expand Down
6 changes: 3 additions & 3 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ pub const SCRATCH_BUFFER_NAME: &str = "[scratch]";

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Mode {
Normal,
Select,
Insert,
Normal = 0,
Select = 1,
Insert = 2,
}
archseer marked this conversation as resolved.
Show resolved Hide resolved

impl Display for Mode {
Expand Down
54 changes: 48 additions & 6 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
clipboard::{get_clipboard_provider, ClipboardProvider},
document::SCRATCH_BUFFER_NAME,
document::{Mode, SCRATCH_BUFFER_NAME},
graphics::{CursorKind, Rect},
input::KeyEvent,
theme::{self, Theme},
Expand All @@ -10,7 +10,7 @@ use crate::{

use futures_util::future;
use std::{
collections::BTreeMap,
collections::{BTreeMap, HashMap},
io::stdin,
num::NonZeroUsize,
path::{Path, PathBuf},
Expand All @@ -27,7 +27,7 @@ pub use helix_core::register::Registers;
use helix_core::syntax;
use helix_core::{Position, Selection};

use serde::Deserialize;
use serde::{Deserialize, Deserializer};

fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
Expand Down Expand Up @@ -105,16 +105,51 @@ pub struct Config {
/// Whether to display infoboxes. Defaults to true.
pub auto_info: bool,
pub file_picker: FilePickerConfig,
/// Shape for cursor in each mode
pub cursor_shape: CursorShapeConfig,
/// Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. Defaults to `false`.
pub true_color: bool,
}

// Cursor shape is read and used on every rendered frame and so needs
// to be fast. Therefore we avoid a hashmap and use an enum indexed array.
#[derive(Debug, Clone, PartialEq)]
pub struct CursorShapeConfig([CursorKind; 3]);

impl<'de> Deserialize<'de> for CursorShapeConfig {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let m = HashMap::<Mode, CursorKind>::deserialize(deserializer)?;
let into_cursor = |mode: Mode| m.get(&mode).copied().unwrap_or_default();
Ok(CursorShapeConfig([
into_cursor(Mode::Normal),
into_cursor(Mode::Select),
into_cursor(Mode::Insert),
]))
}
}

impl std::ops::Deref for CursorShapeConfig {
type Target = [CursorKind; 3];

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl Default for CursorShapeConfig {
fn default() -> Self {
Self([CursorKind::Block; 3])
}
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum LineNumber {
/// Show absolute line number
Absolute,

/// Show relative line number to the primary cursor
Relative,
}
Expand All @@ -139,6 +174,7 @@ impl Default for Config {
completion_trigger_len: 2,
auto_info: true,
file_picker: FilePickerConfig::default(),
cursor_shape: CursorShapeConfig::default(),
true_color: false,
}
}
Expand Down Expand Up @@ -611,9 +647,15 @@ impl Editor {
let inner = view.inner_area();
pos.col += inner.x as usize;
pos.row += inner.y as usize;
(Some(pos), CursorKind::Hidden)
let cursorkind = self
.config
.cursor_shape
.get(doc.mode() as usize)
.copied()
.unwrap_or_default();
(Some(pos), cursorkind)
} else {
(None, CursorKind::Hidden)
(None, CursorKind::default())
}
}

Expand Down
10 changes: 9 additions & 1 deletion helix-view/src/graphics.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use bitflags::bitflags;
use serde::Deserialize;
use std::{
cmp::{max, min},
str::FromStr,
};

#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[serde(rename_all = "lowercase")]
/// UNSTABLE
pub enum CursorKind {
/// █
Expand All @@ -17,6 +19,12 @@ pub enum CursorKind {
Hidden,
}

impl Default for CursorKind {
fn default() -> Self {
Self::Block
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Margin {
pub vertical: u16,
Expand Down