aboutsummaryrefslogtreecommitdiff
path: root/helix-view/src
diff options
context:
space:
mode:
Diffstat (limited to 'helix-view/src')
-rw-r--r--helix-view/src/document.rs46
-rw-r--r--helix-view/src/editor.rs60
-rw-r--r--helix-view/src/handlers/dap.rs2
-rw-r--r--helix-view/src/info.rs33
-rw-r--r--helix-view/src/input.rs1
-rw-r--r--helix-view/src/lib.rs19
-rw-r--r--helix-view/src/theme.rs199
-rw-r--r--helix-view/src/view.rs28
8 files changed, 298 insertions, 90 deletions
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index 2ef99c6a..0daa983f 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -5,6 +5,7 @@ use helix_core::auto_pairs::AutoPairs;
use helix_core::Range;
use serde::de::{self, Deserialize, Deserializer};
use serde::Serialize;
+use std::borrow::Cow;
use std::cell::Cell;
use std::collections::HashMap;
use std::fmt::Display;
@@ -23,7 +24,7 @@ use helix_core::{
DEFAULT_LINE_ENDING,
};
-use crate::{DocumentId, Editor, ViewId};
+use crate::{apply_transaction, DocumentId, Editor, View, ViewId};
/// 8kB of buffer space for encoding and decoding `Rope`s.
const BUF_SIZE: usize = 8192;
@@ -600,7 +601,7 @@ impl Document {
}
/// Reload the document from its path.
- pub fn reload(&mut self, view_id: ViewId) -> Result<(), Error> {
+ pub fn reload(&mut self, view: &mut View) -> Result<(), Error> {
let encoding = &self.encoding;
let path = self.path().filter(|path| path.exists());
@@ -616,8 +617,8 @@ impl Document {
// This is not considered a modification of the contents of the file regardless
// of the encoding.
let transaction = helix_core::diff::compare_ropes(self.text(), &rope);
- self.apply(&transaction, view_id);
- self.append_changes_to_history(view_id);
+ apply_transaction(&transaction, self, view);
+ self.append_changes_to_history(view.id);
self.reset_modified();
self.detect_indent_and_line_ending();
@@ -809,6 +810,9 @@ impl Document {
}
/// Apply a [`Transaction`] to the [`Document`] to change its text.
+ /// Instead of calling this function directly, use [crate::apply_transaction]
+ /// to ensure that the transaction is applied to the appropriate [`View`] as
+ /// well.
pub fn apply(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
// store the state just before any changes are made. This allows us to undo to the
// state just before a transaction was applied.
@@ -830,11 +834,11 @@ impl Document {
success
}
- fn undo_redo_impl(&mut self, view_id: ViewId, undo: bool) -> bool {
+ fn undo_redo_impl(&mut self, view: &mut View, undo: bool) -> bool {
let mut history = self.history.take();
let txn = if undo { history.undo() } else { history.redo() };
let success = if let Some(txn) = txn {
- self.apply_impl(txn, view_id)
+ self.apply_impl(txn, view.id) && view.apply(txn, self)
} else {
false
};
@@ -848,26 +852,26 @@ impl Document {
}
/// Undo the last modification to the [`Document`]. Returns whether the undo was successful.
- pub fn undo(&mut self, view_id: ViewId) -> bool {
- self.undo_redo_impl(view_id, true)
+ pub fn undo(&mut self, view: &mut View) -> bool {
+ self.undo_redo_impl(view, true)
}
/// Redo the last modification to the [`Document`]. Returns whether the redo was successful.
- pub fn redo(&mut self, view_id: ViewId) -> bool {
- self.undo_redo_impl(view_id, false)
+ pub fn redo(&mut self, view: &mut View) -> bool {
+ self.undo_redo_impl(view, false)
}
pub fn savepoint(&mut self) {
self.savepoint = Some(Transaction::new(self.text()));
}
- pub fn restore(&mut self, view_id: ViewId) {
+ pub fn restore(&mut self, view: &mut View) {
if let Some(revert) = self.savepoint.take() {
- self.apply(&revert, view_id);
+ apply_transaction(&revert, self, view);
}
}
- fn earlier_later_impl(&mut self, view_id: ViewId, uk: UndoKind, earlier: bool) -> bool {
+ fn earlier_later_impl(&mut self, view: &mut View, uk: UndoKind, earlier: bool) -> bool {
let txns = if earlier {
self.history.get_mut().earlier(uk)
} else {
@@ -875,7 +879,7 @@ impl Document {
};
let mut success = false;
for txn in txns {
- if self.apply_impl(&txn, view_id) {
+ if self.apply_impl(&txn, view.id) && view.apply(&txn, self) {
success = true;
}
}
@@ -887,13 +891,13 @@ impl Document {
}
/// Undo modifications to the [`Document`] according to `uk`.
- pub fn earlier(&mut self, view_id: ViewId, uk: UndoKind) -> bool {
- self.earlier_later_impl(view_id, uk, true)
+ pub fn earlier(&mut self, view: &mut View, uk: UndoKind) -> bool {
+ self.earlier_later_impl(view, uk, true)
}
/// Redo modifications to the [`Document`] according to `uk`.
- pub fn later(&mut self, view_id: ViewId, uk: UndoKind) -> bool {
- self.earlier_later_impl(view_id, uk, false)
+ pub fn later(&mut self, view: &mut View, uk: UndoKind) -> bool {
+ self.earlier_later_impl(view, uk, false)
}
/// Commit pending changes to history
@@ -1038,6 +1042,12 @@ impl Document {
.map(helix_core::path::get_relative_path)
}
+ pub fn display_name(&self) -> Cow<'static, str> {
+ self.relative_path()
+ .map(|path| path.to_string_lossy().to_string().into())
+ .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into())
+ }
+
// transact(Fn) ?
// -- LSP methods
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 5eff9983..e9a3c639 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -1,6 +1,6 @@
use crate::{
clipboard::{get_clipboard_provider, ClipboardProvider},
- document::{Mode, SCRATCH_BUFFER_NAME},
+ document::Mode,
graphics::{CursorKind, Rect},
info::Info,
input::KeyEvent,
@@ -28,7 +28,7 @@ use tokio::{
time::{sleep, Duration, Instant, Sleep},
};
-use anyhow::{bail, Error};
+use anyhow::Error;
pub use helix_core::diagnostic::Severity;
pub use helix_core::register::Registers;
@@ -124,6 +124,8 @@ pub struct Config {
pub line_number: LineNumber,
/// Highlight the lines cursors are currently on. Defaults to false.
pub cursorline: bool,
+ /// Highlight the columns cursors are currently on. Defaults to false.
+ pub cursorcolumn: bool,
/// Gutters. Default ["diagnostics", "line-numbers"]
pub gutters: Vec<GutterType>,
/// Middle click paste support. Defaults to true.
@@ -260,6 +262,7 @@ pub struct StatusLineConfig {
pub center: Vec<StatusLineElement>,
pub right: Vec<StatusLineElement>,
pub separator: String,
+ pub mode: ModeConfig,
}
impl Default for StatusLineConfig {
@@ -271,6 +274,25 @@ impl Default for StatusLineConfig {
center: vec![],
right: vec![E::Diagnostics, E::Selections, E::Position, E::FileEncoding],
separator: String::from("│"),
+ mode: ModeConfig::default(),
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
+pub struct ModeConfig {
+ pub normal: String,
+ pub insert: String,
+ pub select: String,
+}
+
+impl Default for ModeConfig {
+ fn default() -> Self {
+ Self {
+ normal: String::from("NOR"),
+ insert: String::from("INS"),
+ select: String::from("SEL"),
}
}
}
@@ -311,6 +333,9 @@ pub enum StatusLineElement {
/// The cursor position as a percent of the total file
PositionPercentage,
+ /// The total line numbers of the current file
+ TotalLineNumbers,
+
/// A single space
Spacer,
}
@@ -529,15 +554,17 @@ impl Default for WhitespaceCharacters {
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(default)]
+#[serde(default, rename_all = "kebab-case")]
pub struct IndentGuidesConfig {
pub render: bool,
pub character: char,
+ pub skip_levels: u8,
}
impl Default for IndentGuidesConfig {
fn default() -> Self {
Self {
+ skip_levels: 0,
render: false,
character: '│',
}
@@ -557,6 +584,7 @@ impl Default for Config {
},
line_number: LineNumber::Absolute,
cursorline: false,
+ cursorcolumn: false,
gutters: vec![GutterType::Diagnostics, GutterType::LineNumbers],
middle_click_paste: true,
auto_pairs: AutoPairConfig::default(),
@@ -643,7 +671,7 @@ pub struct Editor {
/// The currently applied editor theme. While previewing a theme, the previewed theme
/// is set here.
pub theme: Theme,
-
+ pub last_line_number: Option<usize>,
pub status_msg: Option<(Cow<'static, str>, Severity)>,
pub autoinfo: Option<Info>,
@@ -652,7 +680,6 @@ pub struct Editor {
pub idle_timer: Pin<Box<Sleep>>,
pub last_motion: Option<Motion>,
- pub pseudo_pending: Option<String>,
pub last_completion: Option<CompleteAction>,
@@ -686,6 +713,14 @@ pub enum Action {
VerticalSplit,
}
+/// Error thrown on failed document closed
+pub enum CloseError {
+ /// Document doesn't exist
+ DoesNotExist,
+ /// Buffer is modified
+ BufferModified(String),
+}
+
impl Editor {
pub fn new(
mut area: Rect,
@@ -717,6 +752,7 @@ impl Editor {
syn_loader,
theme_loader,
last_theme: None,
+ last_line_number: None,
registers: Registers::default(),
clipboard_provider: get_clipboard_provider(),
status_msg: None,
@@ -724,7 +760,6 @@ impl Editor {
idle_timer: Box::pin(sleep(conf.idle_timeout)),
last_motion: None,
last_completion: None,
- pseudo_pending: None,
config,
auto_pairs,
exit_code: 0,
@@ -844,7 +879,7 @@ impl Editor {
// try to find a language server based on the language name
let language_server = doc.language.as_ref().and_then(|language| {
- ls.get(language)
+ ls.get(language, doc.path())
.map_err(|e| {
log::error!(
"Failed to initialize the LSP for `{}` {{ {} }}",
@@ -1044,19 +1079,14 @@ impl Editor {
self._refresh();
}
- pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> anyhow::Result<()> {
+ pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> Result<(), CloseError> {
let doc = match self.documents.get(&doc_id) {
Some(doc) => doc,
- None => bail!("document does not exist"),
+ None => return Err(CloseError::DoesNotExist),
};
if !force && doc.is_modified() {
- bail!(
- "buffer {:?} is modified",
- doc.relative_path()
- .map(|path| path.to_string_lossy().to_string())
- .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into())
- );
+ return Err(CloseError::BufferModified(doc.display_name().into_owned()));
}
if let Some(language_server) = doc.language_server() {
diff --git a/helix-view/src/handlers/dap.rs b/helix-view/src/handlers/dap.rs
index e39584c3..2e86871b 100644
--- a/helix-view/src/handlers/dap.rs
+++ b/helix-view/src/handlers/dap.rs
@@ -262,7 +262,7 @@ impl Editor {
log::info!("{}", output);
self.set_status(format!("{} {}", prefix, output));
}
- Event::Initialized => {
+ Event::Initialized(_) => {
// send existing breakpoints
for (path, breakpoints) in &mut self.breakpoints {
// TODO: call futures in parallel, await all
diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs
index 5ad6a60c..3080cf8e 100644
--- a/helix-view/src/info.rs
+++ b/helix-view/src/info.rs
@@ -16,7 +16,11 @@ pub struct Info {
}
impl Info {
- pub fn new(title: &str, body: Vec<(String, String)>) -> Self {
+ pub fn new<T, U>(title: &str, body: &[(T, U)]) -> Self
+ where
+ T: AsRef<str>,
+ U: AsRef<str>,
+ {
if body.is_empty() {
return Self {
title: title.to_string(),
@@ -26,11 +30,21 @@ impl Info {
};
}
- let item_width = body.iter().map(|(item, _)| item.width()).max().unwrap();
+ let item_width = body
+ .iter()
+ .map(|(item, _)| item.as_ref().width())
+ .max()
+ .unwrap();
let mut text = String::new();
- for (item, desc) in &body {
- let _ = writeln!(text, "{:width$} {}", item, desc, width = item_width);
+ for (item, desc) in body {
+ let _ = writeln!(
+ text,
+ "{:width$} {}",
+ item.as_ref(),
+ desc.as_ref(),
+ width = item_width
+ );
}
Self {
@@ -42,19 +56,19 @@ impl Info {
}
pub fn from_keymap(title: &str, body: Vec<(&str, BTreeSet<KeyEvent>)>) -> Self {
- let body = body
+ let body: Vec<_> = body
.into_iter()
.map(|(desc, events)| {
let events = events.iter().map(ToString::to_string).collect::<Vec<_>>();
- (events.join(", "), desc.to_string())
+ (events.join(", "), desc)
})
.collect();
- Self::new(title, body)
+ Self::new(title, &body)
}
pub fn from_registers(registers: &Registers) -> Self {
- let body = registers
+ let body: Vec<_> = registers
.inner()
.iter()
.map(|(ch, reg)| {
@@ -62,13 +76,12 @@ impl Info {
.read()
.get(0)
.and_then(|s| s.lines().next())
- .map(String::from)
.unwrap_or_default();
(ch.to_string(), content)
})
.collect();
- let mut infobox = Self::new("Registers", body);
+ let mut infobox = Self::new("Registers", &body);
infobox.width = 30; // copied content could be very long
infobox
}
diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs
index 083a1e08..30fa72c4 100644
--- a/helix-view/src/input.rs
+++ b/helix-view/src/input.rs
@@ -14,6 +14,7 @@ pub enum Event {
Mouse(MouseEvent),
Paste(String),
Resize(u16, u16),
+ IdleTimeout,
}
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs
index 788304bc..276be441 100644
--- a/helix-view/src/lib.rs
+++ b/helix-view/src/lib.rs
@@ -53,17 +53,30 @@ pub fn align_view(doc: &Document, view: &mut View, align: Align) {
.cursor(doc.text().slice(..));
let line = doc.text().char_to_line(pos);
- let height = view.inner_area().height as usize;
+ let last_line_height = view.inner_area().height.saturating_sub(1) as usize;
let relative = match align {
- Align::Center => height / 2,
+ Align::Center => last_line_height / 2,
Align::Top => 0,
- Align::Bottom => height,
+ Align::Bottom => last_line_height,
};
view.offset.row = line.saturating_sub(relative);
}
+/// Applies a [`helix_core::Transaction`] to the given [`Document`]
+/// and [`View`].
+pub fn apply_transaction(
+ transaction: &helix_core::Transaction,
+ doc: &mut Document,
+ view: &mut View,
+) -> bool {
+ // This is a short function but it's easy to call `Document::apply`
+ // without calling `View::apply` or in the wrong order. The transaction
+ // must be applied to the document before the view.
+ doc.apply(transaction, view.id) && view.apply(transaction, doc)
+}
+
pub use document::Document;
pub use editor::Editor;
pub use theme::Theme;
diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs
index aaef28b2..302844b7 100644
--- a/helix-view/src/theme.rs
+++ b/helix-view/src/theme.rs
@@ -3,20 +3,29 @@ use std::{
path::{Path, PathBuf},
};
-use anyhow::Context;
+use anyhow::{anyhow, Context, Result};
use helix_core::hashmap;
+use helix_loader::merge_toml_values;
use log::warn;
use once_cell::sync::Lazy;
use serde::{Deserialize, Deserializer};
-use toml::Value;
+use toml::{map::Map, Value};
use crate::graphics::UnderlineStyle;
pub use crate::graphics::{Color, Modifier, Style};
pub static DEFAULT_THEME: Lazy<Theme> = Lazy::new(|| {
+ // let raw_theme: Value = toml::from_slice(include_bytes!("../../theme.toml"))
+ // .expect("Failed to parse default theme");
+ // Theme::from(raw_theme)
+
toml::from_slice(include_bytes!("../../theme.toml")).expect("Failed to parse default theme")
});
pub static BASE16_DEFAULT_THEME: Lazy<Theme> = Lazy::new(|| {
+ // let raw_theme: Value = toml::from_slice(include_bytes!("../../base16_theme.toml"))
+ // .expect("Failed to parse base 16 default theme");
+ // Theme::from(raw_theme)
+
toml::from_slice(include_bytes!("../../base16_theme.toml"))
.expect("Failed to parse base 16 default theme")
});
@@ -36,24 +45,51 @@ impl Loader {
}
/// Loads a theme first looking in the `user_dir` then in `default_dir`
- pub fn load(&self, name: &str) -> Result<Theme, anyhow::Error> {
+ pub fn load(&self, name: &str) -> Result<Theme> {
if name == "default" {
return Ok(self.default());
}
if name == "base16_default" {
return Ok(self.base16_default());
}
- let filename = format!("{}.toml", name);
- let user_path = self.user_dir.join(&filename);
- let path = if user_path.exists() {
- user_path
+ self.load_theme(name, name, false).map(Theme::from)
+ }
+
+ // load the theme and its parent recursively and merge them
+ // `base_theme_name` is the theme from the config.toml,
+ // used to prevent some circular loading scenarios
+ fn load_theme(
+ &self,
+ name: &str,
+ base_them_name: &str,
+ only_default_dir: bool,
+ ) -> Result<Value> {
+ let path = self.path(name, only_default_dir);
+ let theme_toml = self.load_toml(path)?;
+
+ let inherits = theme_toml.get("inherits");
+
+ let theme_toml = if let Some(parent_theme_name) = inherits {
+ let parent_theme_name = parent_theme_name.as_str().ok_or_else(|| {
+ anyhow!(
+ "Theme: expected 'inherits' to be a string: {}",
+ parent_theme_name
+ )
+ })?;
+
+ let parent_theme_toml = self.load_theme(
+ parent_theme_name,
+ base_them_name,
+ base_them_name == parent_theme_name,
+ )?;
+
+ self.merge_themes(parent_theme_toml, theme_toml)
} else {
- self.default_dir.join(filename)
+ theme_toml
};
- let data = std::fs::read(&path)?;
- toml::from_slice(data.as_slice()).context("Failed to deserialize theme")
+ Ok(theme_toml)
}
pub fn read_names(path: &Path) -> Vec<String> {
@@ -71,6 +107,53 @@ impl Loader {
.unwrap_or_default()
}
+ // merge one theme into the parent theme
+ fn merge_themes(&self, parent_theme_toml: Value, theme_toml: Value) -> Value {
+ let parent_palette = parent_theme_toml.get("palette");
+ let palette = theme_toml.get("palette");
+
+ // handle the table seperately since it needs a `merge_depth` of 2
+ // this would conflict with the rest of the theme merge strategy
+ let palette_values = match (parent_palette, palette) {
+ (Some(parent_palette), Some(palette)) => {
+ merge_toml_values(parent_palette.clone(), palette.clone(), 2)
+ }
+ (Some(parent_palette), None) => parent_palette.clone(),
+ (None, Some(palette)) => palette.clone(),
+ (None, None) => Map::new().into(),
+ };
+
+ // add the palette correctly as nested table
+ let mut palette = Map::new();
+ palette.insert(String::from("palette"), palette_values);
+
+ // merge the theme into the parent theme
+ let theme = merge_toml_values(parent_theme_toml, theme_toml, 1);
+ // merge the before specially handled palette into the theme
+ merge_toml_values(theme, palette.into(), 1)
+ }
+
+ // Loads the theme data as `toml::Value` first from the user_dir then in default_dir
+ fn load_toml(&self, path: PathBuf) -> Result<Value> {
+ let data = std::fs::read(&path)?;
+
+ toml::from_slice(data.as_slice()).context("Failed to deserialize theme")
+ }
+
+ // Returns the path to the theme with the name
+ // With `only_default_dir` as false the path will first search for the user path
+ // disabled it ignores the user path and returns only the default path
+ fn path(&self, name: &str, only_default_dir: bool) -> PathBuf {
+ let filename = format!("{}.toml", name);
+
+ let user_path = self.user_dir.join(&filename);
+ if !only_default_dir && user_path.exists() {
+ user_path
+ } else {
+ self.default_dir.join(filename)
+ }
+ }
+
/// Lists all theme names available in default and user directory
pub fn names(&self) -> Vec<String> {
let mut names = Self::read_names(&self.user_dir);
@@ -106,52 +189,77 @@ pub struct Theme {
highlights: Vec<Style>,
}
+impl From<Value> for Theme {
+ fn from(value: Value) -> Self {
+ let values: Result<HashMap<String, Value>> =
+ toml::from_str(&value.to_string()).context("Failed to load theme");
+
+ let (styles, scopes, highlights) = build_theme_values(values);
+
+ Self {
+ styles,
+ scopes,
+ highlights,
+ }
+ }
+}
+
impl<'de> Deserialize<'de> for Theme {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
- let mut styles = HashMap::new();
- let mut scopes = Vec::new();
- let mut highlights = Vec::new();
-
- if let Ok(mut colors) = HashMap::<String, Value>::deserialize(deserializer) {
- // TODO: alert user of parsing failures in editor
- let palette = colors
- .remove("palette")
- .map(|value| {
- ThemePalette::try_from(value).unwrap_or_else(|err| {
- warn!("{}", err);
- ThemePalette::default()
- })
- })
- .unwrap_or_default();
-
- styles.reserve(colors.len());
- scopes.reserve(colors.len());
- highlights.reserve(colors.len());
+ let values = HashMap::<String, Value>::deserialize(deserializer)?;
- for (name, style_value) in colors {
- let mut style = Style::default();
- if let Err(err) = palette.parse_style(&mut style, style_value) {
- warn!("{}", err);
- }
-
- // these are used both as UI and as highlights
- styles.insert(name.clone(), style);
- scopes.push(name);
- highlights.push(style);
- }
- }
+ let (styles, scopes, highlights) = build_theme_values(Ok(values));
Ok(Self {
- scopes,
styles,
+ scopes,
highlights,
})
}
}
+fn build_theme_values(
+ values: Result<HashMap<String, Value>>,
+) -> (HashMap<String, Style>, Vec<String>, Vec<Style>) {
+ let mut styles = HashMap::new();
+ let mut scopes = Vec::new();
+ let mut highlights = Vec::new();
+
+ if let Ok(mut colors) = values {
+ // TODO: alert user of parsing failures in editor
+ let palette = colors
+ .remove("palette")
+ .map(|value| {
+ ThemePalette::try_from(value).unwrap_or_else(|err| {
+ warn!("{}", err);
+ ThemePalette::default()
+ })
+ })
+ .unwrap_or_default();
+ // remove inherits from value to prevent errors
+ let _ = colors.remove("inherits");
+ styles.reserve(colors.len());
+ scopes.reserve(colors.len());
+ highlights.reserve(colors.len());
+ for (name, style_value) in colors {
+ let mut style = Style::default();
+ if let Err(err) = palette.parse_style(&mut style, style_value) {
+ warn!("{}", err);
+ }
+
+ // these are used both as UI and as highlights
+ styles.insert(name.clone(), style);
+ scopes.push(name);
+ highlights.push(style);
+ }
+ }
+
+ (styles, scopes, highlights)
+}
+
impl Theme {
#[inline]
pub fn highlight(&self, index: usize) -> Style {
@@ -170,6 +278,13 @@ impl Theme {
.find_map(|s| self.styles.get(s).copied())
}
+ /// Get the style of a scope, without falling back to dot separated broader
+ /// scopes. For example if `ui.text.focus` is not defined in the theme, it
+ /// will return `None`, even if `ui.text` is.
+ pub fn try_get_exact(&self, scope: &str) -> Option<Style> {
+ self.styles.get(scope).copied()
+ }
+
#[inline]
pub fn scopes(&self) -> &[String] {
&self.scopes
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs
index 3df533df..62984b88 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -3,7 +3,9 @@ use crate::{
gutter::{self, Gutter},
Document, DocumentId, ViewId,
};
-use helix_core::{pos_at_visual_coords, visual_coords_at_pos, Position, RopeSlice, Selection};
+use helix_core::{
+ pos_at_visual_coords, visual_coords_at_pos, Position, RopeSlice, Selection, Transaction,
+};
use std::fmt;
@@ -62,6 +64,22 @@ impl JumpList {
pub fn get(&self) -> &[Jump] {
&self.jumps
}
+
+ /// Applies a [`Transaction`] of changes to the jumplist.
+ /// This is necessary to ensure that changes to documents do not leave jump-list
+ /// selections pointing to parts of the text which no longer exist.
+ fn apply(&mut self, transaction: &Transaction, doc: &Document) {
+ let text = doc.text().slice(..);
+
+ for (doc_id, selection) in &mut self.jumps {
+ if doc.id() == *doc_id {
+ *selection = selection
+ .clone()
+ .map(transaction.changes())
+ .ensure_invariants(text);
+ }
+ }
+ }
}
#[derive(Clone)]
@@ -334,6 +352,14 @@ impl View {
// (None, None) => return,
// }
// }
+
+ /// Applies a [`Transaction`] to the view.
+ /// Instead of calling this function directly, use [crate::apply_transaction]
+ /// which applies a transaction to the [`Document`] and view together.
+ pub fn apply(&mut self, transaction: &Transaction, doc: &Document) -> bool {
+ self.jumps.apply(transaction, doc);
+ true
+ }
}
#[cfg(test)]