diff options
author | Nathan Vegdahl | 2021-07-06 03:27:49 +0000 |
---|---|---|
committer | Nathan Vegdahl | 2021-07-06 03:27:49 +0000 |
commit | 85d5b399de70ff075a455ce2858549d1ed012fe3 (patch) | |
tree | 4824fcb47f39551d3b31a62931adaf0ee406c02b /helix-view/src | |
parent | 6e15c9b8745e9708ee5271c8701d41a8393cb038 (diff) | |
parent | 3c31f501164080998975883eb6f93c49bd8d3efb (diff) |
Merge branch 'master' into great_line_ending_and_cursor_range_cleanup
Diffstat (limited to 'helix-view/src')
-rw-r--r-- | helix-view/src/document.rs | 84 | ||||
-rw-r--r-- | helix-view/src/editor.rs | 3 | ||||
-rw-r--r-- | helix-view/src/info.rs | 57 | ||||
-rw-r--r-- | helix-view/src/input.rs | 165 | ||||
-rw-r--r-- | helix-view/src/lib.rs | 1 |
5 files changed, 249 insertions, 61 deletions
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 86f3dfb8..b917b902 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -70,7 +70,6 @@ pub enum IndentStyle { } pub struct Document { - // rope + selection pub(crate) id: DocumentId, text: Rope, pub(crate) selections: HashMap<ViewId, Selection>, @@ -307,6 +306,19 @@ pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>( Ok(()) } +/// Inserts the final line ending into `rope` if it's missing. [Why?](https://stackoverflow.com/questions/729692/why-should-text-files-end-with-a-newline) +pub fn with_line_ending(rope: &mut Rope) -> LineEnding { + // search for line endings + let line_ending = auto_detect_line_ending(rope).unwrap_or(DEFAULT_LINE_ENDING); + + // add missing newline at the end of file + if rope.len_bytes() == 0 || !char_is_line_ending(rope.char(rope.len_chars() - 1)) { + rope.insert(rope.len_chars(), line_ending.as_str()); + } + + line_ending +} + /// Like std::mem::replace() except it allows the replacement value to be mapped from the /// original value. fn take_with<T, F>(mut_ref: &mut T, closure: F) @@ -395,12 +407,13 @@ pub fn normalize_path(path: &Path) -> PathBuf { /// This function is used instead of `std::fs::canonicalize` because we don't want to verify /// here if the path exists, just normalize it's components. pub fn canonicalize_path(path: &Path) -> std::io::Result<PathBuf> { - let normalized = normalize_path(path); - if normalized.is_absolute() { - Ok(normalized) + let path = if path.is_relative() { + std::env::current_dir().map(|current_dir| current_dir.join(path))? } else { - std::env::current_dir().map(|current_dir| current_dir.join(normalized)) - } + path.to_path_buf() + }; + + Ok(normalize_path(&path)) } use helix_lsp::lsp; @@ -448,7 +461,8 @@ impl Document { } let mut file = std::fs::File::open(&path).context(format!("unable to open {:?}", path))?; - let (rope, encoding) = from_reader(&mut file, encoding)?; + let (mut rope, encoding) = from_reader(&mut file, encoding)?; + let line_ending = with_line_ending(&mut rope); let mut doc = Self::from(rope, Some(encoding)); @@ -458,9 +472,9 @@ impl Document { doc.detect_language(theme, loader); } - // Detect indentation style and line ending. + // Detect indentation style and set line ending. doc.detect_indent_style(); - doc.line_ending = auto_detect_line_ending(&doc.text).unwrap_or(DEFAULT_LINE_ENDING); + doc.line_ending = line_ending; Ok(doc) } @@ -578,6 +592,45 @@ impl Document { } } + /// Reload the document from its path. + pub fn reload(&mut self, view_id: ViewId) -> Result<(), Error> { + let encoding = &self.encoding; + let path = self.path().filter(|path| path.exists()); + + // If there is no path or the path no longer exists. + if path.is_none() { + return Err(anyhow!("can't find file to reload from")); + } + + let mut file = std::fs::File::open(path.unwrap())?; + let (mut rope, ..) = from_reader(&mut file, Some(encoding))?; + let line_ending = with_line_ending(&mut rope); + + let transaction = helix_core::diff::compare_ropes(self.text(), &rope); + self.apply(&transaction, view_id); + self.append_changes_to_history(view_id); + + // Detect indentation style and set line ending. + self.detect_indent_style(); + self.line_ending = line_ending; + + Ok(()) + } + + /// 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()) { + Some(encoding) => self.encoding = encoding, + None => return Err(anyhow::anyhow!("unknown encoding")), + } + Ok(()) + } + + /// Returns the [`Document`]'s current encoding. + pub fn encoding(&self) -> &'static encoding_rs::Encoding { + self.encoding + } + fn detect_indent_style(&mut self) { // Build a histogram of the indentation *increases* between // subsequent lines, ignoring lines that are all whitespace. @@ -996,14 +1049,11 @@ impl Document { let cwdir = std::env::current_dir().expect("couldn't determine current directory"); self.path.as_ref().map(|path| { - let path = fold_home_dir(path); - if path.is_relative() { - path - } else { - path.strip_prefix(cwdir) - .map(|p| p.to_path_buf()) - .unwrap_or(path) - } + let mut path = path.as_path(); + if path.is_absolute() { + path = path.strip_prefix(cwdir).unwrap_or(path) + }; + fold_home_dir(path) }) } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index a16cc50f..4f01cce4 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1,6 +1,7 @@ use crate::{ clipboard::{get_clipboard_provider, ClipboardProvider}, graphics::{CursorKind, Rect}, + info::Info, theme::{self, Theme}, tree::Tree, Document, DocumentId, RegisterSelection, View, ViewId, @@ -32,6 +33,7 @@ pub struct Editor { pub syn_loader: Arc<syntax::Loader>, pub theme_loader: Arc<theme::Loader>, + pub autoinfo: Option<&'static Info>, pub status_msg: Option<(String, Severity)>, } @@ -64,6 +66,7 @@ impl Editor { theme_loader: themes, registers: Registers::default(), clipboard_provider: get_clipboard_provider(), + autoinfo: None, status_msg: None, } } diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs new file mode 100644 index 00000000..f3df50fe --- /dev/null +++ b/helix-view/src/info.rs @@ -0,0 +1,57 @@ +use crate::input::KeyEvent; +use helix_core::unicode::width::UnicodeWidthStr; +use std::fmt::Write; + +#[derive(Debug)] +/// Info box used in editor. Rendering logic will be in other crate. +pub struct Info { + /// Title kept as static str for now. + pub title: &'static str, + /// Text body, should contains newline. + pub text: String, + /// Body width. + pub width: u16, + /// Body height. + pub height: u16, +} + +impl Info { + pub fn key(title: &'static str, body: Vec<(&[KeyEvent], &'static str)>) -> Info { + let (lpad, mpad, rpad) = (1, 2, 1); + let keymaps_width: u16 = body + .iter() + .map(|r| r.0.iter().map(|e| e.width() as u16 + 2).sum::<u16>() - 2) + .max() + .unwrap(); + let mut text = String::new(); + let mut width = 0; + let height = body.len() as u16; + for (keyevents, desc) in body { + let keyevent = keyevents[0]; + let mut left = keymaps_width - keyevent.width() as u16; + for _ in 0..lpad { + text.push(' '); + } + write!(text, "{}", keyevent).ok(); + for keyevent in &keyevents[1..] { + write!(text, ", {}", keyevent).ok(); + left -= 2 + keyevent.width() as u16; + } + for _ in 0..left + mpad { + text.push(' '); + } + let desc = desc.trim(); + let w = lpad + keymaps_width + mpad + (desc.width() as u16) + rpad; + if w > width { + width = w; + } + writeln!(text, "{}", desc).ok(); + } + Info { + title, + text, + width, + height, + } + } +} diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index 5f61ce14..2847bb69 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -1,5 +1,6 @@ //! Input event handling, currently backed by crossterm. use anyhow::{anyhow, Error}; +use helix_core::unicode::width::UnicodeWidthStr; use serde::de::{self, Deserialize, Deserializer}; use std::fmt; @@ -13,6 +14,32 @@ pub struct KeyEvent { pub modifiers: KeyModifiers, } +pub(crate) mod keys { + pub(crate) const BACKSPACE: &str = "backspace"; + pub(crate) const ENTER: &str = "ret"; + pub(crate) const LEFT: &str = "left"; + pub(crate) const RIGHT: &str = "right"; + pub(crate) const UP: &str = "up"; + pub(crate) const DOWN: &str = "down"; + pub(crate) const HOME: &str = "home"; + pub(crate) const END: &str = "end"; + pub(crate) const PAGEUP: &str = "pageup"; + pub(crate) const PAGEDOWN: &str = "pagedown"; + pub(crate) const TAB: &str = "tab"; + pub(crate) const BACKTAB: &str = "backtab"; + pub(crate) const DELETE: &str = "del"; + pub(crate) const INSERT: &str = "ins"; + pub(crate) const NULL: &str = "null"; + pub(crate) const ESC: &str = "esc"; + pub(crate) const SPACE: &str = "space"; + pub(crate) const LESS_THAN: &str = "lt"; + pub(crate) const GREATER_THAN: &str = "gt"; + pub(crate) const PLUS: &str = "plus"; + pub(crate) const MINUS: &str = "minus"; + pub(crate) const SEMICOLON: &str = "semicolon"; + pub(crate) const PERCENT: &str = "percent"; +} + impl fmt::Display for KeyEvent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!( @@ -34,28 +61,29 @@ impl fmt::Display for KeyEvent { }, ))?; match self.code { - KeyCode::Backspace => f.write_str("backspace")?, - KeyCode::Enter => f.write_str("ret")?, - KeyCode::Left => f.write_str("left")?, - KeyCode::Right => f.write_str("right")?, - KeyCode::Up => f.write_str("up")?, - KeyCode::Down => f.write_str("down")?, - KeyCode::Home => f.write_str("home")?, - KeyCode::End => f.write_str("end")?, - KeyCode::PageUp => f.write_str("pageup")?, - KeyCode::PageDown => f.write_str("pagedown")?, - KeyCode::Tab => f.write_str("tab")?, - KeyCode::BackTab => f.write_str("backtab")?, - KeyCode::Delete => f.write_str("del")?, - KeyCode::Insert => f.write_str("ins")?, - KeyCode::Null => f.write_str("null")?, - KeyCode::Esc => f.write_str("esc")?, - KeyCode::Char('<') => f.write_str("lt")?, - KeyCode::Char('>') => f.write_str("gt")?, - KeyCode::Char('+') => f.write_str("plus")?, - KeyCode::Char('-') => f.write_str("minus")?, - KeyCode::Char(';') => f.write_str("semicolon")?, - KeyCode::Char('%') => f.write_str("percent")?, + KeyCode::Backspace => f.write_str(keys::BACKSPACE)?, + KeyCode::Enter => f.write_str(keys::ENTER)?, + KeyCode::Left => f.write_str(keys::LEFT)?, + KeyCode::Right => f.write_str(keys::RIGHT)?, + KeyCode::Up => f.write_str(keys::UP)?, + KeyCode::Down => f.write_str(keys::DOWN)?, + KeyCode::Home => f.write_str(keys::HOME)?, + KeyCode::End => f.write_str(keys::END)?, + KeyCode::PageUp => f.write_str(keys::PAGEUP)?, + KeyCode::PageDown => f.write_str(keys::PAGEDOWN)?, + KeyCode::Tab => f.write_str(keys::TAB)?, + KeyCode::BackTab => f.write_str(keys::BACKTAB)?, + KeyCode::Delete => f.write_str(keys::DELETE)?, + KeyCode::Insert => f.write_str(keys::INSERT)?, + KeyCode::Null => f.write_str(keys::NULL)?, + KeyCode::Esc => f.write_str(keys::ESC)?, + KeyCode::Char(' ') => f.write_str(keys::SPACE)?, + KeyCode::Char('<') => f.write_str(keys::LESS_THAN)?, + KeyCode::Char('>') => f.write_str(keys::GREATER_THAN)?, + KeyCode::Char('+') => f.write_str(keys::PLUS)?, + KeyCode::Char('-') => f.write_str(keys::MINUS)?, + KeyCode::Char(';') => f.write_str(keys::SEMICOLON)?, + KeyCode::Char('%') => f.write_str(keys::PERCENT)?, KeyCode::F(i) => f.write_fmt(format_args!("F{}", i))?, KeyCode::Char(c) => f.write_fmt(format_args!("{}", c))?, }; @@ -63,34 +91,83 @@ impl fmt::Display for KeyEvent { } } +impl UnicodeWidthStr for KeyEvent { + fn width(&self) -> usize { + use helix_core::unicode::width::UnicodeWidthChar; + let mut width = match self.code { + KeyCode::Backspace => keys::BACKSPACE.len(), + KeyCode::Enter => keys::ENTER.len(), + KeyCode::Left => keys::LEFT.len(), + KeyCode::Right => keys::RIGHT.len(), + KeyCode::Up => keys::UP.len(), + KeyCode::Down => keys::DOWN.len(), + KeyCode::Home => keys::HOME.len(), + KeyCode::End => keys::END.len(), + KeyCode::PageUp => keys::PAGEUP.len(), + KeyCode::PageDown => keys::PAGEDOWN.len(), + KeyCode::Tab => keys::TAB.len(), + KeyCode::BackTab => keys::BACKTAB.len(), + KeyCode::Delete => keys::DELETE.len(), + KeyCode::Insert => keys::INSERT.len(), + KeyCode::Null => keys::NULL.len(), + KeyCode::Esc => keys::ESC.len(), + KeyCode::Char(' ') => keys::SPACE.len(), + KeyCode::Char('<') => keys::LESS_THAN.len(), + KeyCode::Char('>') => keys::GREATER_THAN.len(), + KeyCode::Char('+') => keys::PLUS.len(), + KeyCode::Char('-') => keys::MINUS.len(), + KeyCode::Char(';') => keys::SEMICOLON.len(), + KeyCode::Char('%') => keys::PERCENT.len(), + KeyCode::F(1..=9) => 2, + KeyCode::F(_) => 3, + KeyCode::Char(c) => c.width().unwrap_or(0), + }; + if self.modifiers.contains(KeyModifiers::SHIFT) { + width += 2; + } + if self.modifiers.contains(KeyModifiers::ALT) { + width += 2; + } + if self.modifiers.contains(KeyModifiers::CONTROL) { + width += 2; + } + width + } + + fn width_cjk(&self) -> usize { + self.width() + } +} + impl std::str::FromStr for KeyEvent { type Err = Error; fn from_str(s: &str) -> Result<Self, Self::Err> { let mut tokens: Vec<_> = s.split('-').collect(); let code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? { - "backspace" => KeyCode::Backspace, - "space" => KeyCode::Char(' '), - "ret" => KeyCode::Enter, - "lt" => KeyCode::Char('<'), - "gt" => KeyCode::Char('>'), - "plus" => KeyCode::Char('+'), - "minus" => KeyCode::Char('-'), - "semicolon" => KeyCode::Char(';'), - "percent" => KeyCode::Char('%'), - "left" => KeyCode::Left, - "right" => KeyCode::Right, - "up" => KeyCode::Down, - "home" => KeyCode::Home, - "end" => KeyCode::End, - "pageup" => KeyCode::PageUp, - "pagedown" => KeyCode::PageDown, - "tab" => KeyCode::Tab, - "backtab" => KeyCode::BackTab, - "del" => KeyCode::Delete, - "ins" => KeyCode::Insert, - "null" => KeyCode::Null, - "esc" => KeyCode::Esc, + keys::BACKSPACE => KeyCode::Backspace, + keys::ENTER => KeyCode::Enter, + keys::LEFT => KeyCode::Left, + keys::RIGHT => KeyCode::Right, + keys::UP => KeyCode::Up, + keys::DOWN => KeyCode::Down, + keys::HOME => KeyCode::Home, + keys::END => KeyCode::End, + keys::PAGEUP => KeyCode::PageUp, + keys::PAGEDOWN => KeyCode::PageDown, + keys::TAB => KeyCode::Tab, + keys::BACKTAB => KeyCode::BackTab, + keys::DELETE => KeyCode::Delete, + keys::INSERT => KeyCode::Insert, + keys::NULL => KeyCode::Null, + keys::ESC => KeyCode::Esc, + keys::SPACE => KeyCode::Char(' '), + keys::LESS_THAN => KeyCode::Char('<'), + keys::GREATER_THAN => KeyCode::Char('>'), + keys::PLUS => KeyCode::Char('+'), + keys::MINUS => KeyCode::Char('-'), + keys::SEMICOLON => KeyCode::Char(';'), + keys::PERCENT => KeyCode::Char('%'), single if single.len() == 1 => KeyCode::Char(single.chars().next().unwrap()), function if function.len() > 1 && function.starts_with('F') => { let function: String = function.chars().skip(1).collect(); diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index caed2952..9bcc0b7d 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -5,6 +5,7 @@ pub mod clipboard; pub mod document; pub mod editor; pub mod graphics; +pub mod info; pub mod input; pub mod keyboard; pub mod register_selection; |