aboutsummaryrefslogtreecommitdiff
path: root/helix-view
diff options
context:
space:
mode:
Diffstat (limited to 'helix-view')
-rw-r--r--helix-view/Cargo.toml9
-rw-r--r--helix-view/src/document.rs47
-rw-r--r--helix-view/src/editor.rs34
-rw-r--r--helix-view/src/graphics.rs4
-rw-r--r--helix-view/src/input.rs153
-rw-r--r--helix-view/src/view.rs3
6 files changed, 223 insertions, 27 deletions
diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml
index 34f55eb6..3c8866fc 100644
--- a/helix-view/Cargo.toml
+++ b/helix-view/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "helix-view"
-version = "0.5.0"
+version = "0.6.0"
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
edition = "2021"
license = "MPL-2.0"
@@ -16,12 +16,12 @@ term = ["crossterm"]
[dependencies]
bitflags = "1.3"
anyhow = "1"
-helix-core = { version = "0.5", path = "../helix-core" }
-helix-lsp = { version = "0.5", path = "../helix-lsp"}
+helix-core = { version = "0.6", path = "../helix-core" }
+helix-lsp = { version = "0.6", path = "../helix-lsp"}
crossterm = { version = "0.22", optional = true }
# Conversion traits
-once_cell = "1.8"
+once_cell = "1.9"
url = "2"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
@@ -29,7 +29,6 @@ futures-util = { version = "0.3", features = ["std", "async-await"], default-fea
slotmap = "1"
-encoding_rs = "0.8"
chardetng = "0.1"
serde = { version = "1.0", features = ["derive"] }
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index a0315bed..9185e483 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -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;
@@ -9,6 +10,7 @@ use std::str::FromStr;
use std::sync::Arc;
use helix_core::{
+ encoding,
history::History,
indent::{auto_detect_indent_style, IndentStyle},
line_ending::auto_detect_line_ending,
@@ -68,13 +70,22 @@ impl<'de> Deserialize<'de> for Mode {
}
}
+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,
pub(crate) selections: HashMap<ViewId, Selection>,
path: Option<PathBuf>,
- encoding: &'static encoding_rs::Encoding,
+ encoding: &'static encoding::Encoding,
/// Current editing mode.
pub mode: Mode,
@@ -143,8 +154,8 @@ impl fmt::Debug for Document {
/// be used to override encoding auto-detection.
pub fn from_reader<R: std::io::Read + ?Sized>(
reader: &mut R,
- encoding: Option<&'static encoding_rs::Encoding>,
-) -> Result<(Rope, &'static encoding_rs::Encoding), Error> {
+ encoding: Option<&'static encoding::Encoding>,
+) -> Result<(Rope, &'static encoding::Encoding), Error> {
// These two buffers are 8192 bytes in size each and are used as
// intermediaries during the decoding process. Text read into `buf`
// from `reader` is decoded into `buf_out` as UTF-8. Once either
@@ -212,11 +223,11 @@ pub fn from_reader<R: std::io::Read + ?Sized>(
total_read += read;
total_written += written;
match result {
- encoding_rs::CoderResult::InputEmpty => {
+ encoding::CoderResult::InputEmpty => {
debug_assert_eq!(slice.len(), total_read);
break;
}
- encoding_rs::CoderResult::OutputFull => {
+ encoding::CoderResult::OutputFull => {
debug_assert!(slice.len() > total_read);
builder.append(&buf_str[..total_written]);
total_written = 0;
@@ -251,7 +262,7 @@ pub fn from_reader<R: std::io::Read + ?Sized>(
/// replacement characters may appear in the encoded text.
pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
writer: &'a mut W,
- encoding: &'static encoding_rs::Encoding,
+ encoding: &'static encoding::Encoding,
rope: &'a Rope,
) -> Result<(), Error> {
// Text inside a `Rope` is stored as non-contiguous blocks of data called
@@ -286,12 +297,12 @@ pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
total_read += read;
total_written += written;
match result {
- encoding_rs::CoderResult::InputEmpty => {
+ encoding::CoderResult::InputEmpty => {
debug_assert_eq!(chunk.len(), total_read);
debug_assert!(buf.len() >= total_written);
break;
}
- encoding_rs::CoderResult::OutputFull => {
+ encoding::CoderResult::OutputFull => {
debug_assert!(chunk.len() > total_read);
writer.write_all(&buf[..total_written]).await?;
total_written = 0;
@@ -322,8 +333,8 @@ use helix_lsp::lsp;
use url::Url;
impl Document {
- pub fn from(text: Rope, encoding: Option<&'static encoding_rs::Encoding>) -> Self {
- let encoding = encoding.unwrap_or(encoding_rs::UTF_8);
+ pub fn from(text: Rope, encoding: Option<&'static encoding::Encoding>) -> Self {
+ let encoding = encoding.unwrap_or(encoding::UTF_8);
let changes = ChangeSet::new(&text);
let old_state = None;
@@ -356,7 +367,7 @@ impl Document {
/// overwritten with the `encoding` parameter.
pub fn open(
path: &Path,
- encoding: Option<&'static encoding_rs::Encoding>,
+ encoding: Option<&'static encoding::Encoding>,
theme: Option<&Theme>,
config_loader: Option<&syntax::Loader>,
) -> Result<Self, Error> {
@@ -366,7 +377,7 @@ impl Document {
std::fs::File::open(path).context(format!("unable to open {:?}", path))?;
from_reader(&mut file, encoding)?
} else {
- let encoding = encoding.unwrap_or(encoding_rs::UTF_8);
+ let encoding = encoding.unwrap_or(encoding::UTF_8);
(Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
};
@@ -548,7 +559,7 @@ impl Document {
/// Sets the [`Document`]'s encoding with the encoding correspondent to `label`.
pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> {
- match encoding_rs::Encoding::for_label(label.as_bytes()) {
+ match encoding::Encoding::for_label(label.as_bytes()) {
Some(encoding) => self.encoding = encoding,
None => return Err(anyhow::anyhow!("unknown encoding")),
}
@@ -556,7 +567,7 @@ impl Document {
}
/// Returns the [`Document`]'s current encoding.
- pub fn encoding(&self) -> &'static encoding_rs::Encoding {
+ pub fn encoding(&self) -> &'static encoding::Encoding {
self.encoding
}
@@ -889,6 +900,10 @@ impl Document {
self.indent_style.as_str()
}
+ pub fn changes(&self) -> &ChangeSet {
+ &self.changes
+ }
+
#[inline]
/// File path on disk.
pub fn path(&self) -> Option<&PathBuf> {
@@ -1119,7 +1134,7 @@ mod test {
macro_rules! test_decode {
($label:expr, $label_override:expr) => {
- let encoding = encoding_rs::Encoding::for_label($label_override.as_bytes()).unwrap();
+ let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
let path = base_path.join(format!("{}_in.txt", $label));
let ref_path = base_path.join(format!("{}_in_ref.txt", $label));
@@ -1138,7 +1153,7 @@ mod test {
macro_rules! test_encode {
($label:expr, $label_override:expr) => {
- let encoding = encoding_rs::Encoding::for_label($label_override.as_bytes()).unwrap();
+ let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
let path = base_path.join(format!("{}_out.txt", $label));
let ref_path = base_path.join(format!("{}_out_ref.txt", $label));
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index d65c1fb2..eef7520e 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -27,7 +27,7 @@ pub use helix_core::register::Registers;
use helix_core::syntax;
use helix_core::{Position, Selection};
-use serde::{Deserialize, Deserializer};
+use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize};
fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
@@ -37,7 +37,7 @@ where
Ok(Duration::from_millis(millis))
}
-#[derive(Debug, Clone, PartialEq, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct FilePickerConfig {
/// IgnoreOptions
@@ -77,7 +77,7 @@ impl Default for FilePickerConfig {
}
}
-#[derive(Debug, Clone, PartialEq, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct Config {
/// Padding to keep between the edge of the screen and the cursor when scrolling. Defaults to 5.
@@ -137,6 +137,20 @@ impl<'de> Deserialize<'de> for CursorShapeConfig {
}
}
+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];
@@ -151,7 +165,7 @@ impl Default for CursorShapeConfig {
}
}
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum LineNumber {
/// Show absolute line number
@@ -160,6 +174,18 @@ pub enum LineNumber {
Relative,
}
+impl std::str::FromStr for LineNumber {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s.to_lowercase().as_str() {
+ "absolute" | "abs" => Ok(Self::Absolute),
+ "relative" | "rel" => Ok(Self::Relative),
+ _ => anyhow::bail!("Line number can only be `absolute` or `relative`."),
+ }
+ }
+}
+
impl Default for Config {
fn default() -> Self {
Self {
diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs
index 892aa646..d75cde82 100644
--- a/helix-view/src/graphics.rs
+++ b/helix-view/src/graphics.rs
@@ -1,11 +1,11 @@
use bitflags::bitflags;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
use std::{
cmp::{max, min},
str::FromStr,
};
-#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
+#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
/// UNSTABLE
pub enum CursorKind {
diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs
index 92caa517..14dadc3b 100644
--- a/helix-view/src/input.rs
+++ b/helix-view/src/input.rs
@@ -254,6 +254,43 @@ impl From<KeyEvent> for crossterm::event::KeyEvent {
}
}
+pub fn parse_macro(keys_str: &str) -> anyhow::Result<Vec<KeyEvent>> {
+ use anyhow::Context;
+ let mut keys_res: anyhow::Result<_> = Ok(Vec::new());
+ let mut i = 0;
+ while let Ok(keys) = &mut keys_res {
+ if i >= keys_str.len() {
+ break;
+ }
+ if !keys_str.is_char_boundary(i) {
+ i += 1;
+ continue;
+ }
+
+ let s = &keys_str[i..];
+ let mut end_i = 1;
+ while !s.is_char_boundary(end_i) {
+ end_i += 1;
+ }
+ let c = &s[..end_i];
+ if c == ">" {
+ keys_res = Err(anyhow!("Unmatched '>'"));
+ } else if c != "<" {
+ keys.push(c);
+ i += end_i;
+ } else {
+ match s.find('>').context("'>' expected") {
+ Ok(end_i) => {
+ keys.push(&s[1..end_i]);
+ i += end_i + 1;
+ }
+ Err(err) => keys_res = Err(err),
+ }
+ }
+ }
+ keys_res.and_then(|keys| keys.into_iter().map(str::parse).collect())
+}
+
#[cfg(test)]
mod test {
use super::*;
@@ -339,4 +376,120 @@ mod test {
assert!(str::parse::<KeyEvent>("123").is_err());
assert!(str::parse::<KeyEvent>("S--").is_err());
}
+
+ #[test]
+ fn parsing_valid_macros() {
+ assert_eq!(
+ parse_macro("xdo").ok(),
+ Some(vec![
+ KeyEvent {
+ code: KeyCode::Char('x'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('d'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('o'),
+ modifiers: KeyModifiers::NONE,
+ },
+ ]),
+ );
+
+ assert_eq!(
+ parse_macro("<C-w>v<C-w>h<C-o>xx<A-s>").ok(),
+ Some(vec![
+ KeyEvent {
+ code: KeyCode::Char('w'),
+ modifiers: KeyModifiers::CONTROL,
+ },
+ KeyEvent {
+ code: KeyCode::Char('v'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('w'),
+ modifiers: KeyModifiers::CONTROL,
+ },
+ KeyEvent {
+ code: KeyCode::Char('h'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('o'),
+ modifiers: KeyModifiers::CONTROL,
+ },
+ KeyEvent {
+ code: KeyCode::Char('x'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('x'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('s'),
+ modifiers: KeyModifiers::ALT,
+ },
+ ])
+ );
+
+ assert_eq!(
+ parse_macro(":o foo.bar<ret>").ok(),
+ Some(vec![
+ KeyEvent {
+ code: KeyCode::Char(':'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('o'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char(' '),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('f'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('o'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('o'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('.'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('b'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('a'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('r'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Enter,
+ modifiers: KeyModifiers::NONE,
+ },
+ ])
+ );
+ }
+
+ #[test]
+ fn parsing_invalid_macros_fails() {
+ assert!(parse_macro("abc<C-").is_err());
+ assert!(parse_macro("abc>123").is_err());
+ assert!(parse_macro("wd<foo>").is_err());
+ }
}
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs
index 94d67acd..89a6c196 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -80,6 +80,8 @@ pub struct View {
// uses two docs because we want to be able to swap between the
// two last modified docs which we need to manually keep track of
pub last_modified_docs: [Option<DocumentId>; 2],
+ /// used to store previous selections of tree-sitter objecs
+ pub object_selections: Vec<Selection>,
}
impl View {
@@ -92,6 +94,7 @@ impl View {
jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel
last_accessed_doc: None,
last_modified_docs: [None, None],
+ object_selections: Vec::new(),
}
}