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
pull/9143/head
Ingrid 10 months ago
parent ed7fb431aa
commit 1cefa2874c

@ -1,5 +1,5 @@
use crate::{command_histfile, file_histfile, search_histfile}; use crate::{command_histfile, file_histfile, search_histfile};
use bincode::serialize_into; use bincode::{deserialize_from, serialize_into};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
fs::{File, OpenOptions}, fs::{File, OpenOptions},
@ -12,10 +12,10 @@ use std::{
// since this crate is a dependency of helix-view // since this crate is a dependency of helix-view
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct FileHistoryEntry { pub struct FileHistoryEntry {
path: PathBuf, pub path: PathBuf,
anchor: usize, pub anchor: usize,
vertical_offset: usize, pub vertical_offset: usize,
horizontal_offset: usize, pub horizontal_offset: usize,
} }
impl FileHistoryEntry { impl FileHistoryEntry {
@ -46,6 +46,25 @@ pub fn push_file_history(entry: FileHistoryEntry) {
serialize_into(file, &entry).unwrap(); serialize_into(file, &entry).unwrap();
} }
pub fn read_file_history() -> Vec<FileHistoryEntry> {
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) { pub fn push_history(register: char, line: &str) {
let filepath = match register { let filepath = match register {
':' => command_histfile(), ':' => command_histfile(),

@ -16,6 +16,7 @@ use helix_view::{
graphics::Rect, graphics::Rect,
theme, theme,
tree::Layout, tree::Layout,
view::ViewPosition,
Align, Editor, Align, Editor,
}; };
use serde_json::json; use serde_json::json;
@ -34,7 +35,12 @@ use crate::{
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
#[cfg(not(feature = "integration"))] #[cfg(not(feature = "integration"))]
use std::io::stdout; 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))] #[cfg(not(windows))]
use anyhow::Context; use anyhow::Context;
@ -147,6 +153,16 @@ impl Application {
&config.editor &config.editor
})), })),
handlers, 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? // TODO: do most of this in the background?
@ -217,12 +233,15 @@ impl Application {
// NOTE: this isn't necessarily true anymore. If // NOTE: this isn't necessarily true anymore. If
// `--vsplit` or `--hsplit` are used, the file which is // `--vsplit` or `--hsplit` are used, the file which is
// opened last is focused on. // opened last is focused on.
if let Some(pos) = pos {
let view_id = editor.tree.focus; let view_id = editor.tree.focus;
let doc = doc_mut!(editor, &doc_id); let doc = doc_mut!(editor, &doc_id);
let pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true)); let pos =
Selection::point(pos_at_coords(doc.text().slice(..), pos, true));
doc.set_selection(view_id, pos); doc.set_selection(view_id, pos);
} }
} }
}
// if all files were invalid, replace with empty buffer // if all files were invalid, replace with empty buffer
if nr_of_files == 0 { if nr_of_files == 0 {

@ -16,7 +16,7 @@ pub struct Args {
pub verbosity: u64, pub verbosity: u64,
pub log_file: Option<PathBuf>, pub log_file: Option<PathBuf>,
pub config_file: Option<PathBuf>, pub config_file: Option<PathBuf>,
pub files: Vec<(PathBuf, Position)>, pub files: Vec<(PathBuf, Option<Position>)>,
pub working_directory: Option<PathBuf>, pub working_directory: Option<PathBuf>,
} }
@ -106,7 +106,10 @@ impl Args {
if let Some(file) = args.files.first_mut() { if let Some(file) = args.files.first_mut() {
if line_number != 0 { 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. /// Parse arg into [`PathBuf`] and position.
pub(crate) fn parse_file(s: &str) -> (PathBuf, Position) { pub(crate) fn parse_file(s: &str) -> (PathBuf, Option<Position>) {
let def = || (PathBuf::from(s), Position::default()); let def = || (PathBuf::from(s), None);
if Path::new(s).exists() { if Path::new(s).exists() {
return def(); 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. /// Split file.rs:10:2 into [`PathBuf`], row and col.
/// ///
/// Does not validate if file.rs is a file or directory. /// 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<Position>)> {
let mut s = s.rsplitn(3, ':'); let mut s = s.rsplitn(3, ':');
let col: usize = s.next()?.parse().ok()?; let col: usize = s.next()?.parse().ok()?;
let row: usize = s.next()?.parse().ok()?; let row: usize = s.next()?.parse().ok()?;
let path = s.next()?.into(); let path = s.next()?.into();
let pos = Position::new(row.saturating_sub(1), col.saturating_sub(1)); 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. /// Split file.rs:10 into [`PathBuf`] and row.
/// ///
/// Does not validate if file.rs is a file or directory. /// 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<Position>)> {
let (path, row) = s.rsplit_once(':')?; let (path, row) = s.rsplit_once(':')?;
let row: usize = row.parse().ok()?; let row: usize = row.parse().ok()?;
let path = path.into(); let path = path.into();
let pos = Position::new(row.saturating_sub(1), 0); let pos = Position::new(row.saturating_sub(1), 0);
Some((path, pos)) Some((path, Some(pos)))
} }

@ -129,8 +129,10 @@ fn open(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) ->
// Otherwise, just open the file // Otherwise, just open the file
let _ = cx.editor.open(&path, Action::Replace)?; let _ = cx.editor.open(&path, Action::Replace)?;
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
if let Some(pos) = pos {
let pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true)); let pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true));
doc.set_selection(view.id, pos); doc.set_selection(view.id, pos);
}
// does not affect opening a buffer without pos // does not affect opening a buffer without pos
align_view(doc, view, Align::Center); align_view(doc, view, Align::Center);
} }

@ -1034,6 +1034,8 @@ pub struct Editor {
pub debugger_events: SelectAll<UnboundedReceiverStream<dap::Payload>>, pub debugger_events: SelectAll<UnboundedReceiverStream<dap::Payload>>,
pub breakpoints: HashMap<PathBuf, Vec<Breakpoint>>, pub breakpoints: HashMap<PathBuf, Vec<Breakpoint>>,
pub old_file_locs: HashMap<PathBuf, ViewPosition>,
pub syn_loader: Arc<ArcSwap<syntax::Loader>>, pub syn_loader: Arc<ArcSwap<syntax::Loader>>,
pub theme_loader: Arc<theme::Loader>, pub theme_loader: Arc<theme::Loader>,
/// last_theme is used for theme previews. We store the current theme here, /// last_theme is used for theme previews. We store the current theme here,
@ -1150,6 +1152,7 @@ impl Editor {
syn_loader: Arc<ArcSwap<syntax::Loader>>, syn_loader: Arc<ArcSwap<syntax::Loader>>,
config: Arc<dyn DynAccess<Config>>, config: Arc<dyn DynAccess<Config>>,
handlers: Handlers, handlers: Handlers,
old_file_locs: HashMap<PathBuf, ViewPosition>,
) -> Self { ) -> Self {
let language_servers = helix_lsp::Registry::new(syn_loader.clone()); let language_servers = helix_lsp::Registry::new(syn_loader.clone());
let conf = config.load(); let conf = config.load();
@ -1177,6 +1180,7 @@ impl Editor {
debugger: None, debugger: None,
debugger_events: SelectAll::new(), debugger_events: SelectAll::new(),
breakpoints: HashMap::new(), breakpoints: HashMap::new(),
old_file_locs,
syn_loader, syn_loader,
theme_loader, theme_loader,
last_theme: None, last_theme: None,
@ -1636,6 +1640,18 @@ impl Editor {
); );
// initialize selection for view // initialize selection for view
let doc = doc_mut!(self, &id); 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.ensure_view_init(view_id);
doc.mark_as_focused(); doc.mark_as_focused();
} }
@ -1731,11 +1747,12 @@ impl Editor {
let doc = self.document(view.doc).unwrap(); let doc = self.document(view.doc).unwrap();
if let Some(path) = doc.path() { if let Some(path) = doc.path() {
push_file_history(FileHistoryEntry::new( push_file_history(FileHistoryEntry::new(
path.to_owned(), path.clone(),
view.offset.anchor, view.offset.anchor,
view.offset.vertical_offset, view.offset.vertical_offset,
view.offset.horizontal_offset, view.offset.horizontal_offset,
)); ));
self.old_file_locs.insert(path.to_owned(), view.offset);
}; };
// Remove selections for the closed view on all documents. // Remove selections for the closed view on all documents.

Loading…
Cancel
Save