From 8ed20e48ff0ee57a61594f7598a269a467aac331 Mon Sep 17 00:00:00 2001 From: "Rasmus \"Pez\" Wejlgaard" Date: Wed, 12 Nov 2025 20:34:09 +0000 Subject: [PATCH] fix: org syntax highlighting outside of codeblocks and more settings (#12) --- internal/config/config.go | 26 +++++--- internal/model/item.go | 1 + internal/parser/parser.go | 24 ++++--- internal/parser/writer.go | 11 ++++ internal/ui/app.go | 2 +- internal/ui/modes.go | 86 ++++++++++++++++++++++-- internal/ui/settings.go | 135 +++++++++++++++++++++++++++++++++++++- internal/ui/views.go | 88 +++++++++++++++++-------- 8 files changed, 321 insertions(+), 52 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 9541938..71380e6 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -91,9 +91,12 @@ type StatesConfig struct { // UIConfig holds UI-related configurations type UIConfig struct { - HelpTextWidth int `toml:"help_text_width"` - MinTerminalWidth int `toml:"min_terminal_width"` - AgendaDays int `toml:"agenda_days"` + HelpTextWidth int `toml:"help_text_width"` + MinTerminalWidth int `toml:"min_terminal_width"` + AgendaDays int `toml:"agenda_days"` + OrgSyntaxHighlighting bool `toml:"org_syntax_highlighting"` + ShowIndentationGuides bool `toml:"show_indentation_guides"` + IndentationGuideColor string `toml:"indentation_guide_color"` } // DefaultConfig returns the default configuration @@ -161,9 +164,12 @@ func DefaultConfig() *Config { DefaultNewTaskState: "TODO", }, UI: UIConfig{ - HelpTextWidth: 22, - MinTerminalWidth: 40, - AgendaDays: 7, + HelpTextWidth: 22, + MinTerminalWidth: 40, + AgendaDays: 7, + OrgSyntaxHighlighting: true, + ShowIndentationGuides: true, + IndentationGuideColor: "245", }, } } @@ -371,10 +377,11 @@ func (c *Config) fillDefaults() { // Fill states if empty if len(c.States.States) == 0 { c.States.States = defaults.States.States - } - if c.States.DefaultNewTaskState == "" { + // Also set the default new task state since the entire states section is missing c.States.DefaultNewTaskState = defaults.States.DefaultNewTaskState } + // Note: We don't fill DefaultNewTaskState if States.States is non-empty because + // an empty string is a valid intentional value meaning "no default state". // Fill UI if zero values if c.UI.HelpTextWidth == 0 { @@ -386,6 +393,9 @@ func (c *Config) fillDefaults() { if c.UI.AgendaDays == 0 { c.UI.AgendaDays = defaults.UI.AgendaDays } + if c.UI.IndentationGuideColor == "" { + c.UI.IndentationGuideColor = defaults.UI.IndentationGuideColor + } } // BuildKeyBinding creates a key.Binding from config diff --git a/internal/model/item.go b/internal/model/item.go index ac87698..f926152 100644 --- a/internal/model/item.go +++ b/internal/model/item.go @@ -21,6 +21,7 @@ type Item struct { Tags []string // Tags for this item (e.g., :work:urgent:) Scheduled *time.Time Deadline *time.Time + Closed *time.Time // Closed timestamp (when task was marked as done) Effort string // Effort estimate (e.g., "8h", "2d") Notes []string // Notes/content under the heading Children []*Item // Sub-items diff --git a/internal/parser/parser.go b/internal/parser/parser.go index b43cd87..387a7c9 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -14,15 +14,16 @@ import ( // Parser patterns var ( - scheduledPattern = regexp.MustCompile(`SCHEDULED:\s*<([^>]+)>`) - deadlinePattern = regexp.MustCompile(`DEADLINE:\s*<([^>]+)>`) - clockPattern = regexp.MustCompile(`CLOCK:\s*\[([^\]]+)\](?:--\[([^\]]+)\])?`) - effortPattern = regexp.MustCompile(`^\s*:EFFORT:\s*(.+)$`) - logbookDrawerStart = regexp.MustCompile(`^\s*:LOGBOOK:\s*$`) + scheduledPattern = regexp.MustCompile(`SCHEDULED:\s*<([^>]+)>`) + deadlinePattern = regexp.MustCompile(`DEADLINE:\s*<([^>]+)>`) + closedPattern = regexp.MustCompile(`CLOSED:\s*\[([^\]]+)\]`) + clockPattern = regexp.MustCompile(`CLOCK:\s*\[([^\]]+)\](?:--\[([^\]]+)\])?`) + effortPattern = regexp.MustCompile(`^\s*:EFFORT:\s*(.+)$`) + logbookDrawerStart = regexp.MustCompile(`^\s*:LOGBOOK:\s*$`) propertiesDrawerStart = regexp.MustCompile(`^\s*:PROPERTIES:\s*$`) - drawerEnd = regexp.MustCompile(`^\s*:END:\s*$`) - codeBlockStart = regexp.MustCompile(`^\s*#\+BEGIN_SRC`) - codeBlockEnd = regexp.MustCompile(`^\s*#\+END_SRC`) + drawerEnd = regexp.MustCompile(`^\s*:END:\s*$`) + codeBlockStart = regexp.MustCompile(`^\s*#\+BEGIN_SRC`) + codeBlockEnd = regexp.MustCompile(`^\s*#\+END_SRC`) ) // buildHeadingPattern creates a regex pattern that matches configured states @@ -187,6 +188,13 @@ func ParseOrgFile(path string, cfg *config.Config) (*model.OrgFile, error) { } } + // Check for CLOSED + if matches := closedPattern.FindStringSubmatch(line); matches != nil { + if t, err := parseClockTimestamp(matches[1]); err == nil { + currentItem.Closed = &t + } + } + // Check for EFFORT (inside PROPERTIES drawer) if matches := effortPattern.FindStringSubmatch(line); matches != nil { currentItem.Effort = strings.TrimSpace(matches[1]) diff --git a/internal/parser/writer.go b/internal/parser/writer.go index fcebb00..a1197b1 100644 --- a/internal/parser/writer.go +++ b/internal/parser/writer.go @@ -128,6 +128,7 @@ func writeItem(writer *bufio.Writer, item *model.Item) error { // Write scheduling info if not already in notes hasScheduled := false hasDeadline := false + hasClosed := false hasLogbook := false hasProperties := false for _, note := range item.Notes { @@ -137,6 +138,9 @@ func writeItem(writer *bufio.Writer, item *model.Item) error { if strings.Contains(note, "DEADLINE:") { hasDeadline = true } + if strings.Contains(note, "CLOSED:") { + hasClosed = true + } if strings.Contains(note, ":LOGBOOK:") { hasLogbook = true } @@ -145,6 +149,13 @@ func writeItem(writer *bufio.Writer, item *model.Item) error { } } + if item.Closed != nil && !hasClosed { + closedLine := fmt.Sprintf("CLOSED: [%s]\n", formatClockTimestamp(*item.Closed)) + if _, err := writer.WriteString(closedLine); err != nil { + return err + } + } + if item.Scheduled != nil && !hasScheduled { scheduledLine := fmt.Sprintf("SCHEDULED: <%s>\n", FormatOrgDate(*item.Scheduled)) if _, err := writer.WriteString(scheduledLine); err != nil { diff --git a/internal/ui/app.go b/internal/ui/app.go index 9f1bd30..3b89bc8 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -112,7 +112,7 @@ func (m *uiModel) updateScrollOffset(availableHeight int) { noteIndent := indent + " " filteredNotes := filterLogbookDrawer(item.Notes) wrappedNotes := wrapNoteLines(filteredNotes, m.width, noteIndent) - highlightedNotes := renderNotesWithHighlighting(wrappedNotes) + highlightedNotes := m.renderNotesWithHighlighting(wrappedNotes) lineCount += len(highlightedNotes) } itemLineCount[i] = lineCount diff --git a/internal/ui/modes.go b/internal/ui/modes.go index 46d1d57..e4df26d 100644 --- a/internal/ui/modes.go +++ b/internal/ui/modes.go @@ -726,6 +726,7 @@ func (m *uiModel) cycleStateForward(item *model.Item) { // Find current state index currentIndex := -1 currentState := string(item.State) + lastStateIndex := len(stateNames) - 1 // Handle empty state if currentState == "" { @@ -739,15 +740,51 @@ func (m *uiModel) cycleStateForward(item *model.Item) { } } + // Store the old state to check if we're transitioning to/from DONE + oldState := currentState + var newState string + // Cycle forward if currentIndex < 0 || currentIndex >= len(stateNames)-1 { if currentIndex == len(stateNames)-1 { - item.State = model.TodoState("") // Back to empty + newState = "" // Back to empty } else { - item.State = model.TodoState(stateNames[0]) // First state + newState = stateNames[0] // First state } } else { - item.State = model.TodoState(stateNames[currentIndex+1]) + newState = stateNames[currentIndex+1] + } + + // Update the item state + item.State = model.TodoState(newState) + + // Manage CLOSED timestamp + wasInDoneState := (oldState == stateNames[lastStateIndex]) + isInDoneState := (newState == stateNames[lastStateIndex]) + + if isInDoneState && !wasInDoneState { + // Moving TO done state - add CLOSED timestamp + now := time.Now() + item.Closed = &now + // Remove any existing CLOSED line from notes + var filteredNotes []string + for _, note := range item.Notes { + if !strings.HasPrefix(strings.TrimSpace(note), "CLOSED:") { + filteredNotes = append(filteredNotes, note) + } + } + item.Notes = filteredNotes + } else if wasInDoneState && !isInDoneState { + // Moving FROM done state - remove CLOSED timestamp + item.Closed = nil + // Remove any existing CLOSED line from notes + var filteredNotes []string + for _, note := range item.Notes { + if !strings.HasPrefix(strings.TrimSpace(note), "CLOSED:") { + filteredNotes = append(filteredNotes, note) + } + } + item.Notes = filteredNotes } } @@ -760,6 +797,7 @@ func (m *uiModel) cycleStateBackward(item *model.Item) { // Find current state index currentIndex := -1 currentState := string(item.State) + lastStateIndex := len(stateNames) - 1 // Handle empty state if currentState == "" { @@ -773,13 +811,49 @@ func (m *uiModel) cycleStateBackward(item *model.Item) { } } + // Store the old state to check if we're transitioning to/from DONE + oldState := currentState + var newState string + // Cycle backward if currentIndex <= 0 { - item.State = model.TodoState("") // Empty state + newState = "" // Empty state } else if currentIndex > len(stateNames) { - item.State = model.TodoState(stateNames[len(stateNames)-1]) + newState = stateNames[len(stateNames)-1] } else { - item.State = model.TodoState(stateNames[currentIndex-1]) + newState = stateNames[currentIndex-1] + } + + // Update the item state + item.State = model.TodoState(newState) + + // Manage CLOSED timestamp + wasInDoneState := (oldState == stateNames[lastStateIndex]) + isInDoneState := (newState == stateNames[lastStateIndex]) + + if isInDoneState && !wasInDoneState { + // Moving TO done state - add CLOSED timestamp + now := time.Now() + item.Closed = &now + // Remove any existing CLOSED line from notes + var filteredNotes []string + for _, note := range item.Notes { + if !strings.HasPrefix(strings.TrimSpace(note), "CLOSED:") { + filteredNotes = append(filteredNotes, note) + } + } + item.Notes = filteredNotes + } else if wasInDoneState && !isInDoneState { + // Moving FROM done state - remove CLOSED timestamp + item.Closed = nil + // Remove any existing CLOSED line from notes + var filteredNotes []string + for _, note := range item.Notes { + if !strings.HasPrefix(strings.TrimSpace(note), "CLOSED:") { + filteredNotes = append(filteredNotes, note) + } + } + item.Notes = filteredNotes } } diff --git a/internal/ui/settings.go b/internal/ui/settings.go index 2edf88d..528b7d2 100644 --- a/internal/ui/settings.go +++ b/internal/ui/settings.go @@ -13,7 +13,8 @@ import ( type settingsSection int const ( - settingsSectionTags settingsSection = iota + settingsSectionGeneral settingsSection = iota + settingsSectionTags settingsSectionStates settingsSectionKeybindings ) @@ -76,7 +77,7 @@ func (m *uiModel) updateSettings(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, m.keys.Left): // Previous section - if m.settingsSection > settingsSectionTags { + if m.settingsSection > settingsSectionGeneral { m.settingsSection-- m.settingsCursor = 0 m.settingsScroll = 0 @@ -101,6 +102,8 @@ func (m *uiModel) updateSettings(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, m.keys.Capture): // Add new tag or state switch m.settingsSection { + case settingsSectionGeneral: + // No capture action in General case settingsSectionTags: m.addNewTag() case settingsSectionStates: @@ -128,6 +131,8 @@ func (m *uiModel) updateSettings(msg tea.Msg) (tea.Model, tea.Cmd) { // getSettingsItemCount returns the number of items in the current settings view func (m *uiModel) getSettingsItemCount() int { switch m.settingsSection { + case settingsSectionGeneral: + return 3 // Org syntax highlighting toggle, show indentation guides toggle, indentation guide color case settingsSectionTags: return len(m.config.Tags.Tags) + 1 // +1 for "Add new tag" option case settingsSectionStates: @@ -172,6 +177,40 @@ func (m *uiModel) updateSettingsScrollOffset() { // startSettingsEdit starts editing a settings item func (m *uiModel) startSettingsEdit() { switch m.settingsSection { + case settingsSectionGeneral: + // Setting 0: Toggle org syntax highlighting + if m.settingsCursor == 0 { + m.config.UI.OrgSyntaxHighlighting = !m.config.UI.OrgSyntaxHighlighting + if m.config.UI.OrgSyntaxHighlighting { + m.setStatus("Org syntax highlighting enabled (saved)") + } else { + m.setStatus("Org syntax highlighting disabled (saved)") + } + // Auto-save + if err := m.config.Save(); err != nil { + m.setStatus(fmt.Sprintf("Error auto-saving config: %v", err)) + } + } + // Setting 1: Toggle show indentation guides + if m.settingsCursor == 1 { + m.config.UI.ShowIndentationGuides = !m.config.UI.ShowIndentationGuides + if m.config.UI.ShowIndentationGuides { + m.setStatus("Indentation guides enabled (saved)") + } else { + m.setStatus("Indentation guides disabled (saved)") + } + // Auto-save + if err := m.config.Save(); err != nil { + m.setStatus(fmt.Sprintf("Error auto-saving config: %v", err)) + } + } + // Setting 2: Edit indentation guide color + if m.settingsCursor == 2 { + m.textinput.SetValue(m.config.UI.IndentationGuideColor) + m.textinput.Placeholder = "Enter color (e.g., 245, 99)" + m.textinput.Focus() + } + return case settingsSectionTags: if m.settingsCursor >= len(m.config.Tags.Tags) { return @@ -237,6 +276,23 @@ func (m *uiModel) startSettingsEdit() { // saveSettingsEdit saves the edited value and auto-saves to disk func (m *uiModel) saveSettingsEdit() { switch m.settingsSection { + case settingsSectionGeneral: + // Setting 2: Indentation guide color + if m.settingsCursor == 2 { + newColor := strings.TrimSpace(m.textinput.Value()) + if newColor != "" { + m.config.UI.IndentationGuideColor = newColor + m.setStatus(fmt.Sprintf("Indentation guide color set to '%s' (saved)", newColor)) + // Auto-save + if err := m.config.Save(); err != nil { + m.setStatus(fmt.Sprintf("Error auto-saving config: %v", err)) + } else { + // Reload styles + m.styles = newStyleMapFromConfig(m.config) + } + } + } + return case settingsSectionTags: if m.settingsCursor >= len(m.config.Tags.Tags) { return @@ -350,6 +406,9 @@ func (m *uiModel) saveSettingsEdit() { // deleteSettingsItem deletes the current settings item and auto-saves func (m *uiModel) deleteSettingsItem() { switch m.settingsSection { + case settingsSectionGeneral: + // Cannot delete general settings + return case settingsSectionTags: if m.settingsCursor >= len(m.config.Tags.Tags) { return @@ -437,6 +496,12 @@ func (m *uiModel) viewSettings() string { activeTabStyle := lipgloss.NewStyle().Padding(0, 2).Bold(true).Foreground(lipgloss.Color(m.config.Colors.Title)) tabs := "" + if m.settingsSection == settingsSectionGeneral { + tabs += activeTabStyle.Render("[General]") + } else { + tabs += tabStyle.Render("General") + } + tabs += " " if m.settingsSection == settingsSectionTags { tabs += activeTabStyle.Render("[Tags]") } else { @@ -459,6 +524,8 @@ func (m *uiModel) viewSettings() string { // Instructions var instructions string switch m.settingsSection { + case settingsSectionGeneral: + instructions = "←/→: Switch tabs • ↑/↓: Navigate • Enter: Toggle setting\nctrl+s: Save • q/,: Exit" case settingsSectionTags: instructions = "←/→: Switch tabs • ↑/↓: Navigate • Enter: Edit • D: Delete\nc: Add new tag • ctrl+s: Save • q/,: Exit" case settingsSectionStates: @@ -470,6 +537,8 @@ func (m *uiModel) viewSettings() string { // Render the appropriate section switch m.settingsSection { + case settingsSectionGeneral: + content.WriteString(m.viewSettingsGeneral()) case settingsSectionTags: content.WriteString(m.viewSettingsTags()) case settingsSectionStates: @@ -488,6 +557,68 @@ func (m *uiModel) viewSettings() string { return content.String() } +// viewSettingsGeneral renders the general settings section +func (m *uiModel) viewSettingsGeneral() string { + var content strings.Builder + + // Calculate visible window + reservedLines := 10 + if m.textinput.Focused() { + reservedLines += 3 + } + availableHeight := m.height - reservedLines + if availableHeight < 3 { + availableHeight = 3 + } + + enabledStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("34")).Bold(true) + disabledStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("196")) + colorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(m.config.UI.IndentationGuideColor)) + + // Setting 0: Org syntax highlighting toggle + line := "" + if m.settingsCursor == 0 && !m.textinput.Focused() { + line += "▶ " + } else { + line += " " + } + line += "Org syntax highlighting: " + if m.config.UI.OrgSyntaxHighlighting { + line += enabledStyle.Render("Enabled") + } else { + line += disabledStyle.Render("Disabled") + } + content.WriteString(line + "\n") + + // Setting 1: Show indentation guides toggle + line = "" + if m.settingsCursor == 1 && !m.textinput.Focused() { + line += "▶ " + } else { + line += " " + } + line += "Show indentation guides: " + if m.config.UI.ShowIndentationGuides { + line += enabledStyle.Render("Enabled") + } else { + line += disabledStyle.Render("Disabled") + } + content.WriteString(line + "\n") + + // Setting 2: Indentation guide color + line = "" + if m.settingsCursor == 2 && !m.textinput.Focused() { + line += "▶ " + } else { + line += " " + } + line += "Indentation guide color: " + line += colorStyle.Render(m.config.UI.IndentationGuideColor) + content.WriteString(line + "\n") + + return content.String() +} + // viewSettingsTags renders the tags section func (m *uiModel) viewSettingsTags() string { var content strings.Builder diff --git a/internal/ui/views.go b/internal/ui/views.go index 5827bb4..185ec77 100644 --- a/internal/ui/views.go +++ b/internal/ui/views.go @@ -148,17 +148,24 @@ func (m uiModel) View() string { for i, item := range items { lineCount := 1 // The item itself if !item.Folded && len(item.Notes) > 0 && m.mode == modeList { - // Build subtle visual guides for notes - guideStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("240")) + // Build indentation for notes var notePrefix strings.Builder - for j := 1; j <= item.Level; j++ { - notePrefix.WriteString(guideStyle.Render("· ")) + if m.config.UI.ShowIndentationGuides { + guideStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(m.config.UI.IndentationGuideColor)) + for j := 1; j <= item.Level; j++ { + notePrefix.WriteString(guideStyle.Render("· ")) + } + } else { + // No visual guides, just use spaces + for j := 1; j <= item.Level; j++ { + notePrefix.WriteString(" ") + } } indent := notePrefix.String() noteIndent := indent + " " filteredNotes := filterLogbookDrawer(item.Notes) wrappedNotes := wrapNoteLines(filteredNotes, m.width, noteIndent) - highlightedNotes := renderNotesWithHighlighting(wrappedNotes) + highlightedNotes := m.renderNotesWithHighlighting(wrappedNotes) lineCount += len(highlightedNotes) } itemLineCount[i] = lineCount @@ -215,17 +222,24 @@ func (m uiModel) View() string { // Render remaining notes if !item.Folded && len(item.Notes) > 0 && m.mode == modeList { - // Build subtle visual guides for notes - guideStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("245")) + // Build indentation for notes var notePrefix strings.Builder - for i := 1; i <= item.Level; i++ { - notePrefix.WriteString(guideStyle.Render("· ")) + if m.config.UI.ShowIndentationGuides { + guideStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(m.config.UI.IndentationGuideColor)) + for i := 1; i <= item.Level; i++ { + notePrefix.WriteString(guideStyle.Render("· ")) + } + } else { + // No visual guides, just use spaces + for i := 1; i <= item.Level; i++ { + notePrefix.WriteString(" ") + } } indent := notePrefix.String() noteIndent := indent + " " filteredNotes := filterLogbookDrawer(item.Notes) wrappedNotes := wrapNoteLines(filteredNotes, m.width, noteIndent) - highlightedNotes := renderNotesWithHighlighting(wrappedNotes) + highlightedNotes := m.renderNotesWithHighlighting(wrappedNotes) for noteIdx := linesToSkip - 1; noteIdx < len(highlightedNotes) && itemLines < availableHeight; noteIdx++ { content.WriteString(indent) content.WriteString(" " + highlightedNotes[noteIdx]) @@ -245,17 +259,24 @@ func (m uiModel) View() string { // Show notes if not folded if !item.Folded && len(item.Notes) > 0 && m.mode == modeList { - // Build subtle visual guides for notes - guideStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("235")) + // Build indentation for notes var notePrefix strings.Builder - for i := 1; i <= item.Level; i++ { - notePrefix.WriteString(guideStyle.Render("· ")) + if m.config.UI.ShowIndentationGuides { + guideStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(m.config.UI.IndentationGuideColor)) + for i := 1; i <= item.Level; i++ { + notePrefix.WriteString(guideStyle.Render("· ")) + } + } else { + // No visual guides, just use spaces + for i := 1; i <= item.Level; i++ { + notePrefix.WriteString(" ") + } } indent := notePrefix.String() noteIndent := indent + " " filteredNotes := filterLogbookDrawer(item.Notes) wrappedNotes := wrapNoteLines(filteredNotes, m.width, noteIndent) - highlightedNotes := renderNotesWithHighlighting(wrappedNotes) + highlightedNotes := m.renderNotesWithHighlighting(wrappedNotes) for _, note := range highlightedNotes { if itemLines >= availableHeight { break @@ -626,8 +647,8 @@ func filterLogbookDrawer(notes []string) []string { continue } - // Skip SCHEDULED and DEADLINE lines - if strings.HasPrefix(trimmed, "SCHEDULED:") || strings.HasPrefix(trimmed, "DEADLINE:") { + // Skip SCHEDULED, DEADLINE, and CLOSED lines + if strings.HasPrefix(trimmed, "SCHEDULED:") || strings.HasPrefix(trimmed, "DEADLINE:") || strings.HasPrefix(trimmed, "CLOSED:") { continue } @@ -661,7 +682,7 @@ func wrapNoteLines(notes []string, width int, indent string) []string { } // renderNotesWithHighlighting renders notes with syntax highlighting for code blocks -func renderNotesWithHighlighting(notes []string) []string { +func (m uiModel) renderNotesWithHighlighting(notes []string) []string { if len(notes) == 0 { return notes } @@ -762,7 +783,13 @@ func renderNotesWithHighlighting(notes []string) []string { if inCodeBlock { codeLines = append(codeLines, note) } else { - result = append(result, note) + // Apply org-mode syntax highlighting to non-code text if enabled + if m.config.UI.OrgSyntaxHighlighting { + highlighted := highlightCode(note, "org") + result = append(result, highlighted) + } else { + result = append(result, note) + } } } @@ -894,14 +921,21 @@ func (m uiModel) renderItem(item *model.Item, isCursor bool) string { var b strings.Builder // Indentation with subtle visual nesting guides - guideStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("245")) // Very subtle gray - for i := 1; i < item.Level; i++ { - if i == item.Level-1 { - // Last level before the item - use subtle dot connector - b.WriteString(guideStyle.Render("· ")) - } else { - // Parent levels - use subtle dot - b.WriteString(guideStyle.Render("· ")) + if m.config.UI.ShowIndentationGuides { + guideStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(m.config.UI.IndentationGuideColor)) + for i := 1; i < item.Level; i++ { + if i == item.Level-1 { + // Last level before the item - use subtle dot connector + b.WriteString(guideStyle.Render("· ")) + } else { + // Parent levels - use subtle dot + b.WriteString(guideStyle.Render("· ")) + } + } + } else { + // No visual guides, just use spaces for indentation + for i := 1; i < item.Level; i++ { + b.WriteString(" ") } }