From 1cefa2874c79be1f2b72f5fa5aafa123f213b73d Mon Sep 17 00:00:00 2001 From: Ingrid Date: Fri, 9 Feb 2024 19:33:48 +0100 Subject: [PATCH] load file history It was necessary make pos in file args an option to prevent it from overwriting the file positions loaded from persistence. Alignment is not quite right... I think we need to persist selections instead of view positions, or disable center aligning --- helix-loader/src/session.rs | 29 ++++++++++++++++++++++++----- helix-term/src/application.rs | 29 ++++++++++++++++++++++++----- helix-term/src/args.rs | 19 +++++++++++-------- helix-term/src/commands/typed.rs | 6 ++++-- helix-view/src/editor.rs | 19 ++++++++++++++++++- 5 files changed, 81 insertions(+), 21 deletions(-) diff --git a/helix-loader/src/session.rs b/helix-loader/src/session.rs index 33b3814b9..8faecd843 100644 --- a/helix-loader/src/session.rs +++ b/helix-loader/src/session.rs @@ -1,5 +1,5 @@ use crate::{command_histfile, file_histfile, search_histfile}; -use bincode::serialize_into; +use bincode::{deserialize_from, serialize_into}; use serde::{Deserialize, Serialize}; use std::{ fs::{File, OpenOptions}, @@ -12,10 +12,10 @@ use std::{ // since this crate is a dependency of helix-view #[derive(Debug, Serialize, Deserialize)] pub struct FileHistoryEntry { - path: PathBuf, - anchor: usize, - vertical_offset: usize, - horizontal_offset: usize, + pub path: PathBuf, + pub anchor: usize, + pub vertical_offset: usize, + pub horizontal_offset: usize, } impl FileHistoryEntry { @@ -46,6 +46,25 @@ pub fn push_file_history(entry: FileHistoryEntry) { serialize_into(file, &entry).unwrap(); } +pub fn read_file_history() -> Vec { + match File::open(file_histfile()) { + Ok(file) => { + let mut read = BufReader::new(file); + let mut entries = Vec::new(); + // TODO: more sophisticated error handling + while let Ok(entry) = deserialize_from(&mut read) { + entries.push(entry); + } + entries + } + Err(e) => match e.kind() { + io::ErrorKind::NotFound => Vec::new(), + // TODO: do something about this panic + _ => panic!(), + }, + } +} + pub fn push_history(register: char, line: &str) { let filepath = match register { ':' => command_histfile(), diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 5b03a18eb..e7de35bb6 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -16,6 +16,7 @@ use helix_view::{ graphics::Rect, theme, tree::Layout, + view::ViewPosition, Align, Editor, }; use serde_json::json; @@ -34,7 +35,12 @@ use crate::{ use log::{debug, error, info, warn}; #[cfg(not(feature = "integration"))] use std::io::stdout; -use std::{collections::btree_map::Entry, io::stdin, path::Path, sync::Arc}; +use std::{ + collections::{btree_map::Entry, HashMap}, + io::stdin, + path::Path, + sync::Arc, +}; #[cfg(not(windows))] use anyhow::Context; @@ -147,6 +153,16 @@ impl Application { &config.editor })), handlers, + HashMap::from_iter(session::read_file_history().iter().map(|entry| { + ( + entry.path.clone(), + ViewPosition { + anchor: entry.anchor, + horizontal_offset: entry.horizontal_offset, + vertical_offset: entry.vertical_offset, + }, + ) + })), ); // TODO: do most of this in the background? @@ -217,10 +233,13 @@ impl Application { // NOTE: this isn't necessarily true anymore. If // `--vsplit` or `--hsplit` are used, the file which is // opened last is focused on. - let view_id = editor.tree.focus; - let doc = doc_mut!(editor, &doc_id); - let pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true)); - doc.set_selection(view_id, pos); + if let Some(pos) = pos { + let view_id = editor.tree.focus; + let doc = doc_mut!(editor, &doc_id); + let pos = + Selection::point(pos_at_coords(doc.text().slice(..), pos, true)); + doc.set_selection(view_id, pos); + } } } diff --git a/helix-term/src/args.rs b/helix-term/src/args.rs index 0b1c9cde0..9cc3e8559 100644 --- a/helix-term/src/args.rs +++ b/helix-term/src/args.rs @@ -16,7 +16,7 @@ pub struct Args { pub verbosity: u64, pub log_file: Option, pub config_file: Option, - pub files: Vec<(PathBuf, Position)>, + pub files: Vec<(PathBuf, Option)>, pub working_directory: Option, } @@ -106,7 +106,10 @@ impl Args { if let Some(file) = args.files.first_mut() { if line_number != 0 { - file.1.row = line_number; + file.1 = match file.1 { + Some(pos) => Some(Position::new(line_number, pos.col)), + None => Some(Position::new(line_number, 0)), + } } } @@ -115,8 +118,8 @@ impl Args { } /// Parse arg into [`PathBuf`] and position. -pub(crate) fn parse_file(s: &str) -> (PathBuf, Position) { - let def = || (PathBuf::from(s), Position::default()); +pub(crate) fn parse_file(s: &str) -> (PathBuf, Option) { + let def = || (PathBuf::from(s), None); if Path::new(s).exists() { return def(); } @@ -128,22 +131,22 @@ pub(crate) fn parse_file(s: &str) -> (PathBuf, Position) { /// Split file.rs:10:2 into [`PathBuf`], row and col. /// /// Does not validate if file.rs is a file or directory. -fn split_path_row_col(s: &str) -> Option<(PathBuf, Position)> { +fn split_path_row_col(s: &str) -> Option<(PathBuf, Option)> { let mut s = s.rsplitn(3, ':'); let col: usize = s.next()?.parse().ok()?; let row: usize = s.next()?.parse().ok()?; let path = s.next()?.into(); let pos = Position::new(row.saturating_sub(1), col.saturating_sub(1)); - Some((path, pos)) + Some((path, Some(pos))) } /// Split file.rs:10 into [`PathBuf`] and row. /// /// Does not validate if file.rs is a file or directory. -fn split_path_row(s: &str) -> Option<(PathBuf, Position)> { +fn split_path_row(s: &str) -> Option<(PathBuf, Option)> { let (path, row) = s.rsplit_once(':')?; let row: usize = row.parse().ok()?; let path = path.into(); let pos = Position::new(row.saturating_sub(1), 0); - Some((path, pos)) + Some((path, Some(pos))) } diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 7ad0369fc..495e73459 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -129,8 +129,10 @@ fn open(cx: &mut compositor::Context, args: &[Cow], event: PromptEvent) -> // Otherwise, just open the file let _ = cx.editor.open(&path, Action::Replace)?; let (view, doc) = current!(cx.editor); - let pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true)); - doc.set_selection(view.id, pos); + if let Some(pos) = pos { + let pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true)); + doc.set_selection(view.id, pos); + } // does not affect opening a buffer without pos align_view(doc, view, Align::Center); } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 3c8e8edaa..f75019121 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1034,6 +1034,8 @@ pub struct Editor { pub debugger_events: SelectAll>, pub breakpoints: HashMap>, + pub old_file_locs: HashMap, + pub syn_loader: Arc>, pub theme_loader: Arc, /// last_theme is used for theme previews. We store the current theme here, @@ -1150,6 +1152,7 @@ impl Editor { syn_loader: Arc>, config: Arc>, handlers: Handlers, + old_file_locs: HashMap, ) -> Self { let language_servers = helix_lsp::Registry::new(syn_loader.clone()); let conf = config.load(); @@ -1177,6 +1180,7 @@ impl Editor { debugger: None, debugger_events: SelectAll::new(), breakpoints: HashMap::new(), + old_file_locs, syn_loader, theme_loader, last_theme: None, @@ -1636,6 +1640,18 @@ impl Editor { ); // initialize selection for view let doc = doc_mut!(self, &id); + + let view = self.tree.get_mut(view_id); + view.offset = self + .old_file_locs + .get(doc.path().unwrap()) + .map(|x| x.to_owned()) + .unwrap_or_default(); + doc.set_selection( + view_id, + Selection::single(view.offset.anchor, view.offset.anchor), + ); + doc.ensure_view_init(view_id); doc.mark_as_focused(); } @@ -1731,11 +1747,12 @@ impl Editor { let doc = self.document(view.doc).unwrap(); if let Some(path) = doc.path() { push_file_history(FileHistoryEntry::new( - path.to_owned(), + path.clone(), view.offset.anchor, view.offset.vertical_offset, view.offset.horizontal_offset, )); + self.old_file_locs.insert(path.to_owned(), view.offset); }; // Remove selections for the closed view on all documents.