Skip to content

Commit 917720d

Browse files
fix: interactive docs and 256-color themes
1 parent a66ab96 commit 917720d

10 files changed

Lines changed: 548 additions & 124 deletions

File tree

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.3.1] - 2025-11-21
9+
10+
### Fixed
11+
12+
- **Theme Color Mode Application** - Improved theme initialization order for more consistent color rendering
13+
- Color mode now applied before custom colors for better accuracy
14+
- Theme-specific 256-color variants now correctly loaded based on color mode
15+
- Fixes color inconsistencies when using custom theme colors with 256-color fallback
16+
17+
### Changed
18+
19+
- **Code Formatting** - Applied consistent code formatting throughout codebase
20+
- **Documentation** - Enhanced README with more detailed interactive mode instructions
21+
- Added comprehensive keyboard shortcuts for interactive mode
22+
- Clarified table navigation and editing workflow
23+
- Better organization of feature descriptions
24+
25+
### Technical
26+
27+
- **Theme Architecture** (`src/tui/theme.rs`)
28+
- Added `from_name_256()` method to load theme-specific 256-color variants
29+
- Reordered `with_color_mode()` to apply before `with_custom_colors()`
30+
- Each theme now has dedicated 256-color palette matching official theme colors
31+
- Better color accuracy in 256-color terminals
32+
33+
- **App State** (`src/tui/app.rs`)
34+
- Updated theme initialization: `from_name() → with_color_mode() → with_custom_colors()`
35+
- Custom colors now properly respect color mode constraints
36+
- Improved formatting consistency
37+
838
## [0.3.0] - 2025-11-20
939

1040
### Added

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "treemd"
3-
version = "0.3.0"
3+
version = "0.3.1"
44
edition = "2024"
55
authors = ["epistates, inc. <nick@epistates.com>"]
66
description = "A markdown navigator with tree-based structural navigation and syntax highlighting"

README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ A markdown navigator with tree-based structural navigation. Like `tree`, but int
1818
### Interactive TUI
1919

2020
- **Dual-pane interface** - Navigate outline while viewing content
21+
- **Interactive mode** - Navigate, edit, and interact with all markdown elements (tables, checkboxes, links, code blocks)
22+
- **Table navigation & editing** - Navigate cells with vim keys (hjkl), edit cell content in-place, copy cells/rows/tables
23+
- **Checkbox toggling** - Toggle task list items with instant file updates
2124
- **Live editing** - Edit files in default editor with auto-reload (respects $VISUAL/$EDITOR)
2225
- **Link following** - Follow markdown links with visual popup, supports anchor/file/wikilink/external URLs
2326
- **Navigation history** - Back/forward between files with full state preservation
@@ -27,7 +30,7 @@ A markdown navigator with tree-based structural navigation. Like `tree`, but int
2730
- **Collapsible tree** - Expand/collapse sections with Space/Enter
2831
- **Bookmarks** - Mark positions (`m`) and jump back (`'`)
2932
- **Adjustable layout** - Toggle outline visibility, resize panes
30-
- **Rich rendering** - Bold, italic, inline code, lists, blockquotes, code blocks
33+
- **Rich rendering** - Bold, italic, inline code, lists, blockquotes, code blocks, tables with box-drawing characters
3134

3235
### CLI Mode
3336

@@ -106,6 +109,23 @@ treemd README.md
106109
- `Shift+F` - Go forward in navigation history
107110
- `Esc` - Exit link follow mode
108111

112+
*Interactive Mode:*
113+
- `i` - Enter interactive mode (navigate all interactive elements)
114+
- `Tab`/`j`/`k` or `↓/↑` - Navigate between elements
115+
- `Enter` - Activate element (toggle checkbox, follow link, enter table mode)
116+
- `Space` - Toggle checkboxes or details blocks
117+
- `y` - Copy element content (code blocks, table cells, links)
118+
- `Esc` - Exit interactive mode
119+
120+
*Table Navigation (in interactive mode):*
121+
- `Enter` on table - Enter table navigation mode
122+
- `h`/`j`/`k`/`l` or arrow keys - Navigate table cells
123+
- `y` - Copy current cell content
124+
- `Y` - Copy current row (tab-separated)
125+
- `r` - Copy entire table as markdown
126+
- `Enter` on cell - Edit cell content
127+
- `Esc` - Exit table navigation mode
128+
109129
*Editing & System:*
110130
- `e` - Edit current file in default editor (respects $VISUAL or $EDITOR)
111131
- `t` - Cycle color theme
@@ -119,6 +139,9 @@ treemd README.md
119139

