hyperlist 1.2.7 → 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 +582 -81
- 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
|
@@ -2479,8 +2794,13 @@ class HyperListApp
|
|
2479
2794
|
end
|
2480
2795
|
|
2481
2796
|
def show_help
|
2482
|
-
|
2483
|
-
|
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
|
2484
2804
|
help_lines << " Press #{"?".fg("10")} for full documentation, #{"UP/DOWN".fg("10")} to scroll, or any other key to return"
|
2485
2805
|
help_lines << ""
|
2486
2806
|
help_lines << "#{"HYPERLIST KEY BINDINGS".b}"
|
@@ -2509,7 +2829,7 @@ class HyperListApp
|
|
2509
2829
|
help_lines << help_line("#{"i/Enter".fg("10")}", "Edit line", "#{"o".fg("10")}", "Insert line below")
|
2510
2830
|
help_lines << help_line("#{"O".fg("10")}", "Insert line above", "#{"a".fg("10")}", "Insert child")
|
2511
2831
|
help_lines << help_line("#{"A".fg("10")}", "Insert outdented", "#{"W".fg("10")}", "Save and quit")
|
2512
|
-
help_lines << help_line("#{"I".fg("10")}", "Cycle indent (2-5)"
|
2832
|
+
help_lines << help_line("#{"I".fg("10")}", "Cycle indent (2-5)")
|
2513
2833
|
help_lines << help_line("#{"D".fg("10")}", "Delete+yank line", "#{"C-D".fg("10")}", "Delete+yank item&descendants")
|
2514
2834
|
help_lines << help_line("#{"y".fg("10")}" + "/".fg("10") + "#{"Y".fg("10")}", "Copy line/tree", "#{"p".fg("10")}", "Paste")
|
2515
2835
|
help_lines << help_line("#{"u".fg("10")}", "Undo", "#{".".fg("10")}", "Repeat last action")
|
@@ -2524,7 +2844,7 @@ class HyperListApp
|
|
2524
2844
|
help_lines << help_line("#{"C-E".fg("10")}", "Encrypt/decrypt line", "#{"C-U".fg("10")}", "Toggle State/Trans underline")
|
2525
2845
|
help_lines << help_line("#{"P".fg("10")}", "Presentation mode", "#{"Tab/S-Tab".fg("10")}", "Next/prev sibling (in P)")
|
2526
2846
|
help_lines << help_line("#{"Ma".fg("10")}", "Record macro 'a'", "#{"@a".fg("10")}", "Play macro 'a'")
|
2527
|
-
help_lines << help_line("#{"w".fg("10")}", "Switch panes (split view)"
|
2847
|
+
help_lines << help_line("#{"w".fg("10")}", "Switch panes (split view)")
|
2528
2848
|
help_lines << ""
|
2529
2849
|
help_lines << "#{"FILE OPERATIONS".fg("14")}"
|
2530
2850
|
help_lines << help_line("#{":w".fg("10")}", "Save", "#{":q".fg("10")}", "Quit")
|
@@ -2538,6 +2858,29 @@ class HyperListApp
|
|
2538
2858
|
help_lines << help_line("#{"t".fg("10")}", "Insert template", "#{":st".fg("10")}", "Save as template")
|
2539
2859
|
help_lines << help_line("#{":dt".fg("10")}", "Delete template", "#{":lt".fg("10")}", "List user templates")
|
2540
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
|
+
|
2541
2884
|
help_lines << "#{"HELP & QUIT".fg("14")}"
|
2542
2885
|
help_lines << help_line("#{"?".fg("10")}", "This help", "#{"??".fg("10")}", "Full documentation")
|
2543
2886
|
help_lines << help_line("#{"q".fg("10")}", "Quit (asks to save)", "#{"Q".fg("10")}", "Force quit")
|
@@ -2552,6 +2895,9 @@ class HyperListApp
|
|
2552
2895
|
|
2553
2896
|
help = help_lines.join("\n")
|
2554
2897
|
|
2898
|
+
debug_log.puts "Help generation complete, #{help_lines.length} lines" if debug_log
|
2899
|
+
debug_log.close if debug_log
|
2900
|
+
|
2555
2901
|
# Store current state
|
2556
2902
|
saved_items = @items.dup
|
2557
2903
|
saved_current = @current
|
@@ -2605,6 +2951,18 @@ class HyperListApp
|
|
2605
2951
|
@current = saved_current
|
2606
2952
|
@offset = saved_offset
|
2607
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
|
2608
2966
|
end
|
2609
2967
|
|
2610
2968
|
def show_documentation
|
@@ -3142,6 +3500,9 @@ class HyperListApp
|
|
3142
3500
|
|
3143
3501
|
def handle_command
|
3144
3502
|
@mode = :command
|
3503
|
+
# Set command history (reversed for proper UP arrow navigation)
|
3504
|
+
@footer.history = @command_history.reverse
|
3505
|
+
@footer.record = true
|
3145
3506
|
@command = @footer.ask(":", "")
|
3146
3507
|
@mode = :normal
|
3147
3508
|
@footer.clear # Clear footer immediately
|
@@ -3149,6 +3510,14 @@ class HyperListApp
|
|
3149
3510
|
|
3150
3511
|
return unless @command
|
3151
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
|
+
|
3152
3521
|
case @command
|
3153
3522
|
when "w", "write"
|
3154
3523
|
if @filename
|
@@ -3240,11 +3609,134 @@ class HyperListApp
|
|
3240
3609
|
when "split"
|
3241
3610
|
# Copy current section to split view
|
3242
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
|
3243
3624
|
else
|
3244
3625
|
@message = "Unknown command: #{@command}"
|
3245
3626
|
end
|
3246
3627
|
end
|
3247
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
|
+
|
3248
3740
|
def jump_to_reference
|
3249
3741
|
visible = get_visible_items
|
3250
3742
|
return if @current >= visible.length
|
@@ -4419,11 +4911,19 @@ class HyperListApp
|
|
4419
4911
|
visible_items.each_with_index do |item, idx|
|
4420
4912
|
next unless item
|
4421
4913
|
|
4422
|
-
line = " " * item["level"]
|
4423
|
-
|
4424
|
-
# Add fold indicator with colors
|
4425
4914
|
# Find the item's position in the original split_items array
|
4426
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
|
4427
4927
|
if real_idx && has_children_in_array?(real_idx, @split_items)
|
4428
4928
|
if item["fold"]
|
4429
4929
|
line += "▶".fg("245") + " "
|
@@ -4959,6 +5459,7 @@ class HyperListApp
|
|
4959
5459
|
end
|
4960
5460
|
|
4961
5461
|
def quit
|
5462
|
+
save_command_history
|
4962
5463
|
Cursor.show
|
4963
5464
|
Rcurses.clear_screen
|
4964
5465
|
exit
|