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
108 changes: 108 additions & 0 deletions src/app/code_blocks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use super::{App, CodeBlockFlash};
use crate::{clipboard::copy_to_clipboard, markdown::CodeBlockInfo};

impl App {
pub(crate) fn set_code_blocks(&mut self, code_blocks: Vec<CodeBlockInfo>) {
self.code_blocks = code_blocks;
}

pub(crate) fn is_code_select_mode(&self) -> bool {
self.code_select.is_some()
}

pub(crate) fn exit_code_select_mode(&mut self) -> bool {
let was_active = self.code_select.is_some();
self.code_select = None;
was_active
}

fn visible_code_block_indices(&self) -> Vec<usize> {
let top = self.scroll;
let bottom = self.visible_end();
self.code_blocks
.iter()
.enumerate()
.filter(|(_, b)| b.rendered_end >= top && b.rendered_start < bottom)
.map(|(i, _)| i)
.collect()
}

pub(crate) fn copy_first_visible_code_block(&mut self) {
let indices = self.visible_code_block_indices();
match indices.first() {
Some(&idx) => self.copy_code_block_at(idx),
None => self.set_code_block_flash(CodeBlockFlash::NoneVisible),
}
}

pub(crate) fn enter_code_select_mode(&mut self) {
let indices = self.visible_code_block_indices();
match indices.first() {
Some(&idx) => self.code_select = Some(idx),
None => self.set_code_block_flash(CodeBlockFlash::NoneVisible),
}
}

pub(crate) fn code_select_next(&mut self) {
let indices = self.visible_code_block_indices();
if indices.is_empty() {
self.code_select = None;
return;
}
let current = self.code_select.unwrap_or(usize::MAX);
let next = indices
.iter()
.copied()
.find(|&i| i > current)
.unwrap_or(indices[0]);
self.code_select = Some(next);
}

pub(crate) fn code_select_prev(&mut self) {
let indices = self.visible_code_block_indices();
if indices.is_empty() {
self.code_select = None;
return;
}
let current = self.code_select.unwrap_or(0);
let prev = indices
.iter()
.rev()
.copied()
.find(|&i| i < current)
.unwrap_or_else(|| *indices.last().unwrap());
self.code_select = Some(prev);
}

pub(crate) fn copy_selected_code_block(&mut self) {
if let Some(idx) = self.code_select {
self.copy_code_block_at(idx);
}
self.code_select = None;
}

pub(crate) fn copy_code_block_at(&mut self, idx: usize) {
let raw = match self.code_blocks.get(idx) {
Some(b) => b.raw_content.clone(),
None => {
self.set_code_block_flash(CodeBlockFlash::CopyFailed);
return;
}
};
if copy_to_clipboard(&raw) {
self.set_code_block_flash(CodeBlockFlash::Copied);
} else {
self.set_code_block_flash(CodeBlockFlash::CopyFailed);
}
}

pub(crate) fn code_block_at_line(&self, line_idx: usize) -> Option<usize> {
self.code_blocks
.iter()
.position(|b| line_idx >= b.rendered_start && line_idx <= b.rendered_end)
}

pub(crate) fn highlighted_code_block(&self) -> Option<usize> {
self.code_select.or(self.hovered_code_block)
}
}
4 changes: 4 additions & 0 deletions src/app/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ impl App {
link_spans,
line_number_map,
source_line_map,
code_blocks,
} = parsed;

self.plain_lines = build_searchable_lines(&lines)
Expand All @@ -45,6 +46,9 @@ impl App {
self.toc_header_line = toc_header_line();
self.link_spans_by_line = super::links::link_spans_to_map(link_spans);
self.hovered_link = None;
self.set_code_blocks(code_blocks);
self.code_select = None;
self.hovered_code_block = None;
self.set_line_maps(line_number_map, source_line_map);
self.refresh_static_caches();
}
Expand Down
15 changes: 15 additions & 0 deletions src/app/flash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ pub(crate) enum PathFlash {
CopyFailed,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum CodeBlockFlash {
Copied,
CopyFailed,
NoneVisible,
}

impl App {
pub(crate) fn set_editor_flash(&mut self, flash: EditorFlash) {
self.editor_flash = Some((flash, Instant::now()));
Expand Down Expand Up @@ -142,4 +149,12 @@ impl App {
pub(crate) fn reload_flash_started(&self) -> Option<Instant> {
self.reload_flash
}

pub(crate) fn set_code_block_flash(&mut self, flash: CodeBlockFlash) {
self.code_block_flash = Some((flash, Instant::now()));
}

pub(crate) fn code_block_flash(&self) -> Option<(&CodeBlockFlash, &Instant)> {
self.code_block_flash.as_ref().map(|(f, t)| (f, t))
}
}
22 changes: 20 additions & 2 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
markdown::{
build_searchable_lines,
toc::{toc_levels, TocEntry},
LinkSpan,
CodeBlockInfo, LinkSpan,
},
render::{build_status_bar, build_toc_line_with_index, toc_header_line},
theme::{app_theme, current_theme_selection, theme_preset_index},
Expand Down Expand Up @@ -32,13 +32,17 @@ mod content;
pub(crate) use content::{FileChange, FileState};

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

mod popups;
pub(crate) use popups::{EditorPickerState, PathKind};

mod links;

mod code_blocks;

mod io_picker;

mod theme_picker;
Expand Down Expand Up @@ -68,6 +72,7 @@ pub(crate) struct StatusCacheKey {
config_flash_active: bool,
link_flash_active: bool,
path_flash_active: bool,
code_block_flash_active: bool,
}

pub(crate) struct AppConfig {
Expand Down Expand Up @@ -128,6 +133,10 @@ pub(crate) struct App {
config_flash: Option<(String, Instant)>,
pub(crate) link_spans_by_line: HashMap<usize, Vec<LinkSpan>>,
pub(crate) hovered_link: Option<(usize, usize)>,
pub(crate) code_blocks: Vec<CodeBlockInfo>,
pub(crate) code_select: Option<usize>,
pub(crate) hovered_code_block: Option<usize>,
code_block_flash: Option<(CodeBlockFlash, Instant)>,
link_flash: Option<(LinkFlash, Instant)>,
path_flash: Option<(PathFlash, Instant)>,
pub(crate) last_click: Option<(u16, u16, Instant)>,
Expand Down Expand Up @@ -278,6 +287,10 @@ impl App {
config_flash: None,
link_spans_by_line: HashMap::new(),
hovered_link: None,
code_blocks: Vec::new(),
code_select: None,
hovered_code_block: None,
code_block_flash: None,
link_flash: None,
path_flash: None,
last_click: None,
Expand Down Expand Up @@ -551,6 +564,11 @@ impl App {
.as_ref()
.map(|(_, t)| t.elapsed() < Duration::from_millis(FLASH_DURATION_MS))
.unwrap_or(false),
code_block_flash_active: self
.code_block_flash
.as_ref()
.map(|(_, t)| t.elapsed() < Duration::from_millis(FLASH_DURATION_MS))
.unwrap_or(false),
};

if self.status_cache_key.as_ref() == Some(&cache_key) {
Expand Down
9 changes: 9 additions & 0 deletions src/app/navigation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ impl App {
.saturating_sub(self.content_area.height as usize)
}

pub(crate) fn visible_end(&self) -> usize {
(self.scroll + self.content_area.height as usize).min(self.total())
}

pub(crate) fn scroll_percent(&self) -> u16 {
let max = self.max_scroll();
if max == 0 {
Expand All @@ -36,26 +40,31 @@ impl App {
pub(crate) fn scroll_down(&mut self, n: usize) {
self.reset_numkey_state();
self.scroll = (self.scroll + n).min(self.max_scroll());
self.hovered_code_block = None;
}

pub(crate) fn scroll_up(&mut self, n: usize) {
self.reset_numkey_state();
self.scroll = self.scroll.saturating_sub(n);
self.hovered_code_block = None;
}

pub(crate) fn scroll_top(&mut self) {
self.reset_numkey_state();
self.scroll = 0;
self.hovered_code_block = None;
}

pub(crate) fn scroll_bottom(&mut self) {
self.reset_numkey_state();
self.scroll = self.max_scroll();
self.hovered_code_block = None;
}

pub(crate) fn scroll_to(&mut self, position: usize) {
self.reset_numkey_state();
self.scroll = position.min(self.max_scroll());
self.hovered_code_block = None;
}

pub(crate) fn toggle_toc(&mut self) {
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ fn main() -> Result<()> {
link_spans,
line_number_map,
source_line_map,
code_blocks,
} = parsed;
let mut app = App::new_with_source(
lines,
Expand All @@ -357,6 +358,7 @@ fn main() -> Result<()> {
},
);
app.set_link_spans(link_spans);
app.set_code_blocks(code_blocks);
app.set_line_maps(line_number_map, source_line_map);
app.set_last_content_hash(last_content_hash);
app.set_watch_from_config(watch_from_config);
Expand Down
33 changes: 33 additions & 0 deletions src/markdown/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,36 @@ fn push_text_event(
push_custom_marker_spans(text, CUSTOM_MARKERS, fallback, theme, spans);
}

#[derive(Clone, Debug)]
pub(crate) struct CodeBlockInfo {
pub(crate) rendered_start: usize,
pub(crate) rendered_end: usize,
pub(crate) raw_content: String,
}

fn record_code_block(
code_blocks: &mut Vec<CodeBlockInfo>,
raw_content: String,
rendered_start: usize,
lines_len_after: usize,
) {
let rendered_end = lines_len_after.saturating_sub(1);
if rendered_end >= rendered_start {
code_blocks.push(CodeBlockInfo {
rendered_start,
rendered_end,
raw_content,
});
}
}

pub(crate) struct ParseResult {
pub(crate) lines: Vec<Line<'static>>,
pub(crate) toc: Vec<TocEntry>,
pub(crate) link_spans: Vec<LinkSpan>,
pub(crate) line_number_map: Vec<usize>,
pub(crate) source_line_map: Vec<usize>,
pub(crate) code_blocks: Vec<CodeBlockInfo>,
}

impl ParseResult {
Expand All @@ -236,6 +260,7 @@ impl ParseResult {
link_spans: Vec::new(),
line_number_map: Vec::new(),
source_line_map: Vec::new(),
code_blocks: Vec::new(),
}
}
}
Expand Down Expand Up @@ -297,6 +322,7 @@ pub(crate) fn parse_markdown_with_width(
let mut in_code = false;
let mut code_lang = String::new();
let mut code_buf = String::new();
let mut code_blocks: Vec<CodeBlockInfo> = Vec::new();
let mut blockquote_depth = 0usize;
let mut inline = InlineStyleState::default();
let mut list_stack: Vec<ListKind> = Vec::new();
Expand Down Expand Up @@ -383,6 +409,8 @@ pub(crate) fn parse_markdown_with_width(
}
MdEvent::End(TagEnd::CodeBlock) => {
in_code = false;
let raw_content = code_buf.clone();
let rendered_start = lines.len();
if code_lang == "latex" || code_lang == "tex" {
push_latex_block_lines(
&mut lines,
Expand Down Expand Up @@ -433,6 +461,7 @@ pub(crate) fn parse_markdown_with_width(
);
wraps = true;
}
record_code_block(&mut code_blocks, raw_content, rendered_start, lines.len());
last_block = LastBlock::Other;
}
MdEvent::Code(text) => {
Expand Down Expand Up @@ -539,6 +568,8 @@ pub(crate) fn parse_markdown_with_width(
lines.push(Line::from(std::mem::take(&mut spans)));
}
trim_paragraph_gap_before_block(&mut lines, last_block);
let raw_content = text.as_ref().trim().to_string();
let rendered_start = lines.len();
push_latex_block_lines(
&mut lines,
text.as_ref(),
Expand All @@ -551,6 +582,7 @@ pub(crate) fn parse_markdown_with_width(
},
&mut item_stack,
);
record_code_block(&mut code_blocks, raw_content, rendered_start, lines.len());
wraps = true;
last_block = LastBlock::Other;
}
Expand Down Expand Up @@ -583,6 +615,7 @@ pub(crate) fn parse_markdown_with_width(
link_spans,
line_number_map: state.line_number_map,
source_line_map: state.source_line_map,
code_blocks,
}
}

Expand Down
Loading
Loading