Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ file-picker-width = "75%" # width of the file picker (fuzzy + file browser)
toc-filelist-width = "25%" # width of the TOC and file-list side panels
main-line-numbers = false # show line numbers in the main document by default
code-line-numbers = true # show line numbers inside fenced code blocks
keep-soft-breaks = true # keep source line breaks inside paragraphs
tab-title-length = -1 # terminal tab title truncation (min: 20, -1: no truncation)
file-history-length = 0 # recent file history length (0 disables, max: 50)
hyper-link-prefix = "#" # single character before link text ("" for none)
Expand Down Expand Up @@ -390,7 +391,7 @@ leaf --theme rose-pine README.md
| Key | Action | Key | Action |
|---|---|---|---|
| `j` / `↓` | Scroll down | `?` | Show help popup |
| `k` / `↑` | Scroll up | `p` | Show file path |
| `k` / `↑` | Scroll up | `Shift+B` | Toggle soft breaks |
| `d` / PgDn | Page down (20 lines) | `Shift+L` | Toggle line numbers |
| `u` / PgUp | Page up (20 lines) | `Shift+T` | Open theme picker |
| `g` / Home | Top | `Shift+E` | Open editor picker |
Expand All @@ -405,7 +406,8 @@ leaf --theme rose-pine README.md
| `w` | Toggle watch mode | `Double-Click` (code) | Copy code block |
| `r` | Force reload (watch mode) | `Shift+Drag` | Select text |
| `t` | Toggle TOC sidebar | `Option+Drag` | Select text (iTerm2) |
| `q` | Quit | `Ctrl+Double-Click` | Copy line |
| `p` | Show file path | `Ctrl+Double-Click` | Copy line |
| `q` | Quit | | |

## Features

Expand Down
8 changes: 8 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ watch = false
# Default: true
# code-line-numbers = true

# Keep soft line breaks inside paragraphs
# Set to false to join paragraph lines and re-wrap them to the content width.
# Can still be toggled interactively with 'Shift+B'.
# Priority: LEAF_KEEP_SOFT_BREAKS > this setting > default (true)
# Accepted env values: 1 = true, 0 = false (other values ignored)
# Default: true
# keep-soft-breaks = true

# Maximum tab title length (in characters)
# Controls the tab title of the terminal: "leaf: <filename>".
# When the filename is long, it is truncated: "leaf: prefix...ext".
Expand Down
67 changes: 46 additions & 21 deletions src/app/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,18 +262,9 @@ impl App {
self.mode = super::AppMode::Document;
let (wrapped, is_code_file) = Self::wrap_as_code_block(src, &ext, ss);
self.file_mode = is_code_file;
self.plain_text_file = Self::is_plain_text_extension(&ext);
self.source = wrapped;
let theme = current_syntect_theme(themes);
let at = app_theme();
let parsed = parse_markdown_with_width(
&self.source,
ss,
theme,
self.render_width,
&at.markdown,
self.file_mode,
self.code_line_numbers,
);
let parsed = self.parse_current_source(ss, themes);
self.invalidate_theme_previews();
self.store_current_theme_preview_from(&parsed);
self.replace_content(parsed);
Expand All @@ -284,26 +275,60 @@ impl App {
}

pub(crate) fn reparse_source(&mut self, ss: &SyntaxSet, themes: &ThemeSet) {
let old_total = self.total();
let parsed = self.parse_current_source(ss, themes);
let new_total = parsed.lines.len();

if old_total > 0 {
self.scroll = ((self.scroll as f64 / old_total as f64) * new_total as f64) as usize;
let vh = self.content_area.height as usize;
self.scroll = self.scroll.min(new_total.saturating_sub(vh));
}

self.apply_reparsed(parsed);
}

pub(crate) fn toggle_soft_breaks(&mut self, ss: &SyntaxSet, themes: &ThemeSet) {
let keep = !self.keep_soft_breaks();
self.soft_breaks_override = Some(keep);
let anchor = self.source_line_at(self.scroll);
let parsed = self.parse_current_source(ss, themes);
let new_total = parsed.lines.len();
let map = &parsed.source_line_map;
let target = map
.iter()
.copied()
.filter(|&line| line <= anchor)
.max()
.and_then(|best| map.iter().position(|&line| line == best))
.unwrap_or(0);
let vh = self.content_area.height as usize;
self.scroll = target.min(new_total.saturating_sub(vh));

self.apply_reparsed(parsed);
self.set_soft_break_flash(if keep {
super::SoftBreakFlash::Kept
} else {
super::SoftBreakFlash::Joined
});
}

pub(super) fn parse_current_source(&self, ss: &SyntaxSet, themes: &ThemeSet) -> ParseResult {
let theme = current_syntect_theme(themes);
let at = app_theme();
let old_total = self.total();
let parsed = parse_markdown_with_width(
parse_markdown_with_width(
&self.source,
ss,
theme,
self.render_width,
&at.markdown,
self.file_mode,
self.code_line_numbers,
);
let new_total = parsed.lines.len();

if old_total > 0 {
self.scroll = ((self.scroll as f64 / old_total as f64) * new_total as f64) as usize;
let vh = self.content_area.height as usize;
self.scroll = self.scroll.min(new_total.saturating_sub(vh));
}
self.keep_soft_breaks(),
)
}

fn apply_reparsed(&mut self, parsed: ParseResult) {
self.invalidate_theme_previews();
self.store_current_theme_preview_from(&parsed);
self.replace_content(parsed);
Expand Down
18 changes: 18 additions & 0 deletions src/app/flash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ pub(crate) enum HistoryFlash {
LengthCapped { was: i32 },
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum SoftBreakFlash {
Kept,
Joined,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum DiffFlash {
SearchInPreview,
Expand Down Expand Up @@ -231,4 +237,16 @@ impl App {
pub(crate) fn clear_diff_flash(&mut self) {
self.diff_flash = None;
}

pub(crate) fn set_soft_break_flash(&mut self, flash: SoftBreakFlash) {
self.soft_break_flash = Some((flash, Instant::now()));
}

pub(crate) fn soft_break_flash(&self) -> Option<(&SoftBreakFlash, &Instant)> {
self.soft_break_flash.as_ref().map(|(f, t)| (f, t))
}

pub(crate) fn clear_soft_break_flash(&mut self) {
self.soft_break_flash = None;
}
}
4 changes: 4 additions & 0 deletions src/app/io_picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ impl App {
format!("````{ext}\n{src}\n````")
}

pub(crate) fn is_plain_text_extension(ext: &str) -> bool {
!ext.is_empty() && !Self::is_markdown_extension(ext)
}

pub(crate) fn wrap_as_code_block(src: String, ext: &str, ss: &SyntaxSet) -> (String, bool) {
if Self::is_markdown_extension(ext) || !Self::has_code_syntax(ext, ss) {
(src, false)
Expand Down
38 changes: 36 additions & 2 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ pub(crate) use diff_preview::{fetch_before, reconstruct_after_from_hunks};

mod flash;
pub(crate) use flash::{
CodeBlockFlash, DiffFlash, EditorFlash, HistoryFlash, LinkFlash, PathFlash, WatchFlash,
COPY_HIGHLIGHT_DURATION_MS, FLASH_DURATION_MS,
CodeBlockFlash, DiffFlash, EditorFlash, HistoryFlash, LinkFlash, PathFlash, SoftBreakFlash,
WatchFlash, COPY_HIGHLIGHT_DURATION_MS, FLASH_DURATION_MS,
};

mod text_copy;
Expand Down Expand Up @@ -133,6 +133,7 @@ pub(crate) struct StatusCacheKey {
code_block_flash_active: bool,
history_flash_active: bool,
diff_flash_active: bool,
soft_break_flash_active: bool,
preview_reconstruct_error: bool,
mouse_capture: bool,
toc_scroll_hint_visible: bool,
Expand Down Expand Up @@ -196,6 +197,7 @@ pub(crate) struct App {
pub(super) file_history_length: Option<i32>,
pub(super) history_flash: Option<(HistoryFlash, std::time::Instant)>,
pub(super) diff_flash: Option<(DiffFlash, std::time::Instant)>,
pub(super) soft_break_flash: Option<(SoftBreakFlash, std::time::Instant)>,
pub(super) syntax_set: Option<std::sync::Arc<syntect::parsing::SyntaxSet>>,
pub(super) diff_highlight: Option<(
std::sync::Arc<syntect::parsing::SyntaxSet>,
Expand Down Expand Up @@ -238,6 +240,9 @@ pub(crate) struct App {
reverse_mode: bool,
pub(super) file_mode: bool,
pub(super) code_line_numbers: bool,
keep_soft_breaks_pref: bool,
soft_breaks_override: Option<bool>,
pub(super) plain_text_file: bool,
scroll_with_enter: bool,
page_scroll_size: crate::config::PageScrollSize,
max_width: Option<usize>,
Expand Down Expand Up @@ -381,6 +386,7 @@ impl App {
file_history_length: None,
history_flash: None,
diff_flash: None,
soft_break_flash: None,
syntax_set: None,
diff_highlight: None,
history_error_receiver: None,
Expand Down Expand Up @@ -429,6 +435,9 @@ impl App {
reverse_mode: false,
file_mode: false,
code_line_numbers: true,
keep_soft_breaks_pref: true,
soft_breaks_override: None,
plain_text_file: false,
scroll_with_enter: false,
page_scroll_size: crate::config::PageScrollSize::default(),
max_width: None,
Expand Down Expand Up @@ -471,6 +480,26 @@ impl App {
self.code_line_numbers = value;
}

pub(crate) fn set_keep_soft_breaks(&mut self, value: bool) {
self.keep_soft_breaks_pref = value;
}

pub(crate) fn set_plain_text_file(&mut self, value: bool) {
self.plain_text_file = value;
}

pub(crate) fn default_keep_soft_breaks(pref: bool, plain_text_file: bool) -> bool {
pref || plain_text_file
}

pub(crate) fn keep_soft_breaks(&self) -> bool {
self.soft_breaks_override
.unwrap_or(Self::default_keep_soft_breaks(
self.keep_soft_breaks_pref,
self.plain_text_file,
))
}

pub(crate) fn set_scroll_with_enter(&mut self, value: bool) {
self.scroll_with_enter = value;
}
Expand Down Expand Up @@ -909,6 +938,11 @@ impl App {
.as_ref()
.map(|(_, t)| t.elapsed() < Duration::from_millis(FLASH_DURATION_MS))
.unwrap_or(false),
soft_break_flash_active: self
.soft_break_flash
.as_ref()
.map(|(_, t)| t.elapsed() < Duration::from_millis(FLASH_DURATION_MS))
.unwrap_or(false),
preview_reconstruct_error: self.is_diff_preview_reconstruction_error(),
mouse_capture: self.mouse_capture,
toc_scroll_hint_visible: self.is_toc_scroll_hint_visible(),
Expand Down
30 changes: 5 additions & 25 deletions src/app/theme_picker.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{
markdown::{parse_markdown_with_width, ParseResult},
markdown::ParseResult,
theme::{
app_theme, current_syntect_theme, current_theme_selection, set_theme_preset,
set_theme_selection, theme_preset_index, ThemePreset, ThemeSelection, THEME_PRESETS,
current_syntect_theme, current_theme_selection, set_theme_preset, set_theme_selection,
theme_preset_index, ThemePreset, ThemeSelection, THEME_PRESETS,
},
};
use syntect::{highlighting::ThemeSet, parsing::SyntaxSet};
Expand Down Expand Up @@ -116,17 +116,7 @@ impl App {
return;
}

let theme = current_syntect_theme(themes);
let at = app_theme();
let parsed = parse_markdown_with_width(
&self.source,
ss,
theme,
self.render_width,
&at.markdown,
self.file_mode,
self.code_line_numbers,
);
let parsed = self.parse_current_source(ss, themes);
self.store_theme_preview(preset, parsed.clone());
self.replace_content(parsed);
}
Expand All @@ -145,17 +135,7 @@ impl App {
if let Some(parsed) = self.theme_picker.original_preview.take() {
self.replace_content(parsed);
} else {
let theme = current_syntect_theme(themes);
let at = app_theme();
let parsed = parse_markdown_with_width(
&self.source,
ss,
theme,
self.render_width,
&at.markdown,
self.file_mode,
self.code_line_numbers,
);
let parsed = self.parse_current_source(ss, themes);
self.replace_content(parsed);
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ pub(crate) struct LeafConfig {
pub(crate) file_history_length: Option<i32>,
#[serde(rename = "hyper-link-prefix")]
pub(crate) hyper_link_prefix: Option<String>,
#[serde(rename = "keep-soft-breaks")]
pub(crate) keep_soft_breaks: Option<bool>,
#[serde(rename = "scroll-with-enter")]
pub(crate) scroll_with_enter: Option<bool>,
#[serde(
Expand Down
Loading
Loading