mirror of
https://github.com/RWejlgaard/org.git
synced 2026-05-06 04:34:45 +00:00
fix: org syntax highlighting outside of codeblocks and more settings
This commit is contained in:
parent
6b404cd722
commit
1aaf00fee9
8 changed files with 321 additions and 52 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(" ")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue