Merge pull request #1154 from sudormrfbin/cursor-shape-new
Change cursor shape on mode change
This commit is contained in:
commit
e2d2f19fd0
@ -5,9 +5,27 @@ # Configuration
|
||||
* 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 |
|
||||
|--|--|---------|
|
||||
@ -25,7 +43,28 @@ ## Editor
|
||||
| `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.file-picker]` 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.file-picker]` 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 |
|
||||
|--|--|---------|
|
||||
|
@ -19,6 +19,7 @@
|
||||
};
|
||||
use helix_view::{
|
||||
document::{Mode, SCRATCH_BUFFER_NAME},
|
||||
editor::CursorShapeConfig,
|
||||
graphics::{CursorKind, Modifier, Rect, Style},
|
||||
info::Info,
|
||||
input::KeyEvent,
|
||||
@ -80,7 +81,7 @@ pub fn render_view(
|
||||
let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
|
||||
Box::new(syntax::merge(
|
||||
highlights,
|
||||
Self::doc_selection_highlights(doc, view, theme),
|
||||
Self::doc_selection_highlights(doc, view, theme, &config.cursor_shape),
|
||||
))
|
||||
} else {
|
||||
Box::new(highlights)
|
||||
@ -195,11 +196,16 @@ pub fn doc_selection_highlights(
|
||||
doc: &Document,
|
||||
view: &View,
|
||||
theme: &Theme,
|
||||
cursor_shape_config: &CursorShapeConfig,
|
||||
) -> Vec<(usize, std::ops::Range<usize>)> {
|
||||
let text = doc.text().slice(..);
|
||||
let selection = doc.selection(view.id);
|
||||
let primary_idx = selection.primary_index();
|
||||
|
||||
let mode = doc.mode();
|
||||
let cursorkind = cursor_shape_config.from_mode(mode);
|
||||
let cursor_is_block = cursorkind == CursorKind::Block;
|
||||
|
||||
let selection_scope = theme
|
||||
.find_scope_index("ui.selection")
|
||||
.expect("could not find `ui.selection` scope in the theme!");
|
||||
@ -207,7 +213,7 @@ pub fn doc_selection_highlights(
|
||||
.find_scope_index("ui.cursor")
|
||||
.unwrap_or(selection_scope);
|
||||
|
||||
let cursor_scope = match doc.mode() {
|
||||
let cursor_scope = match mode {
|
||||
Mode::Insert => theme.find_scope_index("ui.cursor.insert"),
|
||||
Mode::Select => theme.find_scope_index("ui.cursor.select"),
|
||||
Mode::Normal => Some(base_cursor_scope),
|
||||
@ -223,7 +229,8 @@ pub fn doc_selection_highlights(
|
||||
|
||||
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 {
|
||||
let selection_is_primary = i == primary_idx;
|
||||
let (cursor_scope, selection_scope) = if selection_is_primary {
|
||||
(primary_cursor_scope, primary_selection_scope)
|
||||
} else {
|
||||
(cursor_scope, selection_scope)
|
||||
@ -231,7 +238,14 @@ pub fn doc_selection_highlights(
|
||||
|
||||
// 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 || cursor_is_block {
|
||||
// Bar and underline cursors are drawn by the terminal
|
||||
// BUG: If the editor area loses focus while having a bar or
|
||||
// underline cursor (eg. when a regex prompt has focus) then
|
||||
// the primary cursor will be invisible. This doesn't happen
|
||||
// with block cursors since we manually draw *all* cursors.
|
||||
spans.push((cursor_scope, range.head..range.head + 1));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -240,11 +254,15 @@ pub fn doc_selection_highlights(
|
||||
// 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 || cursor_is_block {
|
||||
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 || cursor_is_block {
|
||||
spans.push((cursor_scope, range.head..cursor_end));
|
||||
}
|
||||
spans.push((selection_scope, cursor_end..range.anchor));
|
||||
}
|
||||
}
|
||||
@ -1141,11 +1159,11 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
||||
}
|
||||
|
||||
fn cursor(&self, _area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
|
||||
// match view.doc.mode() {
|
||||
// Mode::Insert => write!(stdout, "\x1B[6 q"),
|
||||
// mode => write!(stdout, "\x1B[2 q"),
|
||||
// };
|
||||
editor.cursor()
|
||||
match editor.cursor() {
|
||||
// All block cursors are drawn manually
|
||||
(pos, CursorKind::Block) => (pos, CursorKind::Hidden),
|
||||
cursor => cursor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
use anyhow::{anyhow, Context, Error};
|
||||
use serde::de::{self, Deserialize, Deserializer};
|
||||
use serde::Serialize;
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
@ -30,9 +31,9 @@
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Mode {
|
||||
Normal,
|
||||
Select,
|
||||
Insert,
|
||||
Normal = 0,
|
||||
Select = 1,
|
||||
Insert = 2,
|
||||
}
|
||||
|
||||
impl Display for Mode {
|
||||
@ -69,6 +70,15 @@ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Mode {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.collect_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Document {
|
||||
pub(crate) id: DocumentId,
|
||||
text: Rope,
|
||||
|
@ -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},
|
||||
@ -10,7 +10,7 @@
|
||||
|
||||
use futures_util::future;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
collections::{BTreeMap, HashMap},
|
||||
io::stdin,
|
||||
num::NonZeroUsize,
|
||||
path::{Path, PathBuf},
|
||||
@ -27,7 +27,7 @@
|
||||
use helix_core::syntax;
|
||||
use helix_core::{Position, Selection};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize};
|
||||
|
||||
fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||
where
|
||||
@ -105,16 +105,71 @@ 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 CursorShapeConfig {
|
||||
pub fn from_mode(&self, mode: Mode) -> CursorKind {
|
||||
self.get(mode as usize).copied().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
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 Serialize for CursorShapeConfig {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut map = serializer.serialize_map(Some(self.len()))?;
|
||||
let modes = [Mode::Normal, Mode::Select, Mode::Insert];
|
||||
for mode in modes {
|
||||
map.serialize_entry(&mode, &self.from_mode(mode))?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
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, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum LineNumber {
|
||||
/// Show absolute line number
|
||||
Absolute,
|
||||
|
||||
/// Show relative line number to the primary cursor
|
||||
Relative,
|
||||
}
|
||||
@ -151,6 +206,7 @@ fn default() -> Self {
|
||||
completion_trigger_len: 2,
|
||||
auto_info: true,
|
||||
file_picker: FilePickerConfig::default(),
|
||||
cursor_shape: CursorShapeConfig::default(),
|
||||
true_color: false,
|
||||
}
|
||||
}
|
||||
@ -614,9 +670,10 @@ pub fn cursor(&self) -> (Option<Position>, CursorKind) {
|
||||
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.from_mode(doc.mode());
|
||||
(Some(pos), cursorkind)
|
||||
} else {
|
||||
(None, CursorKind::Hidden)
|
||||
(None, CursorKind::default())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
use bitflags::bitflags;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
/// UNSTABLE
|
||||
pub enum CursorKind {
|
||||
/// █
|
||||
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user