Compare commits

...

10 Commits

Author SHA1 Message Date
scbj 92b1080daa deny bookmark name help 2026-04-16 09:49:27 +02:00
scbj 1512d5117e added completions for book clean 2026-01-28 09:49:17 +01:00
root af40dee02d updated bash setup script 2025-10-25 12:05:50 +02:00
root d66f0defb8 inserted check for bookmarknames 2025-08-04 09:40:03 +02:00
scbj 4e8c311476 improved output of book remove & added todo 2025-07-09 10:39:33 +02:00
scbj 3eee1e304c added todo 2025-07-09 10:31:56 +02:00
scbj b10375dac0 readme fixes 2025-07-08 15:33:54 +02:00
root 71658df5fe implemented options to dedup/rotate stack 2025-07-06 21:21:44 +02:00
scbj 7dcdce624f added todo 2025-07-04 08:48:40 +02:00
root 1005eddeb8 implemented bookmarks subcommand clean to remove entries with invalid
paths
2025-07-03 23:48:24 +02:00
7 changed files with 107 additions and 27 deletions
+18 -11
View File
@@ -10,10 +10,11 @@ I implemented this program to learn rust (because I was put on a rust project at
* `push` - save path to the stack and change to specified directory
* `pop` - pop one, or the specified amount of entries from the stack and move to the oldest one
* `stack` - display the stack
* `book` - move to, or add, remove and display bookmarks
* `book` - move to/add/remove/display bookmarks
Every shell has its own stack, save in the file `/tmp/navigate/<process-id>`.
Every shell has its own stack, saved in the file `/tmp/navigate/<process-id>`.
`navigate` checks for and deletes orphaned stack files on execution.
This program does not run background tasks, all state is stored in temporary or configuration files.
## setup
@@ -21,14 +22,15 @@ Every shell has its own stack, save in the file `/tmp/navigate/<process-id>`.
`navigate` requires a setup
1) clone the repository
1) build crate
1) add path to executable to shell environment
1) source setup script `navigate_bash_setup`
1) add path to executable to shell environment, or copy the executable to a directory in the path variable (e.g. `/usr/local/bin`)
1) source setup script `navigate_bash_setup` for convenience functions and bash completions
## configuration
The behaviour of `navigate` can be configured in the file `$XDG_CONFIG_HOME/navigate/navigate.toml`.
*It has the toml extension, but might not implement the full toml specification.*
> *It has the toml extension, but might not implement the full toml specification.*
> `navigate` will check for the file `default.toml` in the configuration directory and create it if not found.
> It contains all settings with default values and a short explanation.
@@ -36,18 +38,21 @@ The behaviour of `navigate` can be configured in the file `$XDG_CONFIG_HOME/navi
The lines without type and value are categories and need to be defined as toml table (`[table]`) in the configuration file.
Options are written as `value = key`.
Style settings accept styles and one color in the following formats:
Style settings accept styles and one color separated by commas.
Make sure to wrap the whole string in single or double quotes.
The following formats are supported:
* **styles**: `bold`, `dim`, `italic`, `underlined`, `blinking`, `reversed`, `invisible`, `strikethrough`
* **named color**: `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`
* **numbered color**: `16`..`255`
* **rgb color**: `#rrggbb`
> *NOTE*: The styles and colors are applied as ansi escape sequences and I do not know a terminal which implements all sequences.
> *NOTE*: The styles and colors are applied as ansi escape sequences and your terminal may not support some of them.
# todos
- [x] replace `expect` statements in 'stack.rs' with actual error handling
- [ ] replace `std::io::Error` with `thiserror::Error`
- [x] option to pop several entries at a time and option to pop the entire stack
- [x] drop stack
- [x] config file
@@ -60,13 +65,15 @@ Style settings accept styles and one color in the following formats:
- [x] `show-bookmarks-on-book`
- [x] setting for separator string when displaying stack/bookmarks
- [x] color option for punctuation (mostly '/')
- [ ] option to dedup stack entries
- [ ] option for behaviour when jumping to stack entry via `push =<n>`
- [x] option to dedup stack entries
- [x] option for behaviour when jumping to stack entry via `push =<n>`
- [x] bookmarks
- [x] do not resolve links in bookmarks
- [x] option to show invalid paths
- [x] style option for invalid paths
- [ ] option & subcommand to remove invalid paths
- [x] subcommand to remove invalid paths
- [ ] print deleted bookmarks
- [ ] consistent output of `remove` subcommand
- [x] push <number> to push path in stack
- [x] write documentation
- [x] change config file extension to `.toml`
+11 -5
View File
@@ -1,6 +1,10 @@
#!/usr/bin/env bash
__call_navigate() {
if ! $(which navigate &>/dev/null); then
return 1
fi
function __call_navigate {
arg_pid=" --pid $$ "
eval "$(navigate ${arg_pid} $@)"
}
@@ -29,15 +33,17 @@ function navconfig {
function _book {
CURRENT_WORD=${COMP_WORDS[COMP_CWORD]}
if [[ COMP_CWORD -eq 1 ]]; then
BOOKMARKS="add remove $(__call_navigate bookmark names)"
BOOKMARKS="add remove clean $(__call_navigate bookmark completions)"
COMPREPLY=($(compgen -W "${BOOKMARKS}" -- $CURRENT_WORD))
elif [[ COMP_CWORD -eq 2 ]]; then
if [[ COMP_WORDS[1] = "remove" ]]; then
BOOKMARKS="$(__call_navigate bookmark names)"
if [[ "${COMP_WORDS[1]}" = "clean" ]]; then
unset COMPREPLY
elif [[ "${COMP_WORDS[1]}" = "remove" ]]; then
BOOKMARKS="$(__call_navigate bookmark completions)"
COMPREPLY=($(compgen -W "${BOOKMARKS}" -- $CURRENT_WORD))
fi
elif [[ COMP_CWORD -eq 3 ]]; then
if [[ COMP_WORDS[1] = "add" ]]; then
if [[ "${COMP_WORDS[1]}" = "add" ]]; then
COMPREPLY=($(compgen -o dirnames -- $CURRENT_WORD))
fi
fi
+3
View File
@@ -103,6 +103,9 @@ pub enum BookmarkAction {
/// remove a bookmark by name `book remove <name>`
remove(BookmarkSubArgs),
/// remove bookmarks with invalid paths
clean,
/// get bookmarknames for shell completions
completions,
}
+14 -2
View File
@@ -89,15 +89,27 @@ impl Bookmarks {
}
/// removes a the entry with key=name if it exists, then writes the bookmarks file
pub fn remove_bookmark(&mut self, name: &String) -> Result<()> {
pub fn remove_bookmark(&mut self, name: &String) -> Result<PathBuf> {
let path: PathBuf;
if self.bookmarks.contains_key(name) {
_ = self.bookmarks.remove(name);
path = match self.bookmarks.remove(name) {
Some(path) => path,
None => return Err(Error::other("-- those bastards, they lied to me!")),
};
self.write_bookmark_file()?;
} else {
return Err(Error::other(
"-- bookmark requested to delete does not exist",
));
}
Ok(path)
}
///
pub fn remove_invalid_paths(&mut self) -> Result<()> {
self.bookmarks.retain(|_, path| path.is_dir());
self.write_bookmark_file()?;
Ok(())
}
+8
View File
@@ -25,6 +25,14 @@ pub struct Config {
#[derive(Debug, Clone, Default, ConfigParser)]
pub struct GeneralSettings {
/// (bool) delete all older occurences of paths in the stack
#[default_value(false)]
pub dedup_stack: bool,
/// (bool) rotate stack to chosen path when jumping to stack entry
#[default_value(false)]
pub rotate_stack_on_jump_to_entry: bool,
/// (bool) show stack when pushing a path to the stack
#[default_value(false)]
pub show_stack_on_push: bool,
+16 -6
View File
@@ -58,7 +58,7 @@ fn main() -> Result<()> {
return Ok(());
}
};
let mut stack = match Stack::new(args.pid) {
let mut stack = match Stack::new(&config, args.pid) {
Ok(stack) => stack,
Err(_) => {
output.push_error(&"-- failed to build stack".to_string());
@@ -104,7 +104,12 @@ fn handle_push(args: &PushArgs, config: &Config, stack: &mut Stack, output: &mut
Ok(value) => value,
Err(_) => return Err(Error::other("-- push : failed to convert path argument to number")),
};
stack.get_entry_by_number(number)?.to_path_buf()
let path = stack.get_entry_by_number(number)?.to_path_buf();
if config.general.rotate_stack_on_jump_to_entry {
stack.rotate_stack(number)?;
}
_ = stack.pop_entry(None);
path
} else {
match PathBuf::from_str(&path_string) {
Ok(value) => value,
@@ -163,6 +168,7 @@ fn handle_bookmark(args: &BookmarkArgs, config: &Config, bookmarks: &mut Bookmar
BookmarkAction::list => list_bookmarks(config, bookmarks, output)?,
BookmarkAction::add(args) => add_bookmarks(args, config, bookmarks, output)?,
BookmarkAction::remove(args) => remove_bookmarks(args, config, bookmarks, output)?,
BookmarkAction::clean => bookmarks.remove_invalid_paths()?,
BookmarkAction::completions => println!("echo '{}'", bookmarks.get_bookmark_names()),
};
} else if args.name.is_some() { // handle `change to bookmark`
@@ -190,8 +196,12 @@ fn add_bookmarks(args: &BookmarkSubArgs, config: &Config, bookmarks: &mut Bookma
Ok(value) => value,
Err(error) => return Err(Error::other(error.to_string())),
};
if args.name == "add" || args.name == "remove" {
return Err(Error::other("-- `add` & `remove` are subcommands and cant be used as bookmarknames"));
if args.name == "add"
|| args.name == "remove"
|| args.name == "clean"
|| args.name == "help"
|| args.name == "completions" {
return Err(Error::other("-- `add`, `remove`, `clean`, `help` and `completions` are subcommands and cant be used as bookmarknames"));
}
bookmarks.add_bookmark(&args.name, &path)?;
@@ -208,12 +218,12 @@ fn add_bookmarks(args: &BookmarkSubArgs, config: &Config, bookmarks: &mut Bookma
}
fn remove_bookmarks(args: &BookmarkSubArgs, config: &Config, bookmarks: &mut Bookmarks, output: &mut Output) -> Result<()> {
bookmarks.remove_bookmark(&args.name)?;
let path = bookmarks.remove_bookmark(&args.name)?;
if config.general.show_entries_on_bookmark {
output.push_info(&bookmarks.to_formatted_string(config)?);
} else {
output.push_info(&format!("remove bookmark `{}{}{}`.", generate_style_sequence(Some(STYLES.set.bold), None, None), args.name, RESET_SEQ));
output.push_info(&format!("removed bookmark `{}{}{}{}{}`.", generate_style_sequence(Some(STYLES.set.bold), None, None), args.name, config.format.bookmarks_separator, path.to_str().unwrap(), RESET_SEQ));
}
Ok(())
}
+37 -3
View File
@@ -21,13 +21,13 @@ pub struct Stack {
impl Stack {
const STACK_FILE_DIRECTORY: &str = "/tmp/navigate/";
pub fn new(process_id: u32) -> Result<Self> {
pub fn new(config: &Config, process_id: u32) -> Result<Self> {
let mut stack: Stack = Stack {
pid: process_id,
path: PathBuf::new(),
stack: Vec::<PathBuf>::new(),
};
stack.build_stack()?;
stack.build_stack(config)?;
Ok(stack)
}
@@ -138,8 +138,25 @@ impl Stack {
}
}
/// rotate stack so that the <entry_number> is the latest
/// entry (first to be popped)
pub fn rotate_stack(&mut self, entry_number: usize) -> Result<()> {
if 0 == entry_number {
return Ok(());
} else if self.stack.len() <= entry_number {
return Err(Error::other("-- number to rotate is greater than the stacks length"));
}
let mut rotated_stack: Vec<PathBuf> = self.stack.drain(self.stack.len() - entry_number..).collect();
rotated_stack.extend(self.stack.drain(..));
self.stack = rotated_stack;
Ok(())
}
/// clean up dead stack files, parse and build stack
fn build_stack(&mut self) -> Result<()> {
fn build_stack(&mut self, config: &Config) -> Result<()> {
let stack_dir: PathBuf = match PathBuf::from_str(Self::STACK_FILE_DIRECTORY) {
Ok(value) => value,
Err(_) => {
@@ -187,6 +204,10 @@ impl Stack {
}
self.cleanup_stack();
if config.general.dedup_stack {
self.dedup_stack();
}
Ok(())
}
@@ -208,6 +229,19 @@ impl Stack {
}
}
/// keep only the newest occurence of a path
fn dedup_stack(&mut self) {
let mut deduped_stack: Vec<PathBuf> = Vec::new();
while !self.stack.is_empty() {
let entry: PathBuf = self.stack.remove(0);
if !self.stack.contains(&entry) {
deduped_stack.push(entry);
}
}
self.stack = deduped_stack;
}
/// write stack current stack to file to save it for next execution
fn write_stack_file(&mut self) -> Result<()> {
let mut output = Vec::<&str>::new();