hyperlist 1.9.0 → 1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +52 -0
- data/README.md +30 -11
- data/hyperlist +140 -95
- data/hyperlist.gemspec +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bb81e544d2363e852a1e29bb1eef3d2fd891e766b2ade2a3e5c56fdc3073302e
|
|
4
|
+
data.tar.gz: 14f24e55bfb6f3241ef9120cadcc680971617bfa5378893c3523e4eb1cb1105b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 067b8ba96f97768aa5461e2458b7215cf045926875765ac631b450c9a15ed2313ded67f7986163c6fef8c5408dddf0160a10fa7933ffedbe23722822078e9060
|
|
7
|
+
data.tar.gz: c10a7a1e147fadd212445b9d5ee7baadada737f53ccc55aa89db86f95f2cb208edf1d56a373020c4ac51c5a898e5c2198cb1458cccbfea36526dff104cfe61a7
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,58 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the HyperList Ruby TUI will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.9.2] - 2025-12-02
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Presentation mode completely rewritten**
|
|
9
|
+
- Now dynamically folds/unfolds as you navigate (like hyperlist.vim)
|
|
10
|
+
- Entering presentation mode (C-P) now stays on the current item instead of jumping to the first line
|
|
11
|
+
- Moving up/down properly reveals the path to the current item while folding everything else
|
|
12
|
+
- Uses proper item tracking via real indices to handle fold state changes correctly
|
|
13
|
+
|
|
14
|
+
## [1.9.1] - 2025-12-02
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **External file change detection**
|
|
18
|
+
- Automatically detects when the file is modified by other processes (vim, another Claude Code session, etc.)
|
|
19
|
+
- Prompts user to reload with "File changed externally. Reload? (y/n)"
|
|
20
|
+
- Preserves cursor position after reload
|
|
21
|
+
- Option to ignore external changes (updates internal timestamp to avoid repeated prompts)
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- **Quote coloring inside parentheses (comments)**
|
|
25
|
+
- Fixed issue where quotes inside parentheses like `(this is a "comment" in here)` would break the cyan coloring
|
|
26
|
+
- The closing quote would terminate coloring, leaving the rest of the comment white
|
|
27
|
+
- Now the entire parentheses content stays cyan as intended
|
|
28
|
+
|
|
29
|
+
## [1.9.0] - 2025-10-24
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
- **Item Tagging & Batch Operations**
|
|
33
|
+
- Tag items with 't' key for batch operations (auto-advance cursor)
|
|
34
|
+
- Visual feedback: dark blue background for tagged items, lighter blue for tagged+selected
|
|
35
|
+
- Status bar shows [T:N] count of tagged items
|
|
36
|
+
- Clear all tags with 'u' key
|
|
37
|
+
- Regex tagging with 'C-T' - tag all items matching a pattern
|
|
38
|
+
- Batch operations: D/C-D (delete), y/Y (yank), Tab/S-Tab (indent) work on all tagged items
|
|
39
|
+
|
|
40
|
+
- **External Editor Support**
|
|
41
|
+
- Press 'E' to spawn $EDITOR (vim, nano, etc.)
|
|
42
|
+
- Automatic save/reload workflow
|
|
43
|
+
- Proper terminal state management
|
|
44
|
+
- Full screen refresh on return
|
|
45
|
+
|
|
46
|
+
- **Enhanced Paste & Navigation**
|
|
47
|
+
- 'P' to paste above current item (vim-style)
|
|
48
|
+
- Presentation mode moved to 'C-P'
|
|
49
|
+
- Templates moved to 'T' key
|
|
50
|
+
- Undo moved to 'U' key (RTFM-consistent)
|
|
51
|
+
|
|
52
|
+
### Enhanced
|
|
53
|
+
- **Smart Modified Flag**
|
|
54
|
+
- [+] indicator removed when undoing back to original file state
|
|
55
|
+
- Tracks original state to detect true modifications
|
|
56
|
+
|
|
5
57
|
## [1.8.0] - 2025-09-22
|
|
6
58
|
|
|
7
59
|
### Enhanced
|
data/README.md
CHANGED
|
@@ -29,9 +29,28 @@ For historical context and the original VIM implementation, see: [hyperlist.vim]
|
|
|
29
29
|
### Help Screen
|
|
30
30
|

