hyperlist 1.2.3 → 1.2.5
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/README.md +18 -6
- data/hyperlist +331 -43
- 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: 1bfa281e52d034a301cc645167ff71600f11bbb718b6220ff97ba1df74feed97
|
4
|
+
data.tar.gz: 67ef0888bfad609aea9260b07c538cba47ac219e70aec504f568b7fbea00d6cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f932ad62217a97698953ff73930ce28a54d7abe67510eeadbfb1e84ea2a64421a66d3239e2badde5bd7fd19e920d3c916b378a792e1f87c97549298867c0d17
|
7
|
+
data.tar.gz: 8fe1a8f377ec7775e024030e3119121727935225da24517dd77e72079ca17969cef632ff320259fdb80a6c93d4b044c51493571a42f2b0bd7ff45c6a7cb7c3c4
|
data/README.md
CHANGED
@@ -29,18 +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.2.
|
32
|
+
## What's New in v1.2.4
|
33
33
|
|
34
|
-
###
|
34
|
+
### 🎯 Enhanced Item Movement & Editing
|
35
|
+
- **Smart Item Movement**: `C-UP`/`C-DOWN` now move items past siblings at the same level
|
36
|
+
- **Cursor Tracking**: Cursor follows moved items to their new position
|
37
|
+
- **Auto-renumbering**: Numbered lists automatically renumber when items are moved or deleted
|
38
|
+
- **Outdented Insert**: Press `A` to insert a new item one level less indented
|
39
|
+
- **Quick Save & Quit**: Press `W` as a shortcut for `:wq`
|
40
|
+
- **Configurable Indentation**: Press `I` to cycle between 2-5 spaces per indent level
|
41
|
+
- **Auto-detect Indentation**: Automatically detects indentation from loaded files
|
42
|
+
- **Global Settings**: Indentation preference saved in `~/.hyperlist/config.yml`
|
43
|
+
|
44
|
+
## Previous Updates
|
45
|
+
|
46
|
+
### v1.2.0 - User-Defined Templates
|
35
47
|
- **Save as Template**: Save any HyperList document as a reusable template (`:st`)
|
36
48
|
- **Template Manager**: List and delete your custom templates (`:lt`, `:dt`)
|
37
49
|
- **Enhanced Template Browser**: Shows both built-in and user templates
|
38
50
|
- **Template Metadata**: Includes description and creation date
|
39
51
|
- Templates stored in `~/.hyperlist/templates/` for easy backup and sharing
|
40
52
|
|
41
|
-
|
42
|
-
|
43
|
-
### 🔐 Encryption Support
|
53
|
+
### v1.1.0 - Encryption Support
|
44
54
|
- **File-level encryption** for sensitive files (dot files like `.passwords.hl`)
|
45
55
|
- **Line-level encryption** for individual items (Ctrl-E to toggle)
|
46
56
|
- Secure AES-256-CBC encryption with PBKDF2 key derivation
|
@@ -150,6 +160,8 @@ hyperlist file.txt # Open any text file
|
|
150
160
|
- `o` - Insert line below
|
151
161
|
- `O` - Insert line above
|
152
162
|
- `a` - Insert child item
|
163
|
+
- `A` - Insert outdented item (one level less)
|
164
|
+
- `I` - Cycle indentation size (2-5 spaces)
|
153
165
|
- `D` - Delete and yank line
|
154
166
|
- `C-D` - Delete and yank item with descendants
|
155
167
|
- `y/Y` - Copy line/tree
|
@@ -174,7 +186,7 @@ hyperlist file.txt # Open any text file
|
|
174
186
|
#### File Commands
|
175
187
|
- `:w` - Save
|
176
188
|
- `:q` - Quit
|
177
|
-
- `:wq` - Save and quit
|
189
|
+
- `:wq` or `W` - Save and quit
|
178
190
|
- `:e file` - Open file
|
179
191
|
- `:export html` - Export to HTML
|
180
192
|
- `:export md` - Export to Markdown
|
data/hyperlist
CHANGED
@@ -117,6 +117,11 @@ class HyperListApp
|
|
117
117
|
@active_pane = :main # :main or :split
|
118
118
|
@message_timeout = nil # For timed message display
|
119
119
|
|
120
|
+
# Global configuration
|
121
|
+
@config_file = File.expand_path("~/.hyperlist/config.yml")
|
122
|
+
@indent_size = 2 # Default indentation (2-5 spaces)
|
123
|
+
load_config
|
124
|
+
|
120
125
|
# Terminal setup
|
121
126
|
if IO.console
|
122
127
|
@rows, @cols = IO.console.winsize
|
@@ -219,6 +224,9 @@ class HyperListApp
|
|
219
224
|
lines = content.split("\n")
|
220
225
|
end
|
221
226
|
|
227
|
+
# Auto-detect indentation from the file
|
228
|
+
detect_indentation(lines)
|
229
|
+
|
222
230
|
# Check if file is large
|
223
231
|
large_file = lines.length > 10000
|
224
232
|
|
@@ -230,7 +238,17 @@ class HyperListApp
|
|
230
238
|
# Process lines with optional progress updates for large files
|
231
239
|
lines.each_with_index do |line, idx|
|
232
240
|
next if line.strip.empty?
|
233
|
-
|
241
|
+
|
242
|
+
# Detect level based on leading whitespace
|
243
|
+
if line.start_with?("\t")
|
244
|
+
# Tab-based indentation
|
245
|
+
level = line[/^\t*/].length
|
246
|
+
else
|
247
|
+
# Space-based indentation
|
248
|
+
spaces = line[/^ */].length
|
249
|
+
level = @indent_size > 0 ? spaces / @indent_size : 0
|
250
|
+
end
|
251
|
+
|
234
252
|
text = line.strip
|
235
253
|
|
236
254
|
# Track encrypted lines
|
@@ -411,7 +429,7 @@ class HyperListApp
|
|
411
429
|
|
412
430
|
# Prepare content
|
413
431
|
content = @items.map do |item|
|
414
|
-
|
432
|
+
(' ' * @indent_size) * item["level"] + item["text"]
|
415
433
|
end.join("\n")
|
416
434
|
|
417
435
|
# Check if this should be an encrypted file
|
@@ -535,7 +553,7 @@ class HyperListApp
|
|
535
553
|
lines << "## #{text}" unless text.empty?
|
536
554
|
else
|
537
555
|
# Use spaces for indentation in lists
|
538
|
-
indent = '
|
556
|
+
indent = ' ' * (@indent_size * [level - 2, 0].max)
|
539
557
|
if text.start_with?('- [')
|
540
558
|
lines << indent + text
|
541
559
|
else
|
@@ -585,7 +603,7 @@ class HyperListApp
|
|
585
603
|
@items.each do |item|
|
586
604
|
original_text = item["text"]
|
587
605
|
level = item["level"]
|
588
|
-
indent =
|
606
|
+
indent = ' ' * (@indent_size * level)
|
589
607
|
|
590
608
|
# Process the line
|
591
609
|
# We'll apply formatting BEFORE escaping HTML entities
|
@@ -691,7 +709,7 @@ class HyperListApp
|
|
691
709
|
def export_to_text
|
692
710
|
lines = []
|
693
711
|
@items.each do |item|
|
694
|
-
indent = '
|
712
|
+
indent = ' ' * (@indent_size * item["level"])
|
695
713
|
lines << indent + item["text"]
|
696
714
|
end
|
697
715
|
lines.join("\n")
|
@@ -724,7 +742,7 @@ class HyperListApp
|
|
724
742
|
visible_items.each_with_index do |item, idx|
|
725
743
|
next unless item
|
726
744
|
|
727
|
-
line =
|
745
|
+
line = ' ' * (@indent_size * item["level"])
|
728
746
|
|
729
747
|
# Add fold indicator
|
730
748
|
real_idx = get_real_index(item)
|
@@ -765,13 +783,16 @@ class HyperListApp
|
|
765
783
|
end
|
766
784
|
end
|
767
785
|
|
768
|
-
# Apply current item highlighting
|
786
|
+
# Apply current item highlighting (but not in presentation mode for focused items)
|
769
787
|
if idx == @current
|
770
|
-
|
771
|
-
if
|
772
|
-
|
773
|
-
|
774
|
-
|
788
|
+
# Skip background highlighting in presentation mode for items in focus
|
789
|
+
if !(@presentation_mode && is_item_in_presentation_focus?(item))
|
790
|
+
bg_color = (!@split_view || @active_pane == :main) ? "237" : "234"
|
791
|
+
if bg_color
|
792
|
+
bg_code = "\e[48;5;#{bg_color}m"
|
793
|
+
reset_bg = "\e[49m"
|
794
|
+
line = bg_code + line.gsub(/\e\[49m/, '') + reset_bg
|
795
|
+
end
|
775
796
|
end
|
776
797
|
end
|
777
798
|
|
@@ -1336,6 +1357,44 @@ class HyperListApp
|
|
1336
1357
|
end
|
1337
1358
|
end
|
1338
1359
|
|
1360
|
+
def jump_to_next_sibling
|
1361
|
+
visible = get_visible_items
|
1362
|
+
return if @current >= visible.length - 1
|
1363
|
+
|
1364
|
+
current_level = visible[@current]["level"]
|
1365
|
+
|
1366
|
+
# Search forward for the next item at the same level
|
1367
|
+
(@current + 1...visible.length).each do |i|
|
1368
|
+
if visible[i]["level"] == current_level
|
1369
|
+
@current = i
|
1370
|
+
update_presentation_focus if @presentation_mode
|
1371
|
+
return
|
1372
|
+
elsif visible[i]["level"] < current_level
|
1373
|
+
# We've gone up a level, no more siblings
|
1374
|
+
return
|
1375
|
+
end
|
1376
|
+
end
|
1377
|
+
end
|
1378
|
+
|
1379
|
+
def jump_to_prev_sibling
|
1380
|
+
visible = get_visible_items
|
1381
|
+
return if @current <= 0
|
1382
|
+
|
1383
|
+
current_level = visible[@current]["level"]
|
1384
|
+
|
1385
|
+
# Search backward for the previous item at the same level
|
1386
|
+
(@current - 1).downto(0) do |i|
|
1387
|
+
if visible[i]["level"] == current_level
|
1388
|
+
@current = i
|
1389
|
+
update_presentation_focus if @presentation_mode
|
1390
|
+
return
|
1391
|
+
elsif visible[i]["level"] < current_level
|
1392
|
+
# We've gone up a level, no more siblings before this
|
1393
|
+
return
|
1394
|
+
end
|
1395
|
+
end
|
1396
|
+
end
|
1397
|
+
|
1339
1398
|
def expand_to_level(level)
|
1340
1399
|
@items.each do |item|
|
1341
1400
|
item["fold"] = item["level"] >= level
|
@@ -1426,9 +1485,11 @@ class HyperListApp
|
|
1426
1485
|
item["presentation_focus"] = false
|
1427
1486
|
end
|
1428
1487
|
|
1429
|
-
# Mark current item as in focus and unfold it
|
1430
|
-
|
1431
|
-
|
1488
|
+
# Mark current item as in focus and unfold it in the main items array
|
1489
|
+
if current_real_idx && current_real_idx < @items.length
|
1490
|
+
@items[current_real_idx]["presentation_focus"] = true
|
1491
|
+
@items[current_real_idx]["fold"] = false
|
1492
|
+
end
|
1432
1493
|
|
1433
1494
|
# Unfold all ancestors of current item
|
1434
1495
|
ancestor_indices = []
|
@@ -1554,6 +1615,8 @@ class HyperListApp
|
|
1554
1615
|
insert_line_with_text(@last_action)
|
1555
1616
|
when :insert_child
|
1556
1617
|
insert_child_with_text(@last_action)
|
1618
|
+
when :insert_outdented
|
1619
|
+
insert_outdented_with_text(@last_action)
|
1557
1620
|
when :delete_line
|
1558
1621
|
delete_line
|
1559
1622
|
when :edit_line
|
@@ -1659,6 +1722,21 @@ class HyperListApp
|
|
1659
1722
|
@footer.refresh
|
1660
1723
|
end
|
1661
1724
|
|
1725
|
+
def insert_outdented
|
1726
|
+
@mode = :insert
|
1727
|
+
|
1728
|
+
input = @footer.ask("New outdented item: ", "")
|
1729
|
+
|
1730
|
+
if input && !input.strip.empty?
|
1731
|
+
insert_outdented_with_text(input)
|
1732
|
+
record_last_action(:insert_outdented, input)
|
1733
|
+
end
|
1734
|
+
|
1735
|
+
@mode = :normal
|
1736
|
+
@footer.clear # Clear footer immediately
|
1737
|
+
@footer.refresh
|
1738
|
+
end
|
1739
|
+
|
1662
1740
|
def insert_child_with_text(text)
|
1663
1741
|
return unless text && !text.strip.empty?
|
1664
1742
|
|
@@ -1679,6 +1757,35 @@ class HyperListApp
|
|
1679
1757
|
@current += 1
|
1680
1758
|
end
|
1681
1759
|
|
1760
|
+
def insert_outdented_with_text(text)
|
1761
|
+
return unless text && !text.strip.empty?
|
1762
|
+
|
1763
|
+
save_undo_state # Save state before modification
|
1764
|
+
visible = get_visible_items
|
1765
|
+
if @current < visible.length
|
1766
|
+
current_level = visible[@current]["level"]
|
1767
|
+
# Outdent means one level less, but not less than 0
|
1768
|
+
level = [current_level - 1, 0].max
|
1769
|
+
real_idx = get_real_index(visible[@current])
|
1770
|
+
|
1771
|
+
# Find where to insert: after current item and all its descendants
|
1772
|
+
insert_idx = real_idx + 1
|
1773
|
+
while insert_idx < @items.length && @items[insert_idx]["level"] > current_level
|
1774
|
+
insert_idx += 1
|
1775
|
+
end
|
1776
|
+
|
1777
|
+
@items.insert(insert_idx, {"text" => text, "level" => level, "fold" => false})
|
1778
|
+
|
1779
|
+
# Update cursor to point to the new item
|
1780
|
+
@current = get_visible_items.index { |item| item.equal?(@items[insert_idx]) } || (@current + 1)
|
1781
|
+
else
|
1782
|
+
# If at end of list, add at level 0
|
1783
|
+
@items << {"text" => text, "level" => 0, "fold" => false}
|
1784
|
+
@current = get_visible_items.length - 1
|
1785
|
+
end
|
1786
|
+
@modified = true
|
1787
|
+
end
|
1788
|
+
|
1682
1789
|
def edit_line
|
1683
1790
|
visible = get_visible_items
|
1684
1791
|
return if @current >= visible.length
|
@@ -1756,10 +1863,16 @@ class HyperListApp
|
|
1756
1863
|
end
|
1757
1864
|
|
1758
1865
|
# Delete the items
|
1866
|
+
# Remember the level of the deleted item for renumbering
|
1867
|
+
deleted_level = item["level"]
|
1868
|
+
|
1759
1869
|
delete_count.times { @items.delete_at(real_idx) }
|
1760
1870
|
|
1761
1871
|
@items = [{"text" => "Empty", "level" => 0, "fold" => false}] if @items.empty?
|
1762
1872
|
|
1873
|
+
# Renumber siblings at the deleted item's level
|
1874
|
+
renumber_siblings(deleted_level) unless @items.length == 1
|
1875
|
+
|
1763
1876
|
@current = [@current, get_visible_items.length - 1].min
|
1764
1877
|
@current = 0 if @current < 0
|
1765
1878
|
@modified = true
|
@@ -1863,18 +1976,47 @@ class HyperListApp
|
|
1863
1976
|
end
|
1864
1977
|
end
|
1865
1978
|
|
1979
|
+
# Find the previous sibling at the same level
|
1980
|
+
target_level = item["level"]
|
1981
|
+
target_idx = nil
|
1982
|
+
|
1983
|
+
# Search backwards for a sibling at the same level
|
1984
|
+
(real_idx - 1).downto(0) do |i|
|
1985
|
+
if @items[i]["level"] == target_level
|
1986
|
+
target_idx = i
|
1987
|
+
break
|
1988
|
+
elsif @items[i]["level"] < target_level
|
1989
|
+
# Hit a parent, can't move up
|
1990
|
+
return
|
1991
|
+
end
|
1992
|
+
end
|
1993
|
+
|
1994
|
+
# Can't move if no sibling found
|
1995
|
+
return if target_idx.nil?
|
1996
|
+
|
1997
|
+
# Remember the first item we're moving (to track it)
|
1998
|
+
first_moved_item = items_to_move.first
|
1999
|
+
|
1866
2000
|
# Remove items from their current position
|
1867
2001
|
items_to_move.length.times { @items.delete_at(real_idx) }
|
1868
2002
|
|
1869
|
-
# Insert
|
1870
|
-
target_idx = real_idx - 1
|
2003
|
+
# Insert before the target sibling
|
1871
2004
|
items_to_move.reverse.each do |item_to_move|
|
1872
2005
|
@items.insert(target_idx, item_to_move)
|
1873
2006
|
end
|
1874
2007
|
|
1875
|
-
|
2008
|
+
# Update cursor position to follow the moved item
|
2009
|
+
# The moved item is now at position target_idx in @items
|
2010
|
+
# Find this position in the visible list
|
2011
|
+
new_visible = get_visible_items
|
2012
|
+
new_item_idx = new_visible.find_index { |v| v["_real_index"] == target_idx }
|
2013
|
+
@current = new_item_idx if new_item_idx
|
2014
|
+
|
2015
|
+
# Renumber siblings at the moved item's level
|
2016
|
+
renumber_siblings(first_moved_item["level"])
|
2017
|
+
|
1876
2018
|
@modified = true
|
1877
|
-
@message = "Moved #{items_to_move.length} item(s) up
|
2019
|
+
@message = "Moved #{items_to_move.length} item(s) up"
|
1878
2020
|
record_last_action(:move_item_up, with_children)
|
1879
2021
|
end
|
1880
2022
|
|
@@ -1904,26 +2046,78 @@ class HyperListApp
|
|
1904
2046
|
end
|
1905
2047
|
end
|
1906
2048
|
|
1907
|
-
#
|
1908
|
-
|
2049
|
+
# Find the next sibling at the same level
|
2050
|
+
target_level = item["level"]
|
2051
|
+
target_idx = nil
|
2052
|
+
next_sibling_end = nil
|
2053
|
+
|
2054
|
+
# Search forward for a sibling at the same level
|
2055
|
+
((last_idx + 1)...@items.length).each do |i|
|
2056
|
+
if @items[i]["level"] == target_level
|
2057
|
+
# Found next sibling, now find where it ends (including its children)
|
2058
|
+
next_sibling_end = i
|
2059
|
+
((i + 1)...@items.length).each do |j|
|
2060
|
+
if @items[j]["level"] > target_level
|
2061
|
+
next_sibling_end = j
|
2062
|
+
else
|
2063
|
+
break
|
2064
|
+
end
|
2065
|
+
end
|
2066
|
+
target_idx = next_sibling_end + 1
|
2067
|
+
break
|
2068
|
+
elsif @items[i]["level"] < target_level
|
2069
|
+
# Hit a parent level, can't move down
|
2070
|
+
return
|
2071
|
+
end
|
2072
|
+
end
|
2073
|
+
|
2074
|
+
# Can't move if no sibling found
|
2075
|
+
return if target_idx.nil?
|
2076
|
+
|
2077
|
+
# Remember the first item we're moving (to track it)
|
2078
|
+
first_moved_item = items_to_move.first
|
1909
2079
|
|
1910
2080
|
# Remove items from their current position
|
1911
2081
|
items_to_move.length.times { @items.delete_at(real_idx) }
|
1912
2082
|
|
1913
|
-
#
|
1914
|
-
|
1915
|
-
target_idx = real_idx + 1
|
2083
|
+
# Adjust target index since we removed items
|
2084
|
+
target_idx -= items_to_move.length
|
1916
2085
|
|
2086
|
+
# Insert after the target sibling and its children
|
1917
2087
|
items_to_move.each_with_index do |item_to_move, idx|
|
1918
2088
|
@items.insert(target_idx + idx, item_to_move)
|
1919
2089
|
end
|
1920
2090
|
|
1921
|
-
|
2091
|
+
# Update cursor position to follow the moved item
|
2092
|
+
# The moved item is now at position target_idx in @items
|
2093
|
+
# Find this position in the visible list
|
2094
|
+
new_visible = get_visible_items
|
2095
|
+
new_item_idx = new_visible.find_index { |v| v["_real_index"] == target_idx }
|
2096
|
+
@current = new_item_idx if new_item_idx
|
2097
|
+
|
2098
|
+
# Renumber siblings at the moved item's level
|
2099
|
+
renumber_siblings(first_moved_item["level"])
|
2100
|
+
|
1922
2101
|
@modified = true
|
1923
|
-
@message = "Moved #{items_to_move.length} item(s) down
|
2102
|
+
@message = "Moved #{items_to_move.length} item(s) down"
|
1924
2103
|
record_last_action(:move_item_down, with_children)
|
1925
2104
|
end
|
1926
2105
|
|
2106
|
+
def renumber_siblings(changed_level)
|
2107
|
+
# Renumber items at the specified level that start with numbers
|
2108
|
+
current_number = 1
|
2109
|
+
@items.each do |item|
|
2110
|
+
if item["level"] == changed_level
|
2111
|
+
# Check if item starts with a number pattern like "1." or "1)"
|
2112
|
+
if item["text"] =~ /^\d+[\.\)]\s+(.+)/
|
2113
|
+
# Replace the number with the current sequential number
|
2114
|
+
item["text"] = "#{current_number}. #{$1}"
|
2115
|
+
current_number += 1
|
2116
|
+
end
|
2117
|
+
end
|
2118
|
+
end
|
2119
|
+
end
|
2120
|
+
|
1927
2121
|
def indent_right(with_children = true)
|
1928
2122
|
visible = get_visible_items
|
1929
2123
|
return if @current >= visible.length
|
@@ -2163,11 +2357,13 @@ class HyperListApp
|
|
2163
2357
|
help_lines << help_line("#{"h".fg("10")}", "Go to parent", "#{"l".fg("10")}", "Go to first child")
|
2164
2358
|
help_lines << help_line("#{"PgUp".fg("10")}", "Page up", "#{"PgDn".fg("10")}", "Page down")
|
2165
2359
|
help_lines << help_line("#{"g/Home".fg("10")}", "Go to top", "#{"G/End".fg("10")}", "Go to bottom")
|
2166
|
-
help_lines << help_line("#{"
|
2167
|
-
help_lines << help_line("#{"?".fg("10")}", "This help", "#{"??".fg("10")}", "Full documentation")
|
2360
|
+
help_lines << help_line("#{"R".fg("10")}", "Jump to reference", "#{"F".fg("10")}", "Open file/URL")
|
2168
2361
|
help_lines << help_line("#{"ma".fg("10")}", "Set mark 'a'", "#{"'a".fg("10")}", "Jump to mark 'a'")
|
2169
2362
|
help_lines << help_line("#{"''".fg("10")}", "Jump to prev position", "#{"N".fg("10")}", "Next = template marker")
|
2170
2363
|
help_lines << ""
|
2364
|
+
help_lines << "#{"SEARCH".fg("14")}"
|
2365
|
+
help_lines << help_line("#{"/".fg("10")}", "Search forward", "#{"n".fg("10")}", "Next match")
|
2366
|
+
help_lines << ""
|
2171
2367
|
help_lines << "#{"FOLDING".fg("14")}"
|
2172
2368
|
help_lines << help_line("#{"Space".fg("10")}", "Toggle fold", "#{"za".fg("10")}", "Toggle all folds")
|
2173
2369
|
help_lines << help_line("#{"zo".fg("10")}", "Open fold", "#{"zc".fg("10")}", "Close fold")
|
@@ -2179,6 +2375,8 @@ class HyperListApp
|
|
2179
2375
|
help_lines << "#{"EDITING".fg("14")}"
|
2180
2376
|
help_lines << help_line("#{"i/Enter".fg("10")}", "Edit line", "#{"o".fg("10")}", "Insert line below")
|
2181
2377
|
help_lines << help_line("#{"O".fg("10")}", "Insert line above", "#{"a".fg("10")}", "Insert child")
|
2378
|
+
help_lines << help_line("#{"A".fg("10")}", "Insert outdented", "#{"W".fg("10")}", "Save and quit")
|
2379
|
+
help_lines << help_line("#{"I".fg("10")}", "Cycle indent (2-5)", "", "")
|
2182
2380
|
help_lines << help_line("#{"D".fg("10")}", "Delete+yank line", "#{"C-D".fg("10")}", "Delete+yank item&descendants")
|
2183
2381
|
help_lines << help_line("#{"y".fg("10")}" + "/".fg("10") + "#{"Y".fg("10")}", "Copy line/tree", "#{"p".fg("10")}", "Paste")
|
2184
2382
|
help_lines << help_line("#{"u".fg("10")}", "Undo", "#{".".fg("10")}", "Repeat last action")
|
@@ -2188,14 +2386,12 @@ class HyperListApp
|
|
2188
2386
|
help_lines << help_line("#{"Tab".fg("10")}", "Indent item+kids", "#{"S-Tab".fg("10")}", "Unindent item+kids")
|
2189
2387
|
help_lines << help_line("#{"→".fg("10")}", "Indent item only", "#{"←".fg("10")}", "Unindent item only")
|
2190
2388
|
help_lines << ""
|
2191
|
-
help_lines << "#{"FEATURES".fg("14")}"
|
2389
|
+
help_lines << "#{"SPECIAL FEATURES".fg("14")}"
|
2192
2390
|
help_lines << help_line("#{"v".fg("10")}", "Toggle checkbox", "#{"V".fg("10")}", "Checkbox with date")
|
2193
2391
|
help_lines << help_line("#{"C-E".fg("10")}", "Encrypt/decrypt line", "#{"C-U".fg("10")}", "Toggle State/Trans underline")
|
2194
|
-
help_lines << help_line("#{"
|
2195
|
-
help_lines << help_line("#{"N".fg("10")}", "Next = marker", "#{"P".fg("10")}", "Presentation mode")
|
2196
|
-
help_lines << help_line("#{"t".fg("10")}", "Insert template", "#{":st".fg("10")}", "Save as template")
|
2392
|
+
help_lines << help_line("#{"P".fg("10")}", "Presentation mode", "#{"Tab/S-Tab".fg("10")}", "Next/prev sibling (in P)")
|
2197
2393
|
help_lines << help_line("#{"Ma".fg("10")}", "Record macro 'a'", "#{"@a".fg("10")}", "Play macro 'a'")
|
2198
|
-
help_lines << help_line("#{"
|
2394
|
+
help_lines << help_line("#{"w".fg("10")}", "Switch panes (split view)", "", "")
|
2199
2395
|
help_lines << ""
|
2200
2396
|
help_lines << "#{"FILE OPERATIONS".fg("14")}"
|
2201
2397
|
help_lines << help_line("#{":w".fg("10")}", "Save", "#{":q".fg("10")}", "Quit")
|
@@ -2206,9 +2402,11 @@ class HyperListApp
|
|
2206
2402
|
help_lines << help_line("#{":as N".fg("10")}", "Set interval (secs)", "#{":as".fg("10")}", "Show autosave status")
|
2207
2403
|
help_lines << ""
|
2208
2404
|
help_lines << "#{"TEMPLATES".fg("14")}"
|
2209
|
-
help_lines << help_line("#{"
|
2210
|
-
help_lines << help_line("#{":
|
2405
|
+
help_lines << help_line("#{"t".fg("10")}", "Insert template", "#{":st".fg("10")}", "Save as template")
|
2406
|
+
help_lines << help_line("#{":dt".fg("10")}", "Delete template", "#{":lt".fg("10")}", "List user templates")
|
2211
2407
|
help_lines << ""
|
2408
|
+
help_lines << "#{"HELP & QUIT".fg("14")}"
|
2409
|
+
help_lines << help_line("#{"?".fg("10")}", "This help", "#{"??".fg("10")}", "Full documentation")
|
2212
2410
|
help_lines << help_line("#{"q".fg("10")}", "Quit (asks to save)", "#{"Q".fg("10")}", "Force quit")
|
2213
2411
|
help_lines << ""
|
2214
2412
|
help_lines << "#{"COLOR SCHEME".fg("14")}"
|
@@ -3282,6 +3480,69 @@ class HyperListApp
|
|
3282
3480
|
end
|
3283
3481
|
end
|
3284
3482
|
|
3483
|
+
def detect_indentation(lines)
|
3484
|
+
# Try to auto-detect indentation size from the file
|
3485
|
+
# Find the greatest common divisor of all indentation levels
|
3486
|
+
indents = []
|
3487
|
+
|
3488
|
+
lines.each do |line|
|
3489
|
+
next if line.strip.empty?
|
3490
|
+
|
3491
|
+
# Skip tab-indented lines for space detection
|
3492
|
+
next if line.start_with?("\t")
|
3493
|
+
|
3494
|
+
# Count leading spaces
|
3495
|
+
spaces = line[/^ */].length
|
3496
|
+
indents << spaces if spaces > 0
|
3497
|
+
end
|
3498
|
+
|
3499
|
+
return if indents.empty?
|
3500
|
+
|
3501
|
+
# Find the GCD of all indentation levels
|
3502
|
+
gcd = indents.reduce(indents.first) { |g, n| n.gcd(g) }
|
3503
|
+
|
3504
|
+
# Clamp to reasonable range (2-5)
|
3505
|
+
if gcd >= 2 && gcd <= 5
|
3506
|
+
@indent_size = gcd
|
3507
|
+
elsif gcd > 5
|
3508
|
+
# If GCD is too large, try to find a divisor between 2-5
|
3509
|
+
[5, 4, 3, 2].each do |size|
|
3510
|
+
if gcd % size == 0
|
3511
|
+
@indent_size = size
|
3512
|
+
break
|
3513
|
+
end
|
3514
|
+
end
|
3515
|
+
end
|
3516
|
+
# If we can't detect, keep the current setting
|
3517
|
+
end
|
3518
|
+
|
3519
|
+
def load_config
|
3520
|
+
# Don't load config, let each file's indentation be detected
|
3521
|
+
# Config is only saved when user explicitly changes with 'I'
|
3522
|
+
end
|
3523
|
+
|
3524
|
+
def save_config
|
3525
|
+
require 'yaml'
|
3526
|
+
config_dir = File.dirname(@config_file)
|
3527
|
+
FileUtils.mkdir_p(config_dir) unless Dir.exist?(config_dir)
|
3528
|
+
|
3529
|
+
config = {
|
3530
|
+
'indent_size' => @indent_size
|
3531
|
+
}
|
3532
|
+
|
3533
|
+
File.write(@config_file, config.to_yaml)
|
3534
|
+
rescue => e
|
3535
|
+
@message = "Failed to save config: #{e.message}"
|
3536
|
+
end
|
3537
|
+
|
3538
|
+
def cycle_indent_size
|
3539
|
+
@indent_size = (@indent_size % 5) + 1 # Cycles through 1,2,3,4,5
|
3540
|
+
@indent_size = 2 if @indent_size < 2 # Ensure minimum of 2
|
3541
|
+
save_config
|
3542
|
+
@message = "Indentation set to #{@indent_size} spaces"
|
3543
|
+
@modified = true # Mark as modified since display will change
|
3544
|
+
end
|
3545
|
+
|
3285
3546
|
def ensure_templates_dir
|
3286
3547
|
FileUtils.mkdir_p(@templates_dir) unless File.exist?(@templates_dir)
|
3287
3548
|
end
|
@@ -3839,6 +4100,8 @@ class HyperListApp
|
|
3839
4100
|
insert_line_above
|
3840
4101
|
when "a"
|
3841
4102
|
insert_child
|
4103
|
+
when "A"
|
4104
|
+
insert_outdented
|
3842
4105
|
when "d"
|
3843
4106
|
# Check if it's dd
|
3844
4107
|
if getchr == "d"
|
@@ -4728,6 +4991,21 @@ class HyperListApp
|
|
4728
4991
|
@current += 1
|
4729
4992
|
when "a"
|
4730
4993
|
insert_child
|
4994
|
+
when "A"
|
4995
|
+
insert_outdented
|
4996
|
+
when "W"
|
4997
|
+
# Save and quit (shortcut for :wq)
|
4998
|
+
if @filename
|
4999
|
+
save_file
|
5000
|
+
else
|
5001
|
+
filename_input = @footer.ask("Save as: ", "")
|
5002
|
+
@filename = File.expand_path(filename_input) if filename_input && !filename_input.empty?
|
5003
|
+
save_file if @filename && !@filename.empty?
|
5004
|
+
end
|
5005
|
+
quit if @filename
|
5006
|
+
when "I"
|
5007
|
+
# Cycle through indentation sizes (2-5 spaces)
|
5008
|
+
cycle_indent_size
|
4731
5009
|
when "t"
|
4732
5010
|
show_templates
|
4733
5011
|
when "D" # Delete line (with children)
|
@@ -4753,18 +5031,28 @@ class HyperListApp
|
|
4753
5031
|
when "C-K" # Alternative: Move item and descendants up (for terminals that intercept C-UP)
|
4754
5032
|
move_item_up(true)
|
4755
5033
|
when "TAB"
|
4756
|
-
|
4757
|
-
|
4758
|
-
|
5034
|
+
if @presentation_mode
|
5035
|
+
# In presentation mode, Tab goes to next sibling
|
5036
|
+
jump_to_next_sibling
|
4759
5037
|
else
|
4760
|
-
|
5038
|
+
# Normal mode: Indent with all children
|
5039
|
+
if @split_view && @active_pane == :split
|
5040
|
+
indent_split_right(true)
|
5041
|
+
else
|
5042
|
+
indent_right(true)
|
5043
|
+
end
|
4761
5044
|
end
|
4762
5045
|
when "S-TAB" # Shift-Tab
|
4763
|
-
|
4764
|
-
|
4765
|
-
|
5046
|
+
if @presentation_mode
|
5047
|
+
# In presentation mode, Shift-Tab goes to previous sibling
|
5048
|
+
jump_to_prev_sibling
|
4766
5049
|
else
|
4767
|
-
|
5050
|
+
# Normal mode: Unindent with all children
|
5051
|
+
if @split_view && @active_pane == :split
|
5052
|
+
indent_split_left(true)
|
5053
|
+
else
|
5054
|
+
indent_left(true)
|
5055
|
+
end
|
4768
5056
|
end
|
4769
5057
|
when "u"
|
4770
5058
|
undo
|
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.2.
|
4
|
+
version: 1.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Geir Isene
|
8
8
|
autorequire:
|
9
9
|
bindir: "."
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-08-
|
11
|
+
date: 2025-08-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rcurses
|