120140
**Interface Features:**
121141
- **Live editing** - Edit files in your default editor and auto-reload (press `e`)
142+
- **Interactive mode** - Navigate and interact with all markdown elements (press `i`)
143+
- **Table editing** - Navigate cells with vim keys, edit cell content in-place
144+
- **Checkbox toggling** - Toggle task list items and save changes to file
122145
- **Link following popup** - Visual navigator shows all links with highlighting (press `f`)
123146
- **Multi-file navigation** - Load files via links with full history (back/forward)
124147
- **External URL opening** - Opens links in default browser automatically

src/config.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
44
use std::fs;
55
use std::path::PathBuf;
66

7-
#[derive(Debug, Clone, Serialize, Deserialize)]
7+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
88
pub struct Config {
99
#[serde(default)]
1010
pub ui: UiConfig,
@@ -122,15 +122,6 @@ impl ColorValue {
122122
}
123123
}
124124

125-
impl Default for Config {
126-
fn default() -> Self {
127-
Self {
128-
ui: UiConfig::default(),
129-
terminal: TerminalConfig::default(),
130-
theme: CustomThemeConfig::default(),
131-
}
132-
}
133-
}
134125

135126
impl Default for UiConfig {
136127
fn default() -> Self {

src/tui/app.rs

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,11 @@ impl App {
115115

116116
let content_lines = document.content.lines().count();
117117

118-
// Load theme from config, apply custom colors, then apply color mode
118+
// Load theme from config, apply color mode, then apply custom colors
119119
let current_theme = config.theme_name();
120120
let theme = Theme::from_name(current_theme)
121-
.with_custom_colors(&config.theme)
122-
.with_color_mode(color_mode);
121+
.with_color_mode(color_mode, current_theme)
122+
.with_custom_colors(&config.theme, color_mode);
123123

124124
// Load outline width from config
125125
let outline_width = config.ui.outline_width;
@@ -440,7 +440,8 @@ impl App {
440440
// Element is below viewport - scroll down to show it
441441
else if end > viewport_end {
442442
// Try to position element at top of viewport
443-
self.content_scroll = start.min(self.content_height.saturating_sub(viewport_height));
443+
self.content_scroll =
444+
start.min(self.content_height.saturating_sub(viewport_height));
444445
}
445446
// Element partially visible at bottom - ensure fully visible
446447
else if start >= scroll && end > viewport_end {
@@ -677,8 +678,8 @@ impl App {
677678
self.current_theme = new_theme;
678679
// Apply color mode when setting theme (also apply custom colors from config)
679680
self.theme = Theme::from_name(new_theme)
680-
.with_custom_colors(&self.config.theme)
681-
.with_color_mode(self.color_mode);
681+
.with_color_mode(self.color_mode, new_theme)
682+
.with_custom_colors(&self.config.theme, self.color_mode);
682683
self.show_theme_picker = false;
683684

684685
// Save to config (silently ignore errors)
@@ -1140,7 +1141,7 @@ impl App {
11401141
// Get current section content to index
11411142
let content = if let Some(selected) = self.selected_heading_text() {
11421143
self.document
1143-
.extract_section(&selected)
1144+
.extract_section(selected)
11441145
.unwrap_or_else(|| self.document.content.clone())
11451146
} else {
11461147
self.document.content.clone()
@@ -1234,10 +1235,9 @@ impl App {
12341235
}
12351236
ElementType::Table { rows, cols, .. } => {
12361237
// Enter table navigation mode
1237-
if let Err(e) = self.interactive_state.enter_table_mode() {
1238-
return Err(e);
1239-
}
1240-
self.status_message = Some(self.interactive_state.table_status_text(rows + 1, *cols));
1238+
self.interactive_state.enter_table_mode()?;
1239+
self.status_message =
1240+
Some(self.interactive_state.table_status_text(rows + 1, *cols));
12411241
Ok(())
12421242
}
12431243
}
@@ -1247,7 +1247,7 @@ impl App {
12471247
fn reindex_interactive_elements(&mut self) {
12481248
let content = if let Some(selected) = self.selected_heading_text() {
12491249
self.document
1250-
.extract_section(&selected)
1250+
.extract_section(selected)
12511251
.unwrap_or_else(|| self.document.content.clone())
12521252
} else {
12531253
self.document.content.clone()
@@ -1268,30 +1268,25 @@ impl App {
12681268
// Get the checkbox content text to use as identifier
12691269
let checkbox_content = {
12701270
let content = if let Some(selected) = self.selected_heading_text() {
1271-
self.document.extract_section(&selected).unwrap_or_else(|| self.document.content.clone())
1271+
self.document
1272+
.extract_section(selected)
1273+
.unwrap_or_else(|| self.document.content.clone())
12721274
} else {
12731275
self.document.content.clone()
12741276
};
12751277

12761278
use crate::parser::content::parse_content;
12771279
let blocks = parse_content(&content, 0);
12781280

1279-
if let Some(block) = blocks.get(block_idx) {
1280-
if let crate::parser::output::Block::List { items, .. } = block {
1281-
if let Some(item) = items.get(item_idx) {
1282-
Some(item.content.clone())
1283-
} else {
1284-
None
1285-
}
1286-
} else {
1287-
None
1288-
}
1281+
if let Some(crate::parser::output::Block::List { items, .. }) = blocks.get(block_idx) {
1282+
items.get(item_idx).map(|item| item.content.clone())
12891283
} else {
12901284
None
12911285
}
12921286
};
12931287

1294-
let checkbox_content = checkbox_content.ok_or_else(|| "Could not find checkbox content".to_string())?;
1288+
let checkbox_content =
1289+
checkbox_content.ok_or_else(|| "Could not find checkbox content".to_string())?;
12951290

12961291
// Read the current file
12971292
let file_content = std::fs::read_to_string(&self.current_file_path)
@@ -1340,7 +1335,9 @@ impl App {
13401335
let trimmed = line.trim_start();
13411336

13421337
// Check if this is a checkbox line
1343-
if (trimmed.starts_with("- [ ]") || trimmed.starts_with("- [x]") || trimmed.starts_with("- [X]"))
1338+
if (trimmed.starts_with("- [ ]")
1339+
|| trimmed.starts_with("- [x]")
1340+
|| trimmed.starts_with("- [X]"))
13441341
&& !found
13451342
{
13461343
// Extract the text after the checkbox marker
@@ -1435,21 +1432,23 @@ impl App {
14351432
/// Get table data for current interactive element
14361433
fn get_current_table_data(&self) -> Option<(Vec<String>, Vec<Vec<String>>)> {
14371434
if let Some(element) = self.interactive_state.current_element() {
1438-
if let crate::tui::interactive::ElementType::Table { block_idx, .. } = &element.element_type {
1435+
if let crate::tui::interactive::ElementType::Table { block_idx, .. } =
1436+
&element.element_type
1437+
{
14391438
// Parse current section to get table data
14401439
let content = if let Some(selected) = self.selected_heading_text() {
1441-
self.document.extract_section(&selected).unwrap_or_else(|| self.document.content.clone())
1440+
self.document
1441+
.extract_section(selected)
1442+
.unwrap_or_else(|| self.document.content.clone())
14421443
} else {
14431444
self.document.content.clone()
14441445
};
14451446

14461447
use crate::parser::content::parse_content;
14471448
let blocks = parse_content(&content, 0);
14481449

1449-
if let Some(block) = blocks.get(*block_idx) {
1450-
if let crate::parser::output::Block::Table { headers, rows, .. } = block {
1451-
return Some((headers.clone(), rows.clone()));
1452-
}
1450+
if let Some(crate::parser::output::Block::Table { headers, rows, .. }) = blocks.get(*block_idx) {
1451+
return Some((headers.clone(), rows.clone()));
14531452
}
14541453
}
14551454
}
@@ -1592,11 +1591,9 @@ impl App {
15921591
if let Some(element) = self.interactive_state.current_element() {
15931592
let block_idx = element.id.block_idx;
15941593

1595-
if let Some(block) = blocks.get(block_idx) {
1596-
if let crate::parser::output::Block::Table { .. } = block {
1597-
// Find this table in the full file content
1598-
return self.replace_table_cell_in_file(content, row, col, new_value);
1599-
}
1594+
if let Some(crate::parser::output::Block::Table { .. }) = blocks.get(block_idx) {
1595+
// Find this table in the full file content
1596+
return self.replace_table_cell_in_file(content, row, col, new_value);
16001597
}
16011598
}
16021599

0 commit comments

Comments
 (0)