|
|
31
31
|
|
|
32
|
-
## What's New in v1.9.
|
|
32
|
+
## What's New in v1.9.2
|
|
33
33
|
|
|
34
|
-
###
|
|
34
|
+
### Presentation Mode Fixed
|
|
35
|
+
- **Dynamic folding**: Presentation mode now works like hyperlist.vim - folds/unfolds dynamically as you navigate
|
|
36
|
+
- **Cursor preservation**: Entering presentation mode (C-P) stays on the current item instead of jumping to the first line
|
|
37
|
+
- **Proper navigation**: Moving up/down reveals the path to the current item while folding everything else
|
|
38
|
+
|
|
39
|
+
## Previous Release: v1.9.1
|
|
40
|
+
|
|
41
|
+
### External File Change Detection
|
|
42
|
+
- **Auto-detect external changes**: HyperList now monitors when the file is modified by other processes (vim, another Claude Code session, etc.)
|
|
43
|
+
- **Reload prompt**: Shows "File changed externally. Reload? (y/n)" when changes are detected
|
|
44
|
+
- **Cursor preservation**: Your cursor position is preserved after reload
|
|
45
|
+
- **Skip option**: Press 'n' to ignore external changes and continue editing
|
|
46
|
+
|
|
47
|
+
### Bug Fix: Quote Coloring in Comments
|
|
48
|
+
- Fixed an issue where quotes inside parentheses (comments) like `(this is a "comment" in here)` would break the cyan coloring
|
|
49
|
+
- The entire parentheses content now stays cyan as intended
|
|
50
|
+
|
|
51
|
+
## Previous Release: v1.9.0
|
|
52
|
+
|
|
53
|
+
### Item Tagging & Batch Operations
|
|
35
54
|
- **Tag items**: Press 't' to tag/untag items for batch operations
|
|
36
55
|
- **Auto-advance**: Cursor automatically moves to next item after tagging for fast consecutive tagging
|
|
37
56
|
- **Visual feedback**: Tagged items show dark blue background, lighter blue when selected
|
|
@@ -41,47 +60,47 @@ For historical context and the original VIM implementation, see: [hyperlist.vim]
|
|
|
41
60
|
- **Batch operations**: Delete (D/C-D), yank (y/Y), and indent (Tab/S-Tab) work on all tagged items
|
|
42
61
|
- Tag consecutive or non-consecutive items, then perform operations on the entire set
|
|
43
62
|
|
|
44
|
-
###
|
|
63
|
+
### External Editor Support
|
|
45
64
|
- **Edit in $EDITOR**: Press 'E' to spawn your preferred editor (vim, nano, emacs, etc.)
|
|
46
65
|
- **Seamless workflow**: File saved automatically, editor launched, changes reloaded on exit
|
|
47
66
|
- **Terminal management**: Terminal state properly saved and restored
|
|
48
67
|
- Uses `$EDITOR` environment variable (defaults to vi if not set)
|
|
49
68
|
|
|
50
|
-
###
|
|
69
|
+
### Enhanced Paste & Navigation
|
|
51
70
|
- **Paste above**: Press 'P' to paste above current item (vim-style)
|
|
52
71
|
- **Paste below**: Press 'p' to paste below current item (existing)
|
|
53
72
|
- **Presentation mode**: Moved to 'C-P' (was 'P') for consistency
|
|
54
73
|
- **Templates**: Moved to 'T' key (was 't')
|
|
55
74
|
- **Undo**: Moved to 'U' key (was 'u') - consistent with RTFM
|
|
56
75
|
|
|
57
|
-
###
|
|
76
|
+
### Smart Modified Flag
|
|
58
77
|
- **Intelligent tracking**: `[+]` indicator automatically removed when undoing back to original file state
|
|
59
78
|
- **Clean status**: No false "modified" indicator after complete undo to original
|
|
60
79
|
|
|
61
80
|
## Previous Release: v1.8.0
|
|
62
81
|
|
|
63
|
-
###
|
|
82
|
+
### Multi-Line Paste Support
|
|
64
83
|
- **Paste multiple lines**: When pasting multi-line content into item insertion prompts ('o', 'O', 'a', 'A'), each line becomes a separate item
|
|
65
84
|
- **Visual feedback**: Shows `[+N lines]` indicator during multi-line paste
|
|
66
85
|
- **Smart insertion**: All pasted lines inserted as siblings at the same level
|
|
67
86
|
- Great for importing bullet lists from PDFs, emails, or other documents
|
|
68
87
|
- Requires rcurses 6.1.5+
|
|
69
88
|
|
|
70
|
-
###
|
|
89
|
+
### PDF/LaTeX Export
|
|
71
90
|
- **Export to PDF**: `:export pdf filename.pdf` - Full LaTeX-based PDF generation
|
|
72
91
|
- **Export to LaTeX**: `:export latex filename.tex` - Get the LaTeX source
|
|
73
92
|
- **Professional output**: Color-coded elements, table of contents, headers
|
|
74
93
|
- **Complete HyperList support**: All syntax elements rendered beautifully
|
|
75
94
|
- Requires: texlive-latex-base and texlive-latex-extra packages
|
|
76
95
|
|
|
77
|
-
###
|
|
96
|
+
### System Clipboard Integration
|
|
78
97
|
- **Yank to clipboard**: 'y' and 'Y' now copy to system clipboard
|
|
79
98
|
- **Middle-click paste**: Yanked items can be pasted into other terminals
|
|
80
99
|
- **Preserves indentation**: Copied text maintains proper structure
|
|
81
100
|
|
|
82
101
|
## Previous Version Features (v1.4.0)
|
|
83
102
|
|
|
84
|
-
###
|
|
103
|
+
### Configuration Lines & Theming
|
|
85
104
|
- **Configuration Lines**: Add settings at the bottom of HyperList files using `((option=value, option2=value))`
|
|
86
105
|
- **Theme Support**: Three color themes - `light` (bright colors), `normal` (standard), `dark` (for light terminals)
|
|
87
106
|
- **Line Wrapping**: Enable with `wrap=yes` - wrapped lines use `+` prefix per HyperList spec
|
|
@@ -142,13 +161,13 @@ All `:set` commands automatically update the file's configuration line.
|
|
|
142
161
|
- Secure AES-256-CBC encryption with PBKDF2 key derivation
|
|
143
162
|
- Password caching for the session
|
|
144
163
|
|
|
145
|
-
###
|
|
164
|
+
### Enhanced Presentation Mode
|
|
146
165
|
- **Auto-collapse** everything outside the current context
|
|
147
166
|
- **Smart focus**: Shows only current item, ancestors, and immediate children
|
|
148
167
|
- **Visual hierarchy**: Focused items in full color, others greyed out
|
|
149
168
|
- Improved navigation with proper cursor tracking
|
|
150
169
|
|
|
151
|
-
###
|
|
170
|
+
### Better Visual Experience
|
|
152
171
|
- **Improved highlighting**: Dark gray background preserves syntax colors
|
|
153
172
|
- **Subtle selection**: No more harsh reverse video
|
|
154
173
|
- **Preserved colors**: All HyperList elements maintain their colors when selected
|
data/hyperlist
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# Check for help/version BEFORE loading any libraries
|
|
8
8
|
if ARGV[0] == '-h' || ARGV[0] == '--help'
|
|
9
9
|
puts <<~HELP
|
|
10
|
-
HyperList v1.9.
|
|
10
|
+
HyperList v1.9.2 - Terminal User Interface for HyperList files
|
|
11
11
|
|
|
12
12
|
USAGE
|
|
13
13
|
hyperlist [OPTIONS] [FILE]
|
|
@@ -52,7 +52,7 @@ if ARGV[0] == '-h' || ARGV[0] == '--help'
|
|
|
52
52
|
HELP
|
|
53
53
|
exit 0
|
|
54
54
|
elsif ARGV[0] == '-v' || ARGV[0] == '--version'
|
|
55
|
-
puts "HyperList v1.9.
|
|
55
|
+
puts "HyperList v1.9.2"
|
|
56
56
|
exit 0
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -73,7 +73,7 @@ class HyperListApp
|
|
|
73
73
|
include Rcurses::Input
|
|
74
74
|
include Rcurses::Cursor
|
|
75
75
|
|
|
76
|
-
VERSION = "1.9.
|
|
76
|
+
VERSION = "1.9.2"
|
|
77
77
|
|
|
78
78
|
def initialize(filename = nil)
|
|
79
79
|
@filename = filename ? File.expand_path(filename) : nil
|
|
@@ -124,7 +124,8 @@ class HyperListApp
|
|
|
124
124
|
@split_offset = 0 # Second view scroll offset
|
|
125
125
|
@active_pane = :main # :main or :split
|
|
126
126
|
@message_timeout = nil # For timed message display
|
|
127
|
-
|
|
127
|
+
@file_mtime = nil # Track file modification time for external change detection
|
|
128
|
+
|
|
128
129
|
# Global configuration
|
|
129
130
|
@config_file = File.expand_path("~/.hyperlist/config.yml")
|
|
130
131
|
@indent_size = 2 # Default indentation (2-5 spaces)
|
|
@@ -228,6 +229,9 @@ class HyperListApp
|
|
|
228
229
|
@config_line = nil # Reset config line before loading
|
|
229
230
|
@tagged_items = [] # Clear tags when loading new file
|
|
230
231
|
|
|
232
|
+
# Track file modification time for external change detection
|
|
233
|
+
@file_mtime = File.mtime(file) rescue nil
|
|
234
|
+
|
|
231
235
|
# Read file content
|
|
232
236
|
content = File.read(file) rescue ""
|
|
233
237
|
|
|
@@ -527,12 +531,48 @@ class HyperListApp
|
|
|
527
531
|
def load_command_history
|
|
528
532
|
history_file = File.expand_path("~/.hyperlist_command_history")
|
|
529
533
|
return [] unless File.exist?(history_file)
|
|
530
|
-
|
|
534
|
+
|
|
531
535
|
File.readlines(history_file).map(&:strip).reject(&:empty?).last(100)
|
|
532
536
|
rescue
|
|
533
537
|
[]
|
|
534
538
|
end
|
|
535
|
-
|
|
539
|
+
|
|
540
|
+
# Check if file was modified externally and prompt for reload
|
|
541
|
+
def check_file_changed
|
|
542
|
+
return unless @filename && File.exist?(@filename) && @file_mtime
|
|
543
|
+
|
|
544
|
+
current_mtime = File.mtime(@filename) rescue nil
|
|
545
|
+
return unless current_mtime && current_mtime > @file_mtime
|
|
546
|
+
|
|
547
|
+
# File changed externally - prompt user
|
|
548
|
+
@footer.text = "File changed externally. Reload? (y/n): "
|
|
549
|
+
@footer.refresh
|
|
550
|
+
|
|
551
|
+
response = getchr
|
|
552
|
+
if response&.downcase == 'y'
|
|
553
|
+
# Save cursor position
|
|
554
|
+
saved_current = @current
|
|
555
|
+
saved_offset = @offset
|
|
556
|
+
|
|
557
|
+
load_file(@filename)
|
|
558
|
+
|
|
559
|
+
# Restore cursor position (clamped to new file size)
|
|
560
|
+
visible = get_visible_items
|
|
561
|
+
@current = [saved_current, visible.length - 1, 0].max
|
|
562
|
+
@current = [@current, visible.length - 1].min if visible.length > 0
|
|
563
|
+
@offset = saved_offset
|
|
564
|
+
|
|
565
|
+
@message = "File reloaded"
|
|
566
|
+
@modified = false
|
|
567
|
+
clear_cache
|
|
568
|
+
render
|
|
569
|
+
else
|
|
570
|
+
# Update mtime to avoid repeated prompts
|
|
571
|
+
@file_mtime = current_mtime
|
|
572
|
+
@message = "External changes ignored"
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
|
|
536
576
|
def save_command_history
|
|
537
577
|
history_file = File.expand_path("~/.hyperlist_command_history")
|
|
538
578
|
File.open(history_file, 'w') do |f|
|
|
@@ -712,6 +752,7 @@ class HyperListApp
|
|
|
712
752
|
@modified = false
|
|
713
753
|
@original_items = @items.map { |item| item.dup } # Save state after save
|
|
714
754
|
@last_auto_save = Time.now if @auto_save_enabled
|
|
755
|
+
@file_mtime = File.mtime(@filename) rescue nil # Update mtime after save
|
|
715
756
|
end
|
|
716
757
|
|
|
717
758
|
def check_auto_save
|
|
@@ -1606,9 +1647,10 @@ class HyperListApp
|
|
|
1606
1647
|
|
|
1607
1648
|
# Handle parentheses content (moved here to avoid conflicts with properties)
|
|
1608
1649
|
# Based on hyperlist.vim: '(.\{-})'
|
|
1650
|
+
# Color entire parentheses content as cyan - quotes inside are also cyan
|
|
1609
1651
|
result = safe_regex_replace(result, /\(([^)]*)\)/) do |match|
|
|
1610
|
-
|
|
1611
|
-
|
|
1652
|
+
# Just color the whole thing cyan - no special handling for quotes inside
|
|
1653
|
+
match.fg(colors["cyan"])
|
|
1612
1654
|
end
|
|
1613
1655
|
|
|
1614
1656
|
# Handle semicolons as separators (they separate items on the same line)
|
|
@@ -1624,11 +1666,17 @@ class HyperListApp
|
|
|
1624
1666
|
|
|
1625
1667
|
# Handle quoted strings (only double quotes are special in HyperList)
|
|
1626
1668
|
# Based on hyperlist.vim: '".\{-}"'
|
|
1627
|
-
|
|
1628
|
-
|
|
1669
|
+
# Use safe_regex_replace to avoid processing quotes inside already-colored parentheses
|
|
1670
|
+
result = safe_regex_replace(result, /"([^"]*)"/) do |match|
|
|
1671
|
+
content = match[1..-2] # Extract content between quotes
|
|
1629
1672
|
# Color any ## sequences inside the quotes as red
|
|
1630
|
-
content.gsub
|
|
1631
|
-
|
|
1673
|
+
colored_content = content.gsub(/(##[<>-]+)/) { $1.fg(colors["red"]) }
|
|
1674
|
+
# If no ## sequences were found, just color the whole thing cyan
|
|
1675
|
+
if colored_content == content
|
|
1676
|
+
match.fg(colors["cyan"]) # Color entire quoted string cyan
|
|
1677
|
+
else
|
|
1678
|
+
'"'.fg(colors["cyan"]) + colored_content + '"'.fg(colors["cyan"])
|
|
1679
|
+
end
|
|
1632
1680
|
end
|
|
1633
1681
|
|
|
1634
1682
|
# Handle change markup - all double-hashes should be red
|
|
@@ -1995,28 +2043,28 @@ class HyperListApp
|
|
|
1995
2043
|
|
|
1996
2044
|
def move_up
|
|
1997
2045
|
if @presentation_mode
|
|
1998
|
-
# In presentation mode
|
|
1999
|
-
|
|
2000
|
-
|
|
2046
|
+
# In presentation mode: move to target, then update folds
|
|
2047
|
+
visible = get_visible_items
|
|
2048
|
+
return if visible.empty?
|
|
2049
|
+
|
|
2050
|
+
# Calculate target position
|
|
2001
2051
|
if @current == 0
|
|
2002
|
-
|
|
2003
|
-
target_index = visible_before.length - 1
|
|
2052
|
+
target_index = visible.length - 1
|
|
2004
2053
|
else
|
|
2005
2054
|
target_index = @current - 1
|
|
2006
2055
|
end
|
|
2007
|
-
|
|
2008
|
-
# Get the
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
# Update presentation focus for the target item
|
|
2056
|
+
|
|
2057
|
+
# Get the real index of the target item
|
|
2058
|
+
target_real_idx = get_real_index(visible[target_index])
|
|
2059
|
+
|
|
2060
|
+
# Temporarily set current to target so update_presentation_focus works on it
|
|
2013
2061
|
@current = target_index
|
|
2014
2062
|
update_presentation_focus
|
|
2015
|
-
|
|
2016
|
-
#
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
if
|
|
2063
|
+
|
|
2064
|
+
# After fold changes, find where the target item is now
|
|
2065
|
+
new_visible = get_visible_items
|
|
2066
|
+
new_visible.each_with_index do |item, idx|
|
|
2067
|
+
if item["_real_index"] == target_real_idx
|
|
2020
2068
|
@current = idx
|
|
2021
2069
|
break
|
|
2022
2070
|
end
|
|
@@ -2024,9 +2072,8 @@ class HyperListApp
|
|
|
2024
2072
|
else
|
|
2025
2073
|
# Normal mode navigation
|
|
2026
2074
|
max_items = get_visible_items.length - 1
|
|
2027
|
-
|
|
2075
|
+
|
|
2028
2076
|
if @current == 0
|
|
2029
|
-
# Wrap around to last item
|
|
2030
2077
|
@current = max_items
|
|
2031
2078
|
else
|
|
2032
2079
|
@current = [@current - 1, 0].max
|
|
@@ -2036,29 +2083,29 @@ class HyperListApp
|
|
|
2036
2083
|
|
|
2037
2084
|
def move_down
|
|
2038
2085
|
if @presentation_mode
|
|
2039
|
-
# In presentation mode
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2086
|
+
# In presentation mode: move to target, then update folds
|
|
2087
|
+
visible = get_visible_items
|
|
2088
|
+
return if visible.empty?
|
|
2089
|
+
max = visible.length - 1
|
|
2090
|
+
|
|
2091
|
+
# Calculate target position
|
|
2092
|
+
if @current >= max
|
|
2045
2093
|
target_index = 0
|
|
2046
2094
|
else
|
|
2047
2095
|
target_index = @current + 1
|
|
2048
2096
|
end
|
|
2049
|
-
|
|
2050
|
-
# Get the
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
# Update presentation focus for the target item
|
|
2097
|
+
|
|
2098
|
+
# Get the real index of the target item
|
|
2099
|
+
target_real_idx = get_real_index(visible[target_index])
|
|
2100
|
+
|
|
2101
|
+
# Temporarily set current to target so update_presentation_focus works on it
|
|
2055
2102
|
@current = target_index
|
|
2056
2103
|
update_presentation_focus
|
|
2057
|
-
|
|
2058
|
-
#
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
if
|
|
2104
|
+
|
|
2105
|
+
# After fold changes, find where the target item is now
|
|
2106
|
+
new_visible = get_visible_items
|
|
2107
|
+
new_visible.each_with_index do |item, idx|
|
|
2108
|
+
if item["_real_index"] == target_real_idx
|
|
2062
2109
|
@current = idx
|
|
2063
2110
|
break
|
|
2064
2111
|
end
|
|
@@ -2066,9 +2113,8 @@ class HyperListApp
|
|
|
2066
2113
|
else
|
|
2067
2114
|
# Normal mode navigation
|
|
2068
2115
|
max = get_visible_items.length - 1
|
|
2069
|
-
|
|
2070
|
-
if @current
|
|
2071
|
-
# Wrap around to first item
|
|
2116
|
+
|
|
2117
|
+
if @current >= max
|
|
2072
2118
|
@current = 0
|
|
2073
2119
|
else
|
|
2074
2120
|
@current = [@current + 1, max].min
|
|
@@ -2297,84 +2343,80 @@ class HyperListApp
|
|
|
2297
2343
|
# Enter presentation mode
|
|
2298
2344
|
# Remember which item we're on before any folding changes
|
|
2299
2345
|
visible_items = get_visible_items
|
|
2300
|
-
if @current
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2346
|
+
return if visible_items.empty? || @current >= visible_items.length
|
|
2347
|
+
|
|
2348
|
+
# Get the real index of the current item
|
|
2349
|
+
target_real_idx = visible_items[@current]["_real_index"]
|
|
2350
|
+
|
|
2304
2351
|
@presentation_mode = true
|
|
2305
|
-
# Clear cache to force re-rendering
|
|
2352
|
+
# Clear cache to force re-rendering
|
|
2306
2353
|
@processed_cache.clear
|
|
2307
2354
|
update_presentation_focus
|
|
2308
|
-
|
|
2309
|
-
#
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2355
|
+
|
|
2356
|
+
# Find where the target item is after fold changes
|
|
2357
|
+
new_visible = get_visible_items
|
|
2358
|
+
new_visible.each_with_index do |item, idx|
|
|
2359
|
+
if item["_real_index"] == target_real_idx
|
|
2360
|
+
@current = idx
|
|
2361
|
+
break
|
|
2362
|
+
end
|
|
2314
2363
|
end
|
|
2315
|
-
|
|
2316
|
-
@message = "Presentation mode enabled
|
|
2364
|
+
|
|
2365
|
+
@message = "Presentation mode enabled"
|
|
2317
2366
|
end
|
|
2318
2367
|
end
|
|
2319
2368
|
|
|
2320
2369
|
def update_presentation_focus
|
|
2321
|
-
#
|
|
2322
|
-
#
|
|
2370
|
+
# Presentation mode: fold everything, then reveal path to current item
|
|
2371
|
+
# Like vim's: fold to level 0, then zv (reveal current line)
|
|
2323
2372
|
return unless @presentation_mode
|
|
2324
|
-
|
|
2373
|
+
|
|
2325
2374
|
visible_items = get_visible_items
|
|
2326
2375
|
return if visible_items.empty? || @current >= visible_items.length
|
|
2327
|
-
|
|
2328
|
-
#
|
|
2376
|
+
|
|
2377
|
+
# Get the current item before we change anything
|
|
2329
2378
|
current_item = visible_items[@current]
|
|
2330
|
-
current_level = current_item["level"]
|
|
2331
2379
|
current_real_idx = get_real_index(current_item)
|
|
2332
|
-
|
|
2333
|
-
|
|
2380
|
+
return unless current_real_idx
|
|
2381
|
+
|
|
2382
|
+
current_level = current_item["level"]
|
|
2383
|
+
|
|
2384
|
+
# Step 1: Fold everything that has children
|
|
2334
2385
|
@items.each_with_index do |item, idx|
|
|
2335
2386
|
item["fold"] = has_children?(idx, @items)
|
|
2336
2387
|
item["presentation_focus"] = false
|
|
2337
2388
|
end
|
|
2338
|
-
|
|
2339
|
-
#
|
|
2340
|
-
if current_real_idx && current_real_idx < @items.length
|
|
2341
|
-
@items[current_real_idx]["presentation_focus"] = true
|
|
2342
|
-
@items[current_real_idx]["fold"] = false
|
|
2343
|
-
end
|
|
2344
|
-
|
|
2345
|
-
# Unfold all ancestors of current item
|
|
2346
|
-
ancestor_indices = []
|
|
2389
|
+
|
|
2390
|
+
# Step 2: Unfold all ancestors (path from root to current item)
|
|
2347
2391
|
search_level = current_level - 1
|
|
2348
2392
|
idx = current_real_idx - 1
|
|
2349
|
-
|
|
2350
2393
|
while idx >= 0 && search_level >= 0
|
|
2351
2394
|
if @items[idx]["level"] == search_level
|
|
2352
|
-
@items[idx]["fold"] = false
|
|
2353
|
-
@items[idx]["presentation_focus"] = false # Ancestors visible but not focused
|
|
2354
|
-
ancestor_indices << idx
|
|
2395
|
+
@items[idx]["fold"] = false # Unfold this ancestor
|
|
2355
2396
|
search_level -= 1
|
|
2356
2397
|
end
|
|
2357
2398
|
idx -= 1
|
|
2358
2399
|
end
|
|
2359
|
-
|
|
2360
|
-
#
|
|
2400
|
+
|
|
2401
|
+
# Step 3: Unfold the current item to show its direct children
|
|
2402
|
+
@items[current_real_idx]["fold"] = false
|
|
2403
|
+
@items[current_real_idx]["presentation_focus"] = true
|
|
2404
|
+
|
|
2405
|
+
# Step 4: Mark direct children as in focus (for visual highlighting)
|
|
2361
2406
|
idx = current_real_idx + 1
|
|
2362
2407
|
while idx < @items.length && @items[idx]["level"] > current_level
|
|
2363
2408
|
if @items[idx]["level"] == current_level + 1
|
|
2364
2409
|
@items[idx]["presentation_focus"] = true
|
|
2365
|
-
# Don't unfold children - let them stay folded unless user explicitly unfolds
|
|
2366
2410
|
end
|
|
2367
2411
|
idx += 1
|
|
2368
2412
|
end
|
|
2369
|
-
|
|
2370
|
-
#
|
|
2413
|
+
|
|
2414
|
+
# Step 5: Recalculate cursor position after fold changes
|
|
2371
2415
|
new_visible_items = get_visible_items
|
|
2372
2416
|
new_position = new_visible_items.index(current_item)
|
|
2373
|
-
if new_position
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
# Clear cache to force re-rendering with new focus
|
|
2417
|
+
@current = new_position if new_position
|
|
2418
|
+
|
|
2419
|
+
# Clear cache to force re-rendering
|
|
2378
2420
|
@processed_cache.clear
|
|
2379
2421
|
end
|
|
2380
2422
|
|
|
@@ -6397,7 +6439,10 @@ class HyperListApp
|
|
|
6397
6439
|
loop do
|
|
6398
6440
|
# Check for auto-save
|
|
6399
6441
|
check_auto_save if @auto_save_enabled
|
|
6400
|
-
|
|
6442
|
+
|
|
6443
|
+
# Check for external file changes
|
|
6444
|
+
check_file_changed
|
|
6445
|
+
|
|
6401
6446
|
c = getchr
|
|
6402
6447
|
|
|
6403
6448
|
# Skip nil input (shouldn't happen normally)
|
data/hyperlist.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hyperlist
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.9.
|
|
4
|
+
version: 1.9.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Geir Isene
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: "."
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-12-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rcurses
|