hyperlist 1.2.6 → 1.4.1
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 +81 -0
- data/README.md +71 -11
- data/hyperlist +586 -92
- data/hyperlist.gemspec +1 -1
- data/sample.hl +84 -82
- metadata +2 -2
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.
|
10
|
+
HyperList v1.4.1 - 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.
|
55
|
+
puts "HyperList v1.4.0"
|
56
56
|
exit 0
|
57
57
|
end
|
58
58
|
|
@@ -72,7 +72,7 @@ class HyperListApp
|
|
72
72
|
include Rcurses::Input
|
73
73
|
include Rcurses::Cursor
|
74
74
|
|
75
|
-
VERSION = "1.
|
75
|
+
VERSION = "1.4.1"
|
76
76
|
|
77
77
|
def initialize(filename = nil)
|
78
78
|
@filename = filename ? File.expand_path(filename) : nil
|
@@ -85,6 +85,11 @@ class HyperListApp
|
|
85
85
|
@search = ""
|
86
86
|
@search_matches = [] # Track search match positions
|
87
87
|
@fold_level = 99
|
88
|
+
@config_line = nil # Store config line from file
|
89
|
+
@theme = "normal" # Default theme
|
90
|
+
@wrap = false # Line wrapping disabled by default
|
91
|
+
@show_numbers = false # Line numbers disabled by default
|
92
|
+
@command_history = load_command_history # Command history for : commands
|
88
93
|
@clipboard = nil
|
89
94
|
@undo_stack = []
|
90
95
|
@undo_position = [] # Stack of cursor positions for undo
|
@@ -192,6 +197,7 @@ class HyperListApp
|
|
192
197
|
def load_file(file)
|
193
198
|
@items = []
|
194
199
|
@encrypted_lines = {}
|
200
|
+
@config_line = nil # Reset config line before loading
|
195
201
|
|
196
202
|
# Read file content
|
197
203
|
content = File.read(file) rescue ""
|
@@ -202,6 +208,9 @@ class HyperListApp
|
|
202
208
|
@redo_stack = []
|
203
209
|
@redo_position = []
|
204
210
|
|
211
|
+
# Parse config line if present (before processing content)
|
212
|
+
parse_config_line(content)
|
213
|
+
|
205
214
|
# Check if file is encrypted (dot file or encrypted content)
|
206
215
|
is_encrypted = false
|
207
216
|
if is_encrypted_file?(file)
|
@@ -239,6 +248,11 @@ class HyperListApp
|
|
239
248
|
lines.each_with_index do |line, idx|
|
240
249
|
next if line.strip.empty?
|
241
250
|
|
251
|
+
# Skip config lines (don't add them to items)
|
252
|
+
if line.strip =~ /^\(\(.+\)\)$/
|
253
|
+
next
|
254
|
+
end
|
255
|
+
|
242
256
|
# Detect level based on leading whitespace
|
243
257
|
if line.start_with?("\t")
|
244
258
|
# Tab-based indentation
|
@@ -285,6 +299,12 @@ class HyperListApp
|
|
285
299
|
@message = "Large file loaded. Deep levels auto-folded for performance."
|
286
300
|
end
|
287
301
|
|
302
|
+
# Apply configured fold level if set (after items are loaded)
|
303
|
+
if @fold_level != 99 && !is_encrypted # Don't override encrypted file folding
|
304
|
+
apply_fold_level(@fold_level)
|
305
|
+
@message = "Applied fold level: #{@fold_level}" if @message.nil? || @message.empty?
|
306
|
+
end
|
307
|
+
|
288
308
|
# Update recent files list
|
289
309
|
add_to_recent_files(File.expand_path(file)) if file
|
290
310
|
end
|
@@ -297,6 +317,128 @@ class HyperListApp
|
|
297
317
|
end
|
298
318
|
end
|
299
319
|
|
320
|
+
def apply_fold_level(level)
|
321
|
+
# Apply fold level: 0 = all folded, 99 = all open
|
322
|
+
# Show items up to and including the specified level
|
323
|
+
# Fold items at levels greater than specified level
|
324
|
+
@items.each_with_index do |item, idx|
|
325
|
+
if has_children?(idx, @items)
|
326
|
+
# Fold if item level is greater than or equal to the fold level
|
327
|
+
# This means: fold_level=1 shows level 0 expanded, level 1 and deeper folded
|
328
|
+
# fold_level=2 shows levels 0-1 expanded, level 2 and deeper folded
|
329
|
+
item["fold"] = item["level"] >= level
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# Special cases
|
334
|
+
if level == 0
|
335
|
+
# Fold everything that has children
|
336
|
+
@items.each_with_index do |item, idx|
|
337
|
+
item["fold"] = true if has_children?(idx, @items)
|
338
|
+
end
|
339
|
+
elsif level >= 99
|
340
|
+
# Unfold everything
|
341
|
+
@items.each { |item| item["fold"] = false }
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def parse_config_line(content)
|
346
|
+
# Look for config line at the bottom of the file
|
347
|
+
# Format: ((option1=value, option2=value))
|
348
|
+
lines = content.split("\n")
|
349
|
+
|
350
|
+
# Check last 10 lines for config (in case there are empty lines or other content)
|
351
|
+
config_line = nil
|
352
|
+
lines.last(10).each do |line|
|
353
|
+
# Allow indented config lines
|
354
|
+
if line.strip =~ /^\(\((.+)\)\)$/
|
355
|
+
@config_line = line.strip # Store the full line to preserve when saving
|
356
|
+
config_line = $1
|
357
|
+
break
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
return unless config_line
|
362
|
+
|
363
|
+
# Parse options (comma-separated)
|
364
|
+
config_options = {}
|
365
|
+
config_line.split(',').each do |option|
|
366
|
+
option = option.strip
|
367
|
+
if option =~ /(\w+)=(.+)/
|
368
|
+
key = $1.strip
|
369
|
+
value = $2.strip
|
370
|
+
# Convert values to appropriate types
|
371
|
+
case key
|
372
|
+
when "fold_level", "auto_save_interval", "tab_width"
|
373
|
+
config_options[key] = value.to_i
|
374
|
+
when "auto_save", "wrap", "show_numbers", "highlight_current", "checkbox_date", "backup", "encrypt"
|
375
|
+
config_options[key] = value.downcase == "true" || value.downcase == "yes"
|
376
|
+
else
|
377
|
+
config_options[key] = value
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
# Apply config options
|
383
|
+
apply_config(config_options)
|
384
|
+
end
|
385
|
+
|
386
|
+
def apply_config(options)
|
387
|
+
# Apply configuration options from config line
|
388
|
+
options.each do |key, value|
|
389
|
+
case key
|
390
|
+
when "fold_level"
|
391
|
+
@fold_level = value if value >= 0 && value <= 99
|
392
|
+
when "auto_save"
|
393
|
+
@auto_save_enabled = value
|
394
|
+
when "auto_save_interval"
|
395
|
+
@auto_save_interval = value if value > 0
|
396
|
+
when "tab_width", "indent_size"
|
397
|
+
@indent_size = value if value >= 2 && value <= 8
|
398
|
+
when "presentation_mode", "presentation"
|
399
|
+
@presentation_mode = value
|
400
|
+
setup_ui if value # Refresh UI if entering presentation mode
|
401
|
+
when "default_view"
|
402
|
+
case value
|
403
|
+
when "split"
|
404
|
+
@split_view = true
|
405
|
+
setup_ui
|
406
|
+
when "presentation"
|
407
|
+
@presentation_mode = true
|
408
|
+
setup_ui
|
409
|
+
end
|
410
|
+
when "theme"
|
411
|
+
# Apply theme setting
|
412
|
+
@theme = value if ["light", "normal", "dark"].include?(value)
|
413
|
+
when "wrap"
|
414
|
+
@wrap = value
|
415
|
+
when "show_numbers"
|
416
|
+
@show_numbers = value
|
417
|
+
when "search_case"
|
418
|
+
@search_case = value # sensitive/insensitive/smart
|
419
|
+
when "backup"
|
420
|
+
@backup_enabled = value
|
421
|
+
when "encrypt"
|
422
|
+
# Handle encryption enabling (would need password prompt)
|
423
|
+
@encrypt_enabled = value
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
# Show message about applied config
|
428
|
+
if options.any?
|
429
|
+
# Build message showing what was applied
|
430
|
+
msg_parts = []
|
431
|
+
msg_parts << "fold_level=#{@fold_level}" if @fold_level != 99
|
432
|
+
msg_parts << "theme=#{@theme}" if options["theme"]
|
433
|
+
msg_parts << "wrap=#{@wrap ? 'yes' : 'no'}" if options.key?("wrap")
|
434
|
+
msg_parts << "auto_save=#{@auto_save_enabled ? 'yes' : 'no'}" if options.key?("auto_save")
|
435
|
+
|
436
|
+
@message = "Applied config: #{msg_parts.join(', ')}" if msg_parts.any?
|
437
|
+
# Set a longer timeout for config message
|
438
|
+
@message_timeout = Time.now + 5.0
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
300
442
|
def add_to_recent_files(filepath)
|
301
443
|
return unless filepath && File.exist?(filepath)
|
302
444
|
|
@@ -328,6 +470,24 @@ class HyperListApp
|
|
328
470
|
[]
|
329
471
|
end
|
330
472
|
|
473
|
+
def load_command_history
|
474
|
+
history_file = File.expand_path("~/.hyperlist_command_history")
|
475
|
+
return [] unless File.exist?(history_file)
|
476
|
+
|
477
|
+
File.readlines(history_file).map(&:strip).reject(&:empty?).last(100)
|
478
|
+
rescue
|
479
|
+
[]
|
480
|
+
end
|
481
|
+
|
482
|
+
def save_command_history
|
483
|
+
history_file = File.expand_path("~/.hyperlist_command_history")
|
484
|
+
File.open(history_file, 'w') do |f|
|
485
|
+
@command_history.last(100).each { |cmd| f.puts(cmd) }
|
486
|
+
end
|
487
|
+
rescue
|
488
|
+
# Silently fail if can't write history
|
489
|
+
end
|
490
|
+
|
331
491
|
def show_recent_files
|
332
492
|
recent = load_recent_files
|
333
493
|
|
@@ -432,6 +592,23 @@ class HyperListApp
|
|
432
592
|
(' ' * @indent_size) * item["level"] + item["text"]
|
433
593
|
end.join("\n")
|
434
594
|
|
595
|
+
# Append config line if present
|
596
|
+
if @config_line && !@config_line.empty?
|
597
|
+
# Ensure there's a blank line before config
|
598
|
+
content += "\n" unless content.end_with?("\n")
|
599
|
+
content += "\n" + @config_line
|
600
|
+
else
|
601
|
+
# Debug: Check why config line is missing
|
602
|
+
if @fold_level != 99
|
603
|
+
# Rebuild config line if we have config settings but no line
|
604
|
+
update_config_line
|
605
|
+
if @config_line && !@config_line.empty?
|
606
|
+
content += "\n" unless content.end_with?("\n")
|
607
|
+
content += "\n" + @config_line
|
608
|
+
end
|
609
|
+
end
|
610
|
+
end
|
611
|
+
|
435
612
|
# Check if this should be an encrypted file
|
436
613
|
if is_encrypted_file?(@filename) && !content.empty?
|
437
614
|
# Check if any lines are already encrypted
|
@@ -731,6 +908,61 @@ class HyperListApp
|
|
731
908
|
end
|
732
909
|
|
733
910
|
|
911
|
+
def wrap_line(text, width, indent_level)
|
912
|
+
# Wrap a line per HyperList spec:
|
913
|
+
# Multi-line items start with '+' on first line
|
914
|
+
# Continuation lines have just a space prefix
|
915
|
+
return [text] unless @wrap
|
916
|
+
|
917
|
+
# Calculate effective width (account for indent and fold indicators)
|
918
|
+
indent_width = @indent_size * indent_level + 2 # +2 for fold indicator
|
919
|
+
effective_width = width - indent_width - 5 # Extra margin for readability
|
920
|
+
|
921
|
+
return [text] if text.length <= effective_width || effective_width <= 10
|
922
|
+
|
923
|
+
wrapped = []
|
924
|
+
remaining = text.dup
|
925
|
+
first_line = true
|
926
|
+
|
927
|
+
# Check if line already starts with + (multi-line indicator)
|
928
|
+
has_plus = text.strip.start_with?('+')
|
929
|
+
if has_plus
|
930
|
+
# Remove the + for processing, we'll add it back
|
931
|
+
remaining = remaining.sub(/^\s*\+\s*/, '')
|
932
|
+
end
|
933
|
+
|
934
|
+
while remaining && !remaining.empty?
|
935
|
+
if first_line
|
936
|
+
# First line gets the multi-line indicator if needed
|
937
|
+
if remaining.length <= effective_width
|
938
|
+
wrapped << (has_plus || wrapped.any? ? "+ #{remaining}" : remaining)
|
939
|
+
break
|
940
|
+
else
|
941
|
+
# Find a good break point (prefer spaces)
|
942
|
+
break_point = remaining[0...effective_width].rindex(' ') || effective_width
|
943
|
+
line_text = remaining[0...break_point].rstrip
|
944
|
+
# Add + to first line of multi-line items
|
945
|
+
wrapped << "+ #{line_text}"
|
946
|
+
remaining = remaining[break_point..-1].lstrip
|
947
|
+
first_line = false
|
948
|
+
end
|
949
|
+
else
|
950
|
+
# Continuation lines get just a space prefix per HyperList spec
|
951
|
+
cont_width = effective_width - 1 # Account for space prefix
|
952
|
+
if remaining.length <= cont_width
|
953
|
+
wrapped << " #{remaining}"
|
954
|
+
break
|
955
|
+
else
|
956
|
+
break_point = remaining[0...cont_width].rindex(' ') || cont_width
|
957
|
+
wrapped << " #{remaining[0...break_point].rstrip}"
|
958
|
+
remaining = remaining[break_point..-1].lstrip
|
959
|
+
end
|
960
|
+
end
|
961
|
+
end
|
962
|
+
|
963
|
+
wrapped
|
964
|
+
end
|
965
|
+
|
734
966
|
def render_main
|
735
967
|
visible_items = get_visible_items
|
736
968
|
|
@@ -742,62 +974,92 @@ class HyperListApp
|
|
742
974
|
visible_items.each_with_index do |item, idx|
|
743
975
|
next unless item
|
744
976
|
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
real_idx = get_real_index(item)
|
749
|
-
if real_idx && has_children?(real_idx, @items) && item["fold"]
|
750
|
-
color = (@presentation_mode && !is_item_in_presentation_focus?(item)) ? "240" : "245"
|
751
|
-
line += "▶".fg(color) + " "
|
752
|
-
elsif real_idx && has_children?(real_idx, @items)
|
753
|
-
color = (@presentation_mode && !is_item_in_presentation_focus?(item)) ? "240" : "245"
|
754
|
-
line += "▷".fg(color) + " "
|
977
|
+
# Handle line wrapping if enabled
|
978
|
+
if @wrap
|
979
|
+
text_lines = wrap_line(item["text"], @cols, item["level"])
|
755
980
|
else
|
756
|
-
|
981
|
+
text_lines = [item["text"]]
|
757
982
|
end
|
758
983
|
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
984
|
+
text_lines.each_with_index do |text_line, line_idx|
|
985
|
+
# Get the actual line number from the real index in @items
|
986
|
+
real_idx = get_real_index(item)
|
987
|
+
actual_line_number = real_idx ? real_idx + 1 : 0 # +1 for 1-based line numbers
|
988
|
+
|
989
|
+
# Add line number if enabled (only on first line of wrapped text)
|
990
|
+
line = ""
|
991
|
+
if @show_numbers
|
992
|
+
if line_idx == 0
|
993
|
+
line = "#{actual_line_number.to_s.rjust(4)} "
|
994
|
+
else
|
995
|
+
line = " " # Empty space for continuation lines
|
996
|
+
end
|
997
|
+
end
|
998
|
+
|
999
|
+
line += ' ' * (@indent_size * item["level"])
|
1000
|
+
|
1001
|
+
# Add fold indicator only on first line
|
1002
|
+
if line_idx == 0
|
1003
|
+
real_idx = get_real_index(item)
|
1004
|
+
if real_idx && has_children?(real_idx, @items) && item["fold"]
|
1005
|
+
color = (@presentation_mode && !is_item_in_presentation_focus?(item)) ? "240" : "245"
|
1006
|
+
line += "▶".fg(color) + " "
|
1007
|
+
elsif real_idx && has_children?(real_idx, @items)
|
1008
|
+
color = (@presentation_mode && !is_item_in_presentation_focus?(item)) ? "240" : "245"
|
1009
|
+
line += "▷".fg(color) + " "
|
1010
|
+
else
|
1011
|
+
line += " "
|
1012
|
+
end
|
771
1013
|
else
|
772
|
-
|
1014
|
+
# Continuation lines already have their space prefix from wrap_line
|
1015
|
+
line += " " # Just add fold indicator spacing
|
773
1016
|
end
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
1017
|
+
|
1018
|
+
# Handle literal blocks and syntax highlighting
|
1019
|
+
if item["text"].strip == "\\"
|
1020
|
+
if !in_literal_block
|
1021
|
+
in_literal_block = true
|
1022
|
+
literal_start_level = item["level"]
|
1023
|
+
spaces = text_line.match(/^(\s*)/)[1] || ""
|
1024
|
+
line += spaces + "\\".fg("3")
|
1025
|
+
elsif item["level"] == literal_start_level
|
1026
|
+
in_literal_block = false
|
1027
|
+
literal_start_level = -1
|
1028
|
+
spaces = text_line.match(/^(\s*)/)[1] || ""
|
1029
|
+
line += spaces + "\\".fg("3")
|
1030
|
+
else
|
1031
|
+
line += text_line
|
1032
|
+
end
|
1033
|
+
elsif in_literal_block
|
1034
|
+
line += text_line
|
781
1035
|
else
|
782
|
-
|
1036
|
+
# Normal syntax highlighting
|
1037
|
+
has_match = @search_matches.include?(idx) && @search && !@search.empty?
|
1038
|
+
if @presentation_mode && !is_item_in_presentation_focus?(item)
|
1039
|
+
line += text_line.fg("240")
|
1040
|
+
else
|
1041
|
+
line += process_text(text_line, has_match)
|
1042
|
+
end
|
783
1043
|
end
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
1044
|
+
|
1045
|
+
# Apply current item highlighting (all lines of wrapped text get bg)
|
1046
|
+
if idx == @current
|
1047
|
+
# Skip background highlighting in presentation mode for items in focus
|
1048
|
+
if !(@presentation_mode && is_item_in_presentation_focus?(item))
|
1049
|
+
bg_color = (!@split_view || @active_pane == :main) ? "237" : "234"
|
1050
|
+
if bg_color
|
1051
|
+
# Pad line to full width and apply background
|
1052
|
+
padded_line = line + " " * [@cols - line.pure.length, 0].max
|
1053
|
+
bg_code = "\e[48;5;#{bg_color}m"
|
1054
|
+
reset_bg = "\e[49m"
|
1055
|
+
line = bg_code + padded_line.gsub(/\e\[49m/, '') + reset_bg
|
1056
|
+
end
|
795
1057
|
end
|
796
1058
|
end
|
1059
|
+
|
1060
|
+
lines << line
|
797
1061
|
end
|
798
1062
|
|
799
|
-
lines << line
|
800
|
-
|
801
1063
|
# Check if exiting literal block
|
802
1064
|
if in_literal_block && item["level"] <= literal_start_level && !item["text"].strip == "\\"
|
803
1065
|
in_literal_block = false
|
@@ -847,10 +1109,63 @@ class HyperListApp
|
|
847
1109
|
@main.refresh
|
848
1110
|
end
|
849
1111
|
|
1112
|
+
def get_theme_colors
|
1113
|
+
# Theme definitions based on HyperList spec colors
|
1114
|
+
# Using hex RGB colors (RRGGBB format) for rcurses
|
1115
|
+
# Per HyperList spec from hyperlist.tex:
|
1116
|
+
# red (properties/dates), green (qualifiers/states), blue (operators)
|
1117
|
+
# magenta/violet (references), cyan (parentheses/quotes), yellow (literals)
|
1118
|
+
# orange (tags)
|
1119
|
+
case @theme
|
1120
|
+
when "light"
|
1121
|
+
# Brighter, more saturated colors for dark terminals
|
1122
|
+
{
|
1123
|
+
"red" => "FF5050", # Bright red for properties
|
1124
|
+
"green" => "50FF50", # Bright green for qualifiers
|
1125
|
+
"blue" => "6496FF", # Bright blue for operators
|
1126
|
+
"magenta" => "C864FF", # Light purple/violet for references
|
1127
|
+
"cyan" => "50FFFF", # Bright cyan for parentheses
|
1128
|
+
"yellow" => "FFFF64", # Bright yellow for literals
|
1129
|
+
"orange" => "FFB450", # Bright orange for tags
|
1130
|
+
"gray" => "C8C8C8" # Light gray
|
1131
|
+
}
|
1132
|
+
when "dark"
|
1133
|
+
# Darker, less saturated colors for light background terminals
|
1134
|
+
{
|
1135
|
+
"red" => "B40000", # Dark red for properties
|
1136
|
+
"green" => "008C00", # Dark green for qualifiers
|
1137
|
+
"blue" => "0000B4", # Dark blue for operators
|
1138
|
+
"magenta" => "8C008C", # Dark purple for references
|
1139
|
+
"cyan" => "008C8C", # Dark cyan for parentheses
|
1140
|
+
"yellow" => "8C8C00", # Dark yellow for literals
|
1141
|
+
"orange" => "B46400", # Dark orange for tags
|
1142
|
+
"gray" => "646464" # Dark gray
|
1143
|
+
}
|
1144
|
+
else # normal - using 256 color codes for compatibility
|
1145
|
+
{
|
1146
|
+
"red" => "196", # Red for properties/dates (standard red)
|
1147
|
+
"green" => "46", # Green for qualifiers/checkboxes
|
1148
|
+
"blue" => "21", # Blue for operators (AND/OR/IF/THEN)
|
1149
|
+
"magenta" => "165", # Purple/violet for references
|
1150
|
+
"cyan" => "51", # Cyan for parentheses/quotes
|
1151
|
+
"yellow" => "226", # Yellow for literals/substitutions
|
1152
|
+
"orange" => "208", # Orange for tags
|
1153
|
+
"gray" => "245" # Gray
|
1154
|
+
}
|
1155
|
+
end
|
1156
|
+
end
|
1157
|
+
|
850
1158
|
def process_text(text, highlight_search = false)
|
851
1159
|
# Work with a clean copy
|
852
1160
|
result = text.dup
|
853
1161
|
processed_checkbox = false
|
1162
|
+
colors = get_theme_colors
|
1163
|
+
|
1164
|
+
# Config lines should never be displayed (they're filtered out)
|
1165
|
+
# But if somehow one gets through, don't process it
|
1166
|
+
if result =~ /^\(\(.+\)\)$/
|
1167
|
+
return ""
|
1168
|
+
end
|
854
1169
|
|
855
1170
|
# If text already contains ANSI codes, return as-is to avoid double-processing
|
856
1171
|
if result.include?("\e[")
|
@@ -909,7 +1224,7 @@ class HyperListApp
|
|
909
1224
|
|
910
1225
|
# Check if this is a literal block marker (single backslash)
|
911
1226
|
if result.strip == "\\"
|
912
|
-
return result.fg("
|
1227
|
+
return result.fg(colors["yellow"]) # Yellow for literal block markers
|
913
1228
|
end
|
914
1229
|
|
915
1230
|
# Apply search highlighting if we have an active search
|
@@ -923,47 +1238,47 @@ class HyperListApp
|
|
923
1238
|
# Based on hyperlist.vim: '^\(\t\|\*\)*[0-9.]* '
|
924
1239
|
if result =~ /^([0-9][0-9A-Z.]*\s)/
|
925
1240
|
identifier = $1
|
926
|
-
result = result.sub(/^[0-9][0-9A-Z.]*\s/, identifier.fg("
|
1241
|
+
result = result.sub(/^[0-9][0-9A-Z.]*\s/, identifier.fg(colors["magenta"])) # Magenta for identifiers
|
927
1242
|
end
|
928
1243
|
|
929
1244
|
# Handle multi-line indicator at the beginning (+ with space)
|
930
1245
|
# Based on hyperlist.vim: '^\(\t\|\*\)*+ '
|
931
1246
|
if result =~ /^\+\s/
|
932
|
-
result = result.sub(/^(\+\s)/, "+".fg("
|
1247
|
+
result = result.sub(/^(\+\s)/, "+".fg(colors["red"]) + " ") # Red for multi-line indicator
|
933
1248
|
end
|
934
1249
|
|
935
1250
|
# Handle continuation markers (+ at start of indented lines in References section)
|
936
1251
|
if result =~ /^\s*\+\s/
|
937
1252
|
spaces = $1 || ""
|
938
1253
|
marker = $2 || "+ "
|
939
|
-
result = result.sub(/^(\s*)(\+\s)/, spaces + marker.fg("
|
1254
|
+
result = result.sub(/^(\s*)(\+\s)/, spaces + marker.fg(colors["red"])) # Red for continuation marker
|
940
1255
|
end
|
941
1256
|
|
942
1257
|
# Process checkboxes anywhere in the line (can have leading spaces)
|
943
1258
|
if result =~ /^(\s*)(\[X\]|\[x\])/
|
944
1259
|
spaces = $1
|
945
|
-
colored = "[X]".fg("
|
1260
|
+
colored = "[X]".fg(colors["green"])
|
946
1261
|
result = result.sub(/^(\s*)(\[X\]|\[x\])/, "#{spaces}#{colored}") # Bright green for completed
|
947
1262
|
processed_checkbox = true
|
948
1263
|
elsif result =~ /^(\s*)(\[O\])/
|
949
1264
|
spaces = $1
|
950
|
-
colored = "[O]".fg("
|
1265
|
+
colored = "[O]".fg(colors["green"]).b
|
951
1266
|
result = result.sub(/^(\s*)(\[O\])/, "#{spaces}#{colored}") # Bold bright green for in-progress
|
952
1267
|
processed_checkbox = true
|
953
1268
|
elsif result =~ /^(\s*)(\[-\])/
|
954
1269
|
spaces = $1
|
955
|
-
colored = "[-]".fg("
|
1270
|
+
colored = "[-]".fg(colors["green"])
|
956
1271
|
result = result.sub(/^(\s*)(\[-\])/, "#{spaces}#{colored}") # Green for partial
|
957
1272
|
processed_checkbox = true
|
958
1273
|
elsif result =~ /^(\s*)(\[ \]|\[_\])/
|
959
1274
|
spaces = $1
|
960
|
-
colored = "[ ]".fg("
|
1275
|
+
colored = "[ ]".fg(colors["green"])
|
961
1276
|
result = result.sub(/^(\s*)(\[ \]|\[_\])/, "#{spaces}#{colored}") # Dark green for unchecked
|
962
1277
|
processed_checkbox = true
|
963
1278
|
elsif !processed_checkbox
|
964
1279
|
# Only handle other qualifiers if we didn't process a checkbox
|
965
1280
|
# Based on hyperlist.vim: '\[.\{-}\]'
|
966
|
-
result.gsub!(/\[([^\]]*)\]/) { "[#{$1}]".fg("
|
1281
|
+
result.gsub!(/\[([^\]]*)\]/) { "[#{$1}]".fg(colors["green"]) } # Green for all qualifiers
|
967
1282
|
end
|
968
1283
|
|
969
1284
|
# We'll handle parentheses AFTER operators/properties to avoid conflicts
|
@@ -971,7 +1286,7 @@ class HyperListApp
|
|
971
1286
|
# Handle date timestamps as properties (for checkbox dates)
|
972
1287
|
# Format: YYYY-MM-DD HH.MM:
|
973
1288
|
result.gsub!(/(\d{4}-\d{2}-\d{2} \d{2}\.\d{2}):/) do
|
974
|
-
"#{$1}:".fg("
|
1289
|
+
"#{$1}:".fg(colors["red"]) # Red for timestamp properties
|
975
1290
|
end
|
976
1291
|
|
977
1292
|
# Handle operators and properties with colon pattern
|
@@ -985,10 +1300,10 @@ class HyperListApp
|
|
985
1300
|
|
986
1301
|
# Check if it's an operator (ALL-CAPS with optional _, -, (), /, =, spaces)
|
987
1302
|
if text_part =~ /^[A-Z][A-Z_\-() \/=]*$/
|
988
|
-
prefix_space + text_part.fg("
|
1303
|
+
prefix_space + text_part.fg(colors["blue"]) + colon_space.fg(colors["blue"]) # Blue for operators (including S: and T:)
|
989
1304
|
elsif text_part.length >= 2 && space_after.include?(" ")
|
990
1305
|
# It's a property (mixed case, at least 2 chars, has space after colon)
|
991
|
-
prefix_space + text_part.fg("
|
1306
|
+
prefix_space + text_part.fg(colors["red"]) + colon_space.fg(colors["red"]) # Red for properties
|
992
1307
|
else
|
993
1308
|
# Leave as is
|
994
1309
|
prefix_space + text_part + colon_space
|
@@ -997,57 +1312,57 @@ class HyperListApp
|
|
997
1312
|
|
998
1313
|
|
999
1314
|
# Color special state/transition markers (| and /) green
|
1000
|
-
result.gsub!(/^(\s*)\|\s+/) { $1 + "| ".fg("
|
1001
|
-
result.gsub!(/^(\s*)\/\s+/) { $1 + "/ ".fg("
|
1315
|
+
result.gsub!(/^(\s*)\|\s+/) { $1 + "| ".fg(colors["green"]) } # Green for pipe (state marker)
|
1316
|
+
result.gsub!(/^(\s*)\/\s+/) { $1 + "/ ".fg(colors["green"]) } # Green for slash (transition marker)
|
1002
1317
|
|
1003
1318
|
# Handle OR: at the beginning of a line (with optional spaces)
|
1004
|
-
result.sub!(/^(\s*)(OR):/) { $1 + "OR:".fg("
|
1319
|
+
result.sub!(/^(\s*)(OR):/) { $1 + "OR:".fg(colors["blue"]) } # Blue for OR: at line start
|
1005
1320
|
|
1006
1321
|
# Handle parentheses content (moved here to avoid conflicts with properties)
|
1007
1322
|
# Based on hyperlist.vim: '(.\{-})'
|
1008
1323
|
result = safe_regex_replace(result, /\(([^)]*)\)/) do |match|
|
1009
1324
|
content = match[1..-2] # Extract content between parentheses
|
1010
|
-
"(".fg("
|
1325
|
+
"(".fg(colors["cyan"]) + content.fg(colors["cyan"]) + ")".fg(colors["cyan"])
|
1011
1326
|
end
|
1012
1327
|
|
1013
1328
|
# Handle semicolons as separators (they separate items on the same line)
|
1014
1329
|
# Semicolons are green like qualifiers
|
1015
|
-
result = safe_regex_replace(result, /;/) { ";".fg("
|
1330
|
+
result = safe_regex_replace(result, /;/) { ";".fg(colors["green"]) }
|
1016
1331
|
|
1017
1332
|
# Handle references - color entire reference including brackets
|
1018
1333
|
# Based on hyperlist.vim: '<\{1,2}[...]\+>\{1,2}'
|
1019
|
-
result.gsub!(/<{1,2}([^>]+)>{1,2}/) { |match| match.fg("
|
1334
|
+
result.gsub!(/<{1,2}([^>]+)>{1,2}/) { |match| match.fg(colors["magenta"]) } # Magenta for references
|
1020
1335
|
|
1021
1336
|
# Handle special keywords SKIP and END
|
1022
|
-
result.gsub!(/\b(SKIP|END)\b/) { $1.fg("
|
1337
|
+
result.gsub!(/\b(SKIP|END)\b/) { $1.fg(colors["magenta"]) } # Magenta for special keywords (like references)
|
1023
1338
|
|
1024
1339
|
# Handle quoted strings (only double quotes are special in HyperList)
|
1025
1340
|
# Based on hyperlist.vim: '".\{-}"'
|
1026
1341
|
result.gsub!(/"([^"]*)"/) do
|
1027
1342
|
content = $1
|
1028
1343
|
# Color any ## sequences inside the quotes as red
|
1029
|
-
content.gsub!(/(##[<>-]+)/) { $1.fg("
|
1030
|
-
'"'.fg("
|
1344
|
+
content.gsub!(/(##[<>-]+)/) { $1.fg(colors["red"]) }
|
1345
|
+
'"'.fg(colors["cyan"]) + content.fg(colors["cyan"]) + '"'.fg(colors["cyan"]) # Cyan for quoted strings
|
1031
1346
|
end
|
1032
1347
|
|
1033
1348
|
# Handle change markup - all double-hashes should be red
|
1034
1349
|
# First handle ##><Reference>##-> style (with reference in the middle)
|
1035
1350
|
result.gsub!(/(##[<>-]+)(<[^>]+>)(##[<>-]+)/) do
|
1036
|
-
$1.fg("
|
1351
|
+
$1.fg(colors["red"]) + $2.fg(colors["magenta"]) + $3.fg(colors["red"]) # Red markers, magenta reference
|
1037
1352
|
end
|
1038
1353
|
|
1039
1354
|
# Handle ##Text## change info (text between double hashes)
|
1040
|
-
result.gsub!(/(##)([^#]+)(##)/) { $1.fg("
|
1355
|
+
result.gsub!(/(##)([^#]+)(##)/) { $1.fg(colors["red"]) + $2 + $3.fg(colors["red"]) } # Red for change info markers
|
1041
1356
|
|
1042
1357
|
# Then color any remaining ## sequences red
|
1043
|
-
result.gsub!(/(##[<>-]*)/) { $1.fg("
|
1358
|
+
result.gsub!(/(##[<>-]*)/) { $1.fg(colors["red"]) } # Red for all ## markers
|
1044
1359
|
|
1045
1360
|
# Handle substitutions {variable}
|
1046
|
-
result.gsub!(/\{([^}]+)\}/) { "{".fg("
|
1361
|
+
result.gsub!(/\{([^}]+)\}/) { "{".fg(colors["yellow"]) + $1.fg(colors["yellow"]) + "}".fg(colors["yellow"]) } # Yellow for substitutions
|
1047
1362
|
|
1048
1363
|
# Handle hash tags
|
1049
1364
|
# Based on hyperlist.vim: '#[a-zA-Z0-9.:/_&?%=+\-\*]\+'
|
1050
|
-
result.gsub!(/#([a-zA-Z0-9.:_\/&?%=+\-*]+)/) { "##{$1}".fg("
|
1365
|
+
result.gsub!(/#([a-zA-Z0-9.:_\/&?%=+\-*]+)/) { "##{$1}".fg(colors["orange"]) } # Orange for tags
|
1051
1366
|
|
1052
1367
|
# Handle text formatting (bold, italic, underline)
|
1053
1368
|
# Based on hyperlist.vim patterns with tab/space boundaries
|
@@ -1990,16 +2305,9 @@ class HyperListApp
|
|
1990
2305
|
end
|
1991
2306
|
end
|
1992
2307
|
else
|
1993
|
-
# For single line delete
|
2308
|
+
# For single line delete (D key - delete only the current line)
|
1994
2309
|
@clipboard_is_tree = false # Mark as single/adaptive for paste behavior
|
1995
|
-
|
1996
|
-
if @items[i]["level"] > level
|
1997
|
-
@clipboard << @items[i].dup
|
1998
|
-
delete_count += 1
|
1999
|
-
else
|
2000
|
-
break
|
2001
|
-
end
|
2002
|
-
end
|
2310
|
+
# Don't include children - delete_count stays at 1
|
2003
2311
|
end
|
2004
2312
|
|
2005
2313
|
# Delete the items
|
@@ -2486,8 +2794,13 @@ class HyperListApp
|
|
2486
2794
|
end
|
2487
2795
|
|
2488
2796
|
def show_help
|
2489
|
-
|
2490
|
-
|
2797
|
+
begin
|
2798
|
+
# Build help text using consistent formatting
|
2799
|
+
help_lines = []
|
2800
|
+
|
2801
|
+
# Debug logging
|
2802
|
+
debug_log = File.open("/tmp/hyperlist_help_debug.log", "w") rescue nil
|
2803
|
+
debug_log.puts "show_help called at #{Time.now}" if debug_log
|
2491
2804
|
help_lines << " Press #{"?".fg("10")} for full documentation, #{"UP/DOWN".fg("10")} to scroll, or any other key to return"
|
2492
2805
|
help_lines << ""
|
2493
2806
|
help_lines << "#{"HYPERLIST KEY BINDINGS".b}"
|
@@ -2516,7 +2829,7 @@ class HyperListApp
|
|
2516
2829
|
help_lines << help_line("#{"i/Enter".fg("10")}", "Edit line", "#{"o".fg("10")}", "Insert line below")
|
2517
2830
|
help_lines << help_line("#{"O".fg("10")}", "Insert line above", "#{"a".fg("10")}", "Insert child")
|
2518
2831
|
help_lines << help_line("#{"A".fg("10")}", "Insert outdented", "#{"W".fg("10")}", "Save and quit")
|
2519
|
-
help_lines << help_line("#{"I".fg("10")}", "Cycle indent (2-5)"
|
2832
|
+
help_lines << help_line("#{"I".fg("10")}", "Cycle indent (2-5)")
|
2520
2833
|
help_lines << help_line("#{"D".fg("10")}", "Delete+yank line", "#{"C-D".fg("10")}", "Delete+yank item&descendants")
|
2521
2834
|
help_lines << help_line("#{"y".fg("10")}" + "/".fg("10") + "#{"Y".fg("10")}", "Copy line/tree", "#{"p".fg("10")}", "Paste")
|
2522
2835
|
help_lines << help_line("#{"u".fg("10")}", "Undo", "#{".".fg("10")}", "Repeat last action")
|
@@ -2531,7 +2844,7 @@ class HyperListApp
|
|
2531
2844
|
help_lines << help_line("#{"C-E".fg("10")}", "Encrypt/decrypt line", "#{"C-U".fg("10")}", "Toggle State/Trans underline")
|
2532
2845
|
help_lines << help_line("#{"P".fg("10")}", "Presentation mode", "#{"Tab/S-Tab".fg("10")}", "Next/prev sibling (in P)")
|
2533
2846
|
help_lines << help_line("#{"Ma".fg("10")}", "Record macro 'a'", "#{"@a".fg("10")}", "Play macro 'a'")
|
2534
|
-
help_lines << help_line("#{"w".fg("10")}", "Switch panes (split view)"
|
2847
|
+
help_lines << help_line("#{"w".fg("10")}", "Switch panes (split view)")
|
2535
2848
|
help_lines << ""
|
2536
2849
|
help_lines << "#{"FILE OPERATIONS".fg("14")}"
|
2537
2850
|
help_lines << help_line("#{":w".fg("10")}", "Save", "#{":q".fg("10")}", "Quit")
|
@@ -2545,6 +2858,29 @@ class HyperListApp
|
|
2545
2858
|
help_lines << help_line("#{"t".fg("10")}", "Insert template", "#{":st".fg("10")}", "Save as template")
|
2546
2859
|
help_lines << help_line("#{":dt".fg("10")}", "Delete template", "#{":lt".fg("10")}", "List user templates")
|
2547
2860
|
help_lines << ""
|
2861
|
+
|
2862
|
+
# Try adding CONFIGURATION section with error handling
|
2863
|
+
begin
|
2864
|
+
debug_log.puts "Starting CONFIGURATION section" if debug_log
|
2865
|
+
help_lines << "#{"CONFIGURATION".fg("14")}"
|
2866
|
+
debug_log.puts "Added CONFIGURATION header" if debug_log
|
2867
|
+
|
2868
|
+
line1 = help_line("#{":set".fg("10")}", "Show all settings", "#{":set option".fg("10")}", "Show option value")
|
2869
|
+
debug_log.puts "Line1: #{line1.inspect}" if debug_log
|
2870
|
+
help_lines << line1
|
2871
|
+
|
2872
|
+
line2 = help_line("#{":set option=val".fg("10")}", "Set option")
|
2873
|
+
debug_log.puts "Line2: #{line2.inspect}" if debug_log
|
2874
|
+
help_lines << line2
|
2875
|
+
|
2876
|
+
help_lines << ""
|
2877
|
+
debug_log.puts "CONFIGURATION section complete" if debug_log
|
2878
|
+
rescue => e
|
2879
|
+
debug_log.puts "Error in CONFIGURATION: #{e.message}" if debug_log
|
2880
|
+
debug_log.puts e.backtrace.join("\n") if debug_log
|
2881
|
+
# Fall through and continue without config section
|
2882
|
+
end
|
2883
|
+
|
2548
2884
|
help_lines << "#{"HELP & QUIT".fg("14")}"
|
2549
2885
|
help_lines << help_line("#{"?".fg("10")}", "This help", "#{"??".fg("10")}", "Full documentation")
|
2550
2886
|
help_lines << help_line("#{"q".fg("10")}", "Quit (asks to save)", "#{"Q".fg("10")}", "Force quit")
|
@@ -2559,6 +2895,9 @@ class HyperListApp
|
|
2559
2895
|
|
2560
2896
|
help = help_lines.join("\n")
|
2561
2897
|
|
2898
|
+
debug_log.puts "Help generation complete, #{help_lines.length} lines" if debug_log
|
2899
|
+
debug_log.close if debug_log
|
2900
|
+
|
2562
2901
|
# Store current state
|
2563
2902
|
saved_items = @items.dup
|
2564
2903
|
saved_current = @current
|
@@ -2612,6 +2951,18 @@ class HyperListApp
|
|
2612
2951
|
@current = saved_current
|
2613
2952
|
@offset = saved_offset
|
2614
2953
|
@modified = saved_modified
|
2954
|
+
|
2955
|
+
rescue => e
|
2956
|
+
# Log the error and show a simple message
|
2957
|
+
File.open("/tmp/hyperlist_help_crash.log", "w") do |f|
|
2958
|
+
f.puts "Help crashed at #{Time.now}"
|
2959
|
+
f.puts "Error: #{e.message}"
|
2960
|
+
f.puts "Backtrace:"
|
2961
|
+
f.puts e.backtrace.join("\n")
|
2962
|
+
end rescue nil
|
2963
|
+
|
2964
|
+
@message = "Help error logged to /tmp/hyperlist_help_crash.log"
|
2965
|
+
end
|
2615
2966
|
end
|
2616
2967
|
|
2617
2968
|
def show_documentation
|
@@ -3149,6 +3500,9 @@ class HyperListApp
|
|
3149
3500
|
|
3150
3501
|
def handle_command
|
3151
3502
|
@mode = :command
|
3503
|
+
# Set command history (reversed for proper UP arrow navigation)
|
3504
|
+
@footer.history = @command_history.reverse
|
3505
|
+
@footer.record = true
|
3152
3506
|
@command = @footer.ask(":", "")
|
3153
3507
|
@mode = :normal
|
3154
3508
|
@footer.clear # Clear footer immediately
|
@@ -3156,6 +3510,14 @@ class HyperListApp
|
|
3156
3510
|
|
3157
3511
|
return unless @command
|
3158
3512
|
|
3513
|
+
# Add to history if not empty and not duplicate of last entry
|
3514
|
+
if !@command.empty? && (@command_history.empty? || @command_history.last != @command)
|
3515
|
+
@command_history << @command
|
3516
|
+
# Keep only last 100 commands
|
3517
|
+
@command_history = @command_history.last(100)
|
3518
|
+
save_command_history
|
3519
|
+
end
|
3520
|
+
|
3159
3521
|
case @command
|
3160
3522
|
when "w", "write"
|
3161
3523
|
if @filename
|
@@ -3247,11 +3609,134 @@ class HyperListApp
|
|
3247
3609
|
when "split"
|
3248
3610
|
# Copy current section to split view
|
3249
3611
|
copy_section_to_split
|
3612
|
+
when /^set\s+(\w+)=(.+)$/
|
3613
|
+
# Handle :set option=value
|
3614
|
+
option = $1
|
3615
|
+
value = $2.strip
|
3616
|
+
set_config_option(option, value)
|
3617
|
+
when /^set\s+(\w+)$/
|
3618
|
+
# Handle :set option (show current value)
|
3619
|
+
option = $1
|
3620
|
+
show_config_option(option)
|
3621
|
+
when "set"
|
3622
|
+
# Show all current settings
|
3623
|
+
show_all_config_options
|
3250
3624
|
else
|
3251
3625
|
@message = "Unknown command: #{@command}"
|
3252
3626
|
end
|
3253
3627
|
end
|
3254
3628
|
|
3629
|
+
def set_config_option(option, value)
|
3630
|
+
# Convert value to appropriate type
|
3631
|
+
case option
|
3632
|
+
when "theme"
|
3633
|
+
if ["light", "normal", "dark"].include?(value)
|
3634
|
+
@theme = value
|
3635
|
+
@message = "Theme set to: #{value}"
|
3636
|
+
else
|
3637
|
+
@message = "Invalid theme. Use: light, normal, or dark"
|
3638
|
+
end
|
3639
|
+
when "wrap"
|
3640
|
+
@wrap = value == "yes" || value == "true"
|
3641
|
+
@message = "Line wrapping #{@wrap ? 'enabled' : 'disabled'}"
|
3642
|
+
when "show_numbers"
|
3643
|
+
@show_numbers = value == "yes" || value == "true"
|
3644
|
+
@message = "Line numbers #{@show_numbers ? 'enabled' : 'disabled'}"
|
3645
|
+
when "fold_level"
|
3646
|
+
level = value.to_i
|
3647
|
+
if level >= 0 && level <= 99
|
3648
|
+
@fold_level = level
|
3649
|
+
apply_fold_level(level)
|
3650
|
+
@message = "Fold level set to: #{level}"
|
3651
|
+
else
|
3652
|
+
@message = "Invalid fold level. Use 0-99"
|
3653
|
+
end
|
3654
|
+
when "auto_save"
|
3655
|
+
@auto_save_enabled = value == "yes" || value == "true"
|
3656
|
+
@message = "Auto-save #{@auto_save_enabled ? 'enabled' : 'disabled'}"
|
3657
|
+
when "auto_save_interval"
|
3658
|
+
interval = value.to_i
|
3659
|
+
if interval > 0
|
3660
|
+
@auto_save_interval = interval
|
3661
|
+
@message = "Auto-save interval set to: #{interval} seconds"
|
3662
|
+
else
|
3663
|
+
@message = "Invalid interval. Must be > 0"
|
3664
|
+
end
|
3665
|
+
when "tab_width", "indent_size"
|
3666
|
+
width = value.to_i
|
3667
|
+
if width >= 2 && width <= 8
|
3668
|
+
@indent_size = width
|
3669
|
+
@message = "Tab width set to: #{width}"
|
3670
|
+
else
|
3671
|
+
@message = "Invalid tab width. Use 2-8"
|
3672
|
+
end
|
3673
|
+
else
|
3674
|
+
@message = "Unknown option: #{option}"
|
3675
|
+
end
|
3676
|
+
|
3677
|
+
# Update config line if it exists
|
3678
|
+
update_config_line
|
3679
|
+
end
|
3680
|
+
|
3681
|
+
def show_config_option(option)
|
3682
|
+
case option
|
3683
|
+
when "theme"
|
3684
|
+
@message = "theme=#{@theme}"
|
3685
|
+
when "wrap"
|
3686
|
+
@message = "wrap=#{@wrap ? 'yes' : 'no'}"
|
3687
|
+
when "show_numbers"
|
3688
|
+
@message = "show_numbers=#{@show_numbers ? 'yes' : 'no'}"
|
3689
|
+
when "fold_level"
|
3690
|
+
@message = "fold_level=#{@fold_level}"
|
3691
|
+
when "auto_save"
|
3692
|
+
@message = "auto_save=#{@auto_save_enabled ? 'yes' : 'no'}"
|
3693
|
+
when "auto_save_interval"
|
3694
|
+
@message = "auto_save_interval=#{@auto_save_interval}"
|
3695
|
+
when "tab_width", "indent_size"
|
3696
|
+
@message = "tab_width=#{@indent_size}"
|
3697
|
+
else
|
3698
|
+
@message = "Unknown option: #{option}"
|
3699
|
+
end
|
3700
|
+
end
|
3701
|
+
|
3702
|
+
def show_all_config_options
|
3703
|
+
options = []
|
3704
|
+
options << "theme=#{@theme}"
|
3705
|
+
options << "wrap=#{@wrap ? 'yes' : 'no'}"
|
3706
|
+
options << "show_numbers=#{@show_numbers ? 'yes' : 'no'}"
|
3707
|
+
options << "fold_level=#{@fold_level}"
|
3708
|
+
options << "auto_save=#{@auto_save_enabled ? 'yes' : 'no'}"
|
3709
|
+
options << "auto_save_interval=#{@auto_save_interval}"
|
3710
|
+
options << "tab_width=#{@indent_size}"
|
3711
|
+
@message = "Settings: #{options.join(', ')}"
|
3712
|
+
end
|
3713
|
+
|
3714
|
+
def update_config_line
|
3715
|
+
# Build new config line based on current settings
|
3716
|
+
# This should preserve the config line and update it with current values
|
3717
|
+
options = []
|
3718
|
+
|
3719
|
+
# Include fold_level if it's not the default
|
3720
|
+
if @fold_level != 99
|
3721
|
+
options << "fold_level=#{@fold_level}"
|
3722
|
+
end
|
3723
|
+
|
3724
|
+
options << "theme=#{@theme}" if @theme != "normal"
|
3725
|
+
options << "wrap=yes" if @wrap
|
3726
|
+
options << "show_numbers=yes" if @show_numbers
|
3727
|
+
options << "auto_save=yes" if @auto_save_enabled
|
3728
|
+
options << "auto_save_interval=#{@auto_save_interval}" if @auto_save_interval != 60
|
3729
|
+
options << "tab_width=#{@indent_size}" if @indent_size != 2
|
3730
|
+
|
3731
|
+
if options.any?
|
3732
|
+
@config_line = "((#{options.join(', ')}))"
|
3733
|
+
else
|
3734
|
+
@config_line = nil
|
3735
|
+
end
|
3736
|
+
|
3737
|
+
@modified = true
|
3738
|
+
end
|
3739
|
+
|
3255
3740
|
def jump_to_reference
|
3256
3741
|
visible = get_visible_items
|
3257
3742
|
return if @current >= visible.length
|
@@ -4426,11 +4911,19 @@ class HyperListApp
|
|
4426
4911
|
visible_items.each_with_index do |item, idx|
|
4427
4912
|
next unless item
|
4428
4913
|
|
4429
|
-
line = " " * item["level"]
|
4430
|
-
|
4431
|
-
# Add fold indicator with colors
|
4432
4914
|
# Find the item's position in the original split_items array
|
4433
4915
|
real_idx = @split_items.index(item)
|
4916
|
+
|
4917
|
+
# Add line number if enabled
|
4918
|
+
line = ""
|
4919
|
+
if @show_numbers
|
4920
|
+
actual_line_number = real_idx ? real_idx + 1 : 0 # +1 for 1-based line numbers
|
4921
|
+
line = "#{actual_line_number.to_s.rjust(4)} "
|
4922
|
+
end
|
4923
|
+
|
4924
|
+
line += " " * item["level"]
|
4925
|
+
|
4926
|
+
# Add fold indicator with colors
|
4434
4927
|
if real_idx && has_children_in_array?(real_idx, @split_items)
|
4435
4928
|
if item["fold"]
|
4436
4929
|
line += "▶".fg("245") + " "
|
@@ -4966,6 +5459,7 @@ class HyperListApp
|
|
4966
5459
|
end
|
4967
5460
|
|
4968
5461
|
def quit
|
5462
|
+
save_command_history
|
4969
5463
|
Cursor.show
|
4970
5464
|
Rcurses.clear_screen
|
4971
5465
|
exit
|
@@ -5148,8 +5642,8 @@ class HyperListApp
|
|
5148
5642
|
cycle_indent_size
|
5149
5643
|
when "t"
|
5150
5644
|
show_templates
|
5151
|
-
when "D" # Delete line (
|
5152
|
-
delete_line(false) #
|
5645
|
+
when "D" # Delete line only (without children)
|
5646
|
+
delete_line(false) # Delete current line only
|
5153
5647
|
when "C-D" # Delete line and all descendants explicitly
|
5154
5648
|
delete_line(true)
|
5155
5649
|
when "C-E" # Toggle line encryption
|