hyperlist 1.4.2 → 1.4.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e6315c6d2ea2f89a99a74e0d1b9e06b0f0aee972f53f8b9b098e0bbe1d38545
4
- data.tar.gz: 21e2b64e3c54fc9969114c6382b3bbe50526022aab77edc13ea1d8b9ebdb562f
3
+ metadata.gz: 3dc0016e32134b62af690e6f612eac1820b601cbf86246fb9969441371600a4e
4
+ data.tar.gz: 9be26ded0329bc112f10a79f2fffdb3c28174812665956a79b4ec1b88c854a6f
5
5
  SHA512:
6
- metadata.gz: cc3ffc31138c89a0bf68ea1636297c7f261884f4e6b734e0dd96b176b85fd53d361fed3f7889d850ace5d7dde70d040e5d6eecb4b339d596b6a7017085c6ee02
7
- data.tar.gz: d5a210e0c40b9f0eccae343c698ef694d418e2feda327b5e341bad60df3d1e24f4e0917b5b3e4920a5db047f98d271596f26a36130f8c273bbc483da9462639e
6
+ metadata.gz: dbe6dbcec4e20da66ff2e7cea0a8f57d7f7001790a3d5313498ae5be5e170509ad777155b09e1b969dc47fdb630923ebebbc1cb17016608615cd3378d3158652
7
+ data.tar.gz: 57bf4c6764337eba7502ccaceb7a158d8ce8dd09c7840cf9c05047e2787910f0d28e446f913456fd7ee8893ec9be5fadb7b1b8f5766aebfd181a4cd7154fc8a6
data/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to the HyperList Ruby TUI will be documented in this file.
4
4
 
5
+ ## [1.4.4] - 2025-09-01
6
+
7
+ ### Added
8
+ - **Case conversion commands**
9
+ - `gU` to convert current line to UPPERCASE
10
+ - `gu` to convert current line to lowercase
11
+ - Works with all HyperList elements (checkboxes, operators, etc.)
12
+
13
+ ### Fixed
14
+ - **Color code display issues**
15
+ - Fixed bracket content extraction in safe_regex_replace to preserve qualifier text
16
+ - Updated operator regex pattern to match after ANSI placeholders
17
+ - Both numbered lists (1.) and operators (NOT:) now color correctly when combined
18
+
19
+ ## [1.4.3] - 2025-08-30
20
+
21
+ ### Fixed
22
+ - **Multi-line item handling**
23
+ - Proper handling of multi-line items per HyperList specification
24
+ - Fixed display and editing of items with embedded newlines
25
+
5
26
  ## [1.4.2] - 2025-08-29
6
27
 
7
28
  ### Fixed
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.4.2 - Terminal User Interface for HyperList files
10
+ HyperList v1.4.3 - Terminal User Interface for HyperList files
11
11
 
12
12
  USAGE
13
13
  hyperlist [OPTIONS] [FILE]
@@ -72,7 +72,7 @@ class HyperListApp
72
72
  include Rcurses::Input
73
73
  include Rcurses::Cursor
74
74
 
75
- VERSION = "1.4.2"
75
+ VERSION = "1.4.4"
76
76
 
77
77
  def initialize(filename = nil)
78
78
  @filename = filename ? File.expand_path(filename) : nil
@@ -253,6 +253,28 @@ class HyperListApp
253
253
  next
254
254
  end
255
255
 
256
+ # Check if this is a continuation line for a multi-line item
257
+ # According to HyperList spec: continuation lines start with space after the indent
258
+ is_continuation = false
259
+ if @items.length > 0
260
+ last_item = @items.last
261
+ # Check if previous item started with + (multi-line indicator)
262
+ if last_item["text"].strip.start_with?("+")
263
+ # For continuation lines, we expect: same indent level + one space prefix
264
+ # Calculate the expected spaces for a continuation line
265
+ expected_spaces = last_item["level"] * @indent_size + 1
266
+ actual_spaces = line[/^ */].length
267
+
268
+ # Check if this matches the continuation pattern
269
+ if actual_spaces == expected_spaces
270
+ # This is a continuation line - append it to the previous item
271
+ continuation_text = line[expected_spaces..-1] || ""
272
+ last_item["text"] += "\n " + continuation_text
273
+ next # Skip adding as a new item
274
+ end
275
+ end
276
+ end
277
+
256
278
  # Detect level based on leading whitespace
257
279
  if line.start_with?("\t")
258
280
  # Tab-based indentation
@@ -589,7 +611,18 @@ class HyperListApp
589
611
 
590
612
  # Prepare content
591
613
  content = @items.map do |item|
592
- (' ' * @indent_size) * item["level"] + item["text"]
614
+ # Handle multi-line items (those with embedded newlines)
615
+ if item["text"].include?("\n")
616
+ lines = item["text"].split("\n")
617
+ first_line = (' ' * @indent_size) * item["level"] + lines[0]
618
+ # Continuation lines get the same indent + space prefix
619
+ continuation_lines = lines[1..-1].map do |line|
620
+ (' ' * @indent_size) * item["level"] + line
621
+ end
622
+ [first_line, *continuation_lines].join("\n")
623
+ else
624
+ (' ' * @indent_size) * item["level"] + item["text"]
625
+ end
593
626
  end.join("\n")
594
627
 
595
628
  # Append config line if present
@@ -935,7 +968,16 @@ class HyperListApp
935
968
  if first_line
936
969
  # First line gets the multi-line indicator if needed
937
970
  if remaining.length <= effective_width
938
- wrapped << (has_plus || wrapped.any? ? "+ #{remaining}" : remaining)
971
+ # If line already had +, it was removed earlier, so we add it back
972
+ # If line didn't have + but needs wrapping (wrapped.any?), add +
973
+ # Otherwise, leave as is
974
+ if has_plus
975
+ wrapped << "+ #{remaining}"
976
+ elsif wrapped.any?
977
+ wrapped << "+ #{remaining}"
978
+ else
979
+ wrapped << remaining
980
+ end
939
981
  break
940
982
  else
941
983
  # Find a good break point (prefer spaces)
@@ -971,14 +1013,28 @@ class HyperListApp
971
1013
  in_literal_block = false
972
1014
  literal_start_level = -1
973
1015
 
1016
+ # Track which line number each item starts at (for scroll calculation)
1017
+ item_line_starts = {}
1018
+
974
1019
  visible_items.each_with_index do |item, idx|
975
1020
  next unless item
976
1021
 
977
- # Handle line wrapping if enabled
978
- if @wrap
979
- text_lines = wrap_line(item["text"], @cols, item["level"])
980
- else
981
- text_lines = [item["text"]]
1022
+ # Record where this item starts in the display
1023
+ item_line_starts[idx] = lines.length
1024
+
1025
+ # Handle line wrapping and multi-line items
1026
+ # First, split by embedded newlines (from multi-line items with + indicator)
1027
+ embedded_lines = item["text"].split("\n")
1028
+ text_lines = []
1029
+
1030
+ embedded_lines.each do |embedded_line|
1031
+ if @wrap
1032
+ # Wrap each embedded line separately
1033
+ wrapped = wrap_line(embedded_line, @cols, item["level"])
1034
+ text_lines.concat(wrapped)
1035
+ else
1036
+ text_lines << embedded_line
1037
+ end
982
1038
  end
983
1039
 
984
1040
  text_lines.each_with_index do |text_line, line_idx|
@@ -1087,23 +1143,26 @@ class HyperListApp
1087
1143
  end
1088
1144
  end
1089
1145
 
1090
- # Calculate scroll position exactly like RTFM does, but account for wrapping
1091
- # Treat the content as having one extra line (the blank line at bottom)
1146
+ # Calculate scroll position based on actual line positions, not item indices
1147
+ # The current item starts at line item_line_starts[@current]
1092
1148
  scrolloff = 3
1093
- total = visible_items.length + 1 # +1 for the blank line
1094
- page = @main.h
1149
+ total_lines = lines.length # Total number of actual display lines
1150
+ page = @main.h # Height of the display area
1151
+
1152
+ # Get the actual line where the current item starts
1153
+ current_line = item_line_starts[@current] || 0
1095
1154
 
1096
- if total <= page
1155
+ if total_lines <= page
1097
1156
  # If everything fits, always start from the very top
1098
1157
  @main.ix = 0
1099
- elsif @current - @main.ix < scrolloff
1158
+ elsif current_line - @main.ix < scrolloff
1100
1159
  # If we're too close to the top of the pane, scroll up
1101
- @main.ix = [@current - scrolloff, 0].max
1102
- elsif (@main.ix + page - 1 - @current) < scrolloff
1160
+ @main.ix = [current_line - scrolloff, 0].max
1161
+ elsif (@main.ix + page - 1 - current_line) < scrolloff
1103
1162
  # If we're too close to the bottom of the pane, scroll down
1104
- # Account for wrapped lines dynamically
1105
- max_off = [total - page + extra_wrapped_lines, 0].max
1106
- @main.ix = [@current + scrolloff - page + 1, max_off].min
1163
+ # Make sure we don't scroll past the last line
1164
+ max_scroll = [total_lines - page, 0].max
1165
+ @main.ix = [current_line + scrolloff - page + 1, max_scroll].min
1107
1166
  end
1108
1167
 
1109
1168
  @main.refresh
@@ -1278,7 +1337,10 @@ class HyperListApp
1278
1337
  elsif !processed_checkbox
1279
1338
  # Only handle other qualifiers if we didn't process a checkbox
1280
1339
  # Based on hyperlist.vim: '\[.\{-}\]'
1281
- result.gsub!(/\[([^\]]*)\]/) { "[#{$1}]".fg(colors["green"]) } # Green for all qualifiers
1340
+ result = safe_regex_replace(result, /\[([^\]]*)\]/) do |match|
1341
+ content = match[1..-2] # Extract content between brackets
1342
+ "[#{content}]".fg(colors["green"]) # Green for all qualifiers
1343
+ end
1282
1344
  end
1283
1345
 
1284
1346
  # We'll handle parentheses AFTER operators/properties to avoid conflicts
@@ -1292,21 +1354,27 @@ class HyperListApp
1292
1354
  # Handle operators and properties with colon pattern
1293
1355
  # Operators: ALL-CAPS followed by colon (with or without space)
1294
1356
  # Properties: Mixed case followed by colon and space
1295
- result.gsub!(/(\A|\s+)([a-zA-Z][a-zA-Z0-9_\-() .\/=]*):(\s*)/) do
1296
- prefix_space = $1
1297
- text_part = $2
1298
- space_after = $3 || ""
1299
- colon_space = ":#{space_after}"
1300
-
1301
- # Check if it's an operator (ALL-CAPS with optional _, -, (), /, =, spaces)
1302
- if text_part =~ /^[A-Z][A-Z_\-() \/=]*$/
1303
- prefix_space + text_part.fg(colors["blue"]) + colon_space.fg(colors["blue"]) # Blue for operators (including S: and T:)
1304
- elsif text_part.length >= 2 && space_after.include?(" ")
1305
- # It's a property (mixed case, at least 2 chars, has space after colon)
1306
- prefix_space + text_part.fg(colors["red"]) + colon_space.fg(colors["red"]) # Red for properties
1357
+ # Modified pattern to also match after ANSI placeholders (⟨ANSI\d+⟩)
1358
+ result = safe_regex_replace(result, /(\A|\s+|⟨ANSI\d+⟩)([a-zA-Z][a-zA-Z0-9_\-() .\/=]*):(\s*)/) do |match|
1359
+ # Extract parts from the match
1360
+ if match =~ /(\A|\s+|⟨ANSI\d+⟩)([a-zA-Z][a-zA-Z0-9_\-() .\/=]*):(\s*)/
1361
+ prefix_space = $1
1362
+ text_part = $2
1363
+ space_after = $3 || ""
1364
+ colon_space = ":#{space_after}"
1365
+
1366
+ # Check if it's an operator (ALL-CAPS with optional _, -, (), /, =, spaces)
1367
+ if text_part =~ /^[A-Z][A-Z_\-() \/=]*$/
1368
+ prefix_space + text_part.fg(colors["blue"]) + colon_space.fg(colors["blue"]) # Blue for operators (including S: and T:)
1369
+ elsif text_part.length >= 2 && space_after.include?(" ")
1370
+ # It's a property (mixed case, at least 2 chars, has space after colon)
1371
+ prefix_space + text_part.fg(colors["red"]) + colon_space.fg(colors["red"]) # Red for properties
1372
+ else
1373
+ # Leave as is
1374
+ match
1375
+ end
1307
1376
  else
1308
- # Leave as is
1309
- prefix_space + text_part + colon_space
1377
+ match
1310
1378
  end
1311
1379
  end
1312
1380
 
@@ -2275,6 +2343,34 @@ class HyperListApp
2275
2343
  clear_cache # Clear cache when content changes
2276
2344
  end
2277
2345
 
2346
+ def uppercase_line
2347
+ visible = get_visible_items
2348
+ return if @current >= visible.length
2349
+
2350
+ item = visible[@current]
2351
+ real_idx = get_real_index(item)
2352
+
2353
+ save_undo_state # Save state before modification
2354
+ @items[real_idx]["text"] = item["text"].upcase
2355
+ @modified = true
2356
+ clear_cache # Clear cache when content changes
2357
+ @message = "Line converted to uppercase"
2358
+ end
2359
+
2360
+ def lowercase_line
2361
+ visible = get_visible_items
2362
+ return if @current >= visible.length
2363
+
2364
+ item = visible[@current]
2365
+ real_idx = get_real_index(item)
2366
+
2367
+ save_undo_state # Save state before modification
2368
+ @items[real_idx]["text"] = item["text"].downcase
2369
+ @modified = true
2370
+ clear_cache # Clear cache when content changes
2371
+ @message = "Line converted to lowercase"
2372
+ end
2373
+
2278
2374
  def delete_line(with_children = false)
2279
2375
  visible = get_visible_items
2280
2376
  return if visible.empty?
@@ -2824,6 +2920,7 @@ class HyperListApp
2824
2920
  help_lines << help_line("#{"i/Enter".fg("10")}", "Edit line", "#{"o".fg("10")}", "Insert line below")
2825
2921
  help_lines << help_line("#{"O".fg("10")}", "Insert line above", "#{"a".fg("10")}", "Insert child")
2826
2922
  help_lines << help_line("#{"A".fg("10")}", "Insert outdented", "#{"W".fg("10")}", "Save and quit")
2923
+ help_lines << help_line("#{"gU".fg("10")}", "Uppercase line", "#{"gu".fg("10")}", "Lowercase line")
2827
2924
  help_lines << help_line("#{"I".fg("10")}", "Cycle indent (2-5)")
2828
2925
  help_lines << help_line("#{"D".fg("10")}", "Delete+yank line", "#{"C-D".fg("10")}", "Delete+yank item&descendants")
2829
2926
  help_lines << help_line("#{"y".fg("10")}" + "/".fg("10") + "#{"Y".fg("10")}", "Copy line/tree", "#{"p".fg("10")}", "Paste")
@@ -4874,46 +4971,77 @@ class HyperListApp
4874
4971
 
4875
4972
  # Build ALL lines for the pane (like we do for main pane)
4876
4973
  lines = []
4974
+
4975
+ # Track which line number each item starts at (for scroll calculation)
4976
+ item_line_starts = {}
4977
+
4877
4978
  visible_items.each_with_index do |item, idx|
4878
4979
  next unless item
4879
4980
 
4981
+ # Record where this item starts in the display
4982
+ item_line_starts[idx] = lines.length
4983
+
4880
4984
  # Find the item's position in the original split_items array
4881
4985
  real_idx = @split_items.index(item)
4882
4986
 
4883
- # Add line number if enabled
4884
- line = ""
4885
- if @show_numbers
4886
- actual_line_number = real_idx ? real_idx + 1 : 0 # +1 for 1-based line numbers
4887
- line = "#{actual_line_number.to_s.rjust(4)} "
4888
- end
4889
-
4890
- line += " " * item["level"]
4987
+ # Handle multi-line items (split by embedded newlines)
4988
+ embedded_lines = item["text"].split("\n")
4989
+ text_lines = []
4891
4990
 
4892
- # Add fold indicator with colors
4893
- if real_idx && has_children_in_array?(real_idx, @split_items)
4894
- if item["fold"]
4895
- line += "▶".fg("245") + " "
4991
+ embedded_lines.each do |embedded_line|
4992
+ if @wrap
4993
+ # Wrap each embedded line separately
4994
+ wrapped = wrap_line(embedded_line, @split_pane.w - 10, item["level"])
4995
+ text_lines.concat(wrapped)
4896
4996
  else
4897
- line += "▷".fg("245") + " "
4997
+ text_lines << embedded_line
4898
4998
  end
4899
- else
4900
- line += " "
4901
4999
  end
4902
5000
 
4903
- # Apply process_text for syntax highlighting
4904
- processed = process_text(item["text"], false)
4905
- line += processed
4906
-
4907
- # Apply background highlighting for current item in split pane
4908
- if idx == @split_current
4909
- # Choose background color based on whether this is the active pane
4910
- bg_color = @active_pane == :split ? "237" : "234"
4911
- bg_code = "\e[48;5;#{bg_color}m"
4912
- reset_bg = "\e[49m"
4913
- line = bg_code + line.gsub(/\e\[49m/, '') + reset_bg
5001
+ text_lines.each_with_index do |text_line, line_idx|
5002
+ # Add line number if enabled (only on first line)
5003
+ line = ""
5004
+ if @show_numbers
5005
+ if line_idx == 0
5006
+ actual_line_number = real_idx ? real_idx + 1 : 0 # +1 for 1-based line numbers
5007
+ line = "#{actual_line_number.to_s.rjust(4)} "
5008
+ else
5009
+ line = " " # Empty space for continuation lines
5010
+ end
5011
+ end
5012
+
5013
+ line += " " * item["level"]
5014
+
5015
+ # Add fold indicator with colors (only on first line)
5016
+ if line_idx == 0
5017
+ if real_idx && has_children_in_array?(real_idx, @split_items)
5018
+ if item["fold"]
5019
+ line += "▶".fg("245") + " "
5020
+ else
5021
+ line += "▷".fg("245") + " "
5022
+ end
5023
+ else
5024
+ line += " "
5025
+ end
5026
+ else
5027
+ line += " " # Just spacing for continuation lines
5028
+ end
5029
+
5030
+ # Apply process_text for syntax highlighting
5031
+ processed = process_text(text_line, false)
5032
+ line += processed
5033
+
5034
+ # Apply background highlighting for current item in split pane (all lines)
5035
+ if idx == @split_current
5036
+ # Choose background color based on whether this is the active pane
5037
+ bg_color = @active_pane == :split ? "237" : "234"
5038
+ bg_code = "\e[48;5;#{bg_color}m"
5039
+ reset_bg = "\e[49m"
5040
+ line = bg_code + line.gsub(/\e\[49m/, '') + reset_bg
5041
+ end
5042
+
5043
+ lines << line
4914
5044
  end
4915
-
4916
- lines << line
4917
5045
  end
4918
5046
 
4919
5047
  # Add a blank line at the bottom to show end of document
@@ -4934,22 +5062,25 @@ class HyperListApp
4934
5062
  end
4935
5063
  end
4936
5064
 
4937
- # Calculate scroll position exactly like RTFM does, but account for wrapping
5065
+ # Calculate scroll position based on actual line positions, not item indices
4938
5066
  scrolloff = 3
4939
- total = visible_items.length + 1 # +1 for the blank line
4940
- page = @split_pane.h
5067
+ total_lines = lines.length # Total number of actual display lines
5068
+ page = @split_pane.h # Height of the display area
5069
+
5070
+ # Get the actual line where the current item starts
5071
+ current_line = item_line_starts[@split_current] || 0
4941
5072
 
4942
- if total <= page
5073
+ if total_lines <= page
4943
5074
  # If everything fits, always start from the very top
4944
5075
  @split_pane.ix = 0
4945
- elsif @split_current - @split_pane.ix < scrolloff
5076
+ elsif current_line - @split_pane.ix < scrolloff
4946
5077
  # If we're too close to the top of the pane, scroll up
4947
- @split_pane.ix = [@split_current - scrolloff, 0].max
4948
- elsif (@split_pane.ix + page - 1 - @split_current) < scrolloff
5078
+ @split_pane.ix = [current_line - scrolloff, 0].max
5079
+ elsif (@split_pane.ix + page - 1 - current_line) < scrolloff
4949
5080
  # If we're too close to the bottom of the pane, scroll down
4950
- # Account for wrapped lines dynamically
4951
- max_off = [total - page + extra_wrapped_lines, 0].max
4952
- @split_pane.ix = [@split_current + scrolloff - page + 1, max_off].min
5081
+ # Make sure we don't scroll past the last line
5082
+ max_scroll = [total_lines - page, 0].max
5083
+ @split_pane.ix = [current_line + scrolloff - page + 1, max_scroll].min
4953
5084
  end
4954
5085
 
4955
5086
  @split_pane.refresh
@@ -5520,13 +5651,21 @@ class HyperListApp
5520
5651
  @current = [visible.length - 1, 0].max
5521
5652
  update_presentation_focus if @presentation_mode
5522
5653
  end
5523
- when "g" # Go to top (was gg)
5524
- if @split_view && @active_pane == :split
5525
- @split_current = 0
5526
- else
5527
- @current = 0
5528
- @offset = 0
5529
- update_presentation_focus if @presentation_mode
5654
+ when "g" # Go to top (gg) or text case commands (gU/gu)
5655
+ next_c = getchr
5656
+ case next_c
5657
+ when "g" # gg - go to top
5658
+ if @split_view && @active_pane == :split
5659
+ @split_current = 0
5660
+ else
5661
+ @current = 0
5662
+ @offset = 0
5663
+ update_presentation_focus if @presentation_mode
5664
+ end
5665
+ when "U" # gU - uppercase line
5666
+ uppercase_line
5667
+ when "u" # gu - lowercase line
5668
+ lowercase_line
5530
5669
  end
5531
5670
  when "G" # Go to bottom
5532
5671
  if @split_view && @active_pane == :split
data/hyperlist.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "hyperlist"
3
- spec.version = "1.4.2"
3
+ spec.version = "1.4.4"
4
4
  spec.authors = ["Geir Isene"]
5
5
  spec.email = ["g@isene.com"]
6
6
 
@@ -1,77 +1,401 @@
1
1
  <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
- <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
3
- <defs>
4
- <!-- Terminal glow effect -->
5
- <filter id="glow">
6
- <feGaussianBlur stdDeviation="3" result="coloredBlur"/>
7
- <feMerge>
8
- <feMergeNode in="coloredBlur"/>
9
- <feMergeNode in="SourceGraphic"/>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+
4
+ <svg
5
+ width="134.72328mm"
6
+ height="134.55478mm"
7
+ viewBox="0 0 134.72328 134.55478"
8
+ version="1.1"
9
+ id="svg1"
10
+ xml:space="preserve"
11
+ sodipodi:docname="hyperlistlogo.svg"
12
+ inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
13
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
14
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
15
+ xmlns="http://www.w3.org/2000/svg"
16
+ xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
17
+ id="namedview1"
18
+ pagecolor="#ffffff"
19
+ bordercolor="#000000"
20
+ borderopacity="0.25"
21
+ inkscape:showpageshadow="2"
22
+ inkscape:pageopacity="0.0"
23
+ inkscape:pagecheckerboard="0"
24
+ inkscape:deskcolor="#d1d1d1"
25
+ inkscape:document-units="mm"
26
+ inkscape:zoom="1.332716"
27
+ inkscape:cx="231.85734"
28
+ inkscape:cy="219.10144"
29
+ inkscape:window-width="1920"
30
+ inkscape:window-height="951"
31
+ inkscape:window-x="0"
32
+ inkscape:window-y="29"
33
+ inkscape:window-maximized="1"
34
+ inkscape:current-layer="svg1" />
35
+ <defs
36
+ id="defs1">
37
+ <linearGradient
38
+ id="linearGradient33"
39
+ x1="0"
40
+ y1="0"
41
+ x2="1"
42
+ y2="1">
43
+ <stop
44
+ offset="0"
45
+ style="stop-color:#00fa86;stop-opacity:1;"
46
+ id="stop32" />
47
+ <stop
48
+ offset="1"
49
+ style="stop-color:#00cc66;stop-opacity:1"
50
+ id="stop33" />
51
+ </linearGradient>
52
+ <filter
53
+ id="glow"
54
+ x="-1.184409"
55
+ y="-2.3688181"
56
+ width="3.3688181"
57
+ height="5.7376362">
58
+ <feGaussianBlur
59
+ stdDeviation="2.9610226"
60
+ result="coloredBlur"
61
+ id="feGaussianBlur1" />
62
+ <feMerge
63
+ id="feMerge2">
64
+ <feMergeNode
65
+ in="coloredBlur"
66
+ id="feMergeNode1" />
67
+ <feMergeNode
68
+ in="SourceGraphic"
69
+ id="feMergeNode2" />
10
70
  </feMerge>
11
71
  </filter>
12
- <!-- Gradient for depth -->
13
- <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
14
- <stop offset="0%" style="stop-color:#00ff88;stop-opacity:1" />
15
- <stop offset="100%" style="stop-color:#00cc66;stop-opacity:1" />
72
+ <linearGradient
73
+ id="grad"
74
+ x1="0"
75
+ y1="0"
76
+ x2="1"
77
+ y2="1">
78
+ <stop
79
+ offset="0%"
80
+ style="stop-color:#00ff88;stop-opacity:1"
81
+ id="stop2" />
82
+ <stop
83
+ offset="100%"
84
+ style="stop-color:#00cc66;stop-opacity:1"
85
+ id="stop3" />
16
86
  </linearGradient>
87
+ <filter
88
+ style="color-interpolation-filters:sRGB"
89
+ id="filter6"
90
+ x="-0.39473927"
91
+ y="-0.17425425"
92
+ width="1.7894785"
93
+ height="1.3485085">
94
+ <feGaussianBlur
95
+ stdDeviation="0.4 0.4"
96
+ result="fbSourceGraphic"
97
+ id="feGaussianBlur6" />
98
+ <feColorMatrix
99
+ result="fbSourceGraphicAlpha"
100
+ in="fbSourceGraphic"
101
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
102
+ id="feColorMatrix8" />
103
+ <feGaussianBlur
104
+ id="feGaussianBlur8"
105
+ stdDeviation="0.4 0.4"
106
+ result="blur"
107
+ in="fbSourceGraphic" />
108
+ </filter>
109
+ <filter
110
+ style="color-interpolation-filters:sRGB"
111
+ id="filter22"
112
+ x="-0.0064666744"
113
+ y="-0.45590055"
114
+ width="1.0129333"
115
+ height="1.9118011">
116
+ <feGaussianBlur
117
+ stdDeviation="0.01 0.1"
118
+ result="blur"
119
+ id="feGaussianBlur22" />
120
+ </filter>
121
+ <filter
122
+ style="color-interpolation-filters:sRGB"
123
+ id="filter23"
124
+ x="-0.00056465436"
125
+ y="-0.20969008"
126
+ width="1.0011293"
127
+ height="1.4193802">
128
+ <feGaussianBlur
129
+ stdDeviation="0.01 0.11"
130
+ result="blur"
131
+ id="feGaussianBlur23" />
132
+ </filter>
133
+ <filter
134
+ id="filter28"
135
+ x="-0.040088417"
136
+ y="-0.21380489"
137
+ width="1.0801768"
138
+ height="1.4276098">
139
+ <feGaussianBlur
140
+ stdDeviation="2.1380488"
141
+ result="coloredBlur"
142
+ id="feGaussianBlur27" />
143
+ <feMerge
144
+ id="feMerge28">
145
+ <feMergeNode
146
+ in="coloredBlur"
147
+ id="feMergeNode27" />
148
+ <feMergeNode
149
+ in="SourceGraphic"
150
+ id="feMergeNode28" />
151
+ </feMerge>
152
+ </filter>
153
+ <filter
154
+ id="filter35"
155
+ x="-0.40035462"
156
+ y="-0.048042555"
157
+ width="1.8007092"
158
+ height="1.0960851">
159
+ <feGaussianBlur
160
+ stdDeviation="4.003546"
161
+ result="coloredBlur"
162
+ id="feGaussianBlur34" />
163
+ <feMerge
164
+ id="feMerge35">
165
+ <feMergeNode
166
+ in="coloredBlur"
167
+ id="feMergeNode34" />
168
+ <feMergeNode
169
+ in="SourceGraphic"
170
+ id="feMergeNode35" />
171
+ </feMerge>
172
+ </filter>
173
+ <filter
174
+ style="color-interpolation-filters:sRGB"
175
+ id="filter36"
176
+ x="-0.092322366"
177
+ y="-0.059838278"
178
+ width="1.1846447"
179
+ height="1.1196766">
180
+ <feGaussianBlur
181
+ stdDeviation="0.25392894"
182
+ id="feGaussianBlur36" />
183
+ </filter>
17
184
  </defs>
185
+ <rect
186
+ width="135.46666"
187
+ height="135.46666"
188
+ fill="#0d1117"
189
+ id="rect3"
190
+ x="-0.15747385"
191
+ y="-0.34253129"
192
+ style="stroke-width:0.264583" />
193
+ <rect
194
+ x="8.3091898"
195
+ y="8.124136"
196
+ width="118.27769"
197
+ height="10.583333"
198
+ rx="4.2242026"
199
+ ry="4.2333331"
200
+ fill="#161b22"
201
+ id="rect5"
202
+ style="stroke-width:0.264298" />
203
+ <rect
204
+ x="8.3091898"
205
+ y="8.124136"
206
+ width="118.53333"
207
+ height="118.53333"
208
+ rx="4.2333331"
209
+ ry="4.2333331"
210
+ fill="none"
211
+ stroke="#00ff88"
212
+ stroke-width="0.79375"
213
+ opacity="0.8"
214
+ id="rect4" />
215
+ <circle
216
+ cx="14.659192"
217
+ cy="13.4158"
218
+ fill="#ff5f56"
219
+ id="circle5"
220
+ style="stroke-width:0.264583"
221
+ r="1.5875" />
222
+ <circle
223
+ cx="19.950855"
224
+ cy="13.4158"
225
+ fill="#ffbd2e"
226
+ id="circle6"
227
+ style="stroke-width:0.264583"
228
+ r="1.5875" />
229
+ <circle
230
+ cx="25.242527"
231
+ cy="13.4158"
232
+ fill="#27c93f"
233
+ id="circle7"
234
+ style="stroke-width:0.264583"
235
+ r="1.5875" />
236
+ <rect
237
+ x="96"
238
+ y="120"
239
+ width="24"
240
+ height="200"
241
+ fill="url(#grad)"
242
+ id="rect7"
243
+ style="fill:#00e878;fill-opacity:1;filter:url(#glow)"
244
+ transform="matrix(0.26458333,0,0,0.26458333,-0.15747435,-0.3425329)" />
245
+ <rect
246
+ x="200"
247
+ y="120"
248
+ width="24"
249
+ height="200"
250
+ fill="url(#grad)"
251
+ id="rect8"
252
+ style="fill:#00e979;fill-opacity:1;filter:url(#filter35)"
253
+ transform="matrix(0.26458333,0,0,0.26458333,-0.15747435,-0.3425329)" />
254
+ <rect
255
+ x="96"
256
+ y="208"
257
+ width="128"
258
+ height="24"
259
+ fill="url(#grad)"
260
+ id="rect9"
261
+ style="mix-blend-mode:normal;fill:#00ea7a;fill-opacity:1;filter:url(#filter28)"
262
+ transform="matrix(0.26111189,0,0,0.26713774,0.40397665,-0.8584999)" />
263
+ <rect
264
+ x="260"
265
+ y="160"
266
+ width="8"
267
+ height="8"
268
+ fill="#00ff88"
269
+ id="rect11"
270
+ transform="matrix(0.26458333,0,0,0.26458333,-0.15747435,-0.3425329)"
271
+ style="filter:url(#glow)" />
272
+ <rect
273
+ x="276"
274
+ y="162"
275
+ width="100"
276
+ height="4"
277
+ fill="#00ff88"
278
+ opacity="0.6"
279
+ id="rect12"
280
+ transform="matrix(0.26458333,0,0,0.26458333,-0.15747435,-0.3425329)"
281
+ style="filter:url(#glow)" />
282
+ <rect
283
+ x="260"
284
+ y="190"
285
+ width="12"
286
+ height="12"
287
+ fill="none"
288
+ stroke="#00ff88"
289
+ stroke-width="2"
290
+ id="rect13"
291
+ transform="matrix(0.26458333,0,0,0.26458333,-0.15747435,-0.3425329)"
292
+ style="filter:url(#glow)" />
293
+ <polyline
294
+ points="263,196 266,199 270,193"
295
+ stroke="#00ff88"
296
+ stroke-width="2"
297
+ fill="none"
298
+ id="polyline13"
299
+ transform="matrix(0.26458333,0,0,0.26458333,-0.15747435,-0.3425329)"
300
+ style="filter:url(#glow)" />
301
+ <rect
302
+ x="280"
303
+ y="194"
304
+ width="96"
305
+ height="4"
306
+ fill="#00ff88"
307
+ opacity="0.6"
308
+ id="rect14"
309
+ transform="matrix(0.26458333,0,0,0.26458333,-0.15747435,-0.3425329)"
310
+ style="filter:url(#glow)" />
311
+ <rect
312
+ x="260"
313
+ y="220"
314
+ width="8"
315
+ height="8"
316
+ fill="#00ff88"
317
+ id="rect15"
318
+ transform="matrix(0.26458333,0,0,0.26458333,-0.15747435,-0.3425329)"
319
+ style="filter:url(#glow)" />
320
+ <rect
321
+ x="276"
322
+ y="222"
323
+ width="100"
324
+ height="4"
325
+ fill="#00ff88"
326
+ opacity="0.6"
327
+ id="rect16"
328
+ transform="matrix(0.26458333,0,0,0.26458333,-0.15747435,-0.3425329)"
329
+ style="filter:url(#glow)" />
330
+ <rect
331
+ x="288"
332
+ y="250"
333
+ width="6"
334
+ height="6"
335
+ fill="#00cc66"
336
+ id="rect17"
337
+ transform="matrix(0.26458333,0,0,0.26458333,-0.15747435,-0.3425329)"
338
+ style="filter:url(#glow)" />
339
+ <rect
340
+ x="300"
341
+ y="251"
342
+ width="76"
343
+ height="3"
344
+ fill="#00cc66"
345
+ opacity="0.5"
346
+ id="rect18"
347
+ transform="matrix(0.26458333,0,0,0.26458333,-0.15747435,-0.3425329)"
348
+ style="filter:url(#glow)" />
349
+ <rect
350
+ x="260"
351
+ y="280"
352
+ width="8"
353
+ height="8"
354
+ fill="#00ff88"
355
+ id="rect19"
356
+ transform="matrix(0.26458333,0,0,0.26458333,-0.15747435,-0.3425329)"
357
+ style="filter:url(#glow)" />
358
+ <rect
359
+ x="276"
360
+ y="282"
361
+ width="100"
362
+ height="4"
363
+ fill="#00ff88"
364
+ opacity="0.6"
365
+ id="rect20"
366
+ transform="matrix(0.26458333,0,0,0.26458333,-0.15747435,-0.3425329)"
367
+ style="filter:url(#glow)" />
368
+ <text
369
+ x="24.334553"
370
+ y="97.818535"
371
+ font-family="monospace"
372
+ font-size="4.57279px"
373
+ fill="#00ff88"
374
+ opacity="0.4"
375
+ id="text21"
376
+ style="stroke-width:0.326627">hyperlist v1.4.0 $</text>
377
+ <rect
378
+ style="fill:#00fe87;fill-opacity:1;stroke:none;stroke-width:5.98157;filter:url(#filter22)"
379
+ id="rect22"
380
+ width="3.7113357"
381
+ height="0.52643061"
382
+ x="68.35778"
383
+ y="52.855618"
384
+ ry="0" />
385
+ <rect
386
+ style="fill:#00e878;fill-opacity:1;stroke:none;stroke-width:6.174;filter:url(#filter36)"
387
+ id="rect35"
388
+ width="6.6011028"
389
+ height="10.184608"
390
+ x="101.5478"
391
+ y="73.953667"
392
+ ry="0.096021399">
393
+ <animate
394
+ attributeName="opacity"
395
+ values="1;0;1"
396
+ dur="1s"
397
+ repeatCount="indefinite" />
398
+ </rect>
399
+ </svg>
400
+ <!-- Terminal cursor blink -->
18
401
 
19
- <!-- Dark terminal background -->
20
- <rect width="512" height="512" fill="#0d1117"/>
21
-
22
- <!-- Terminal frame with rounded corners -->
23
- <rect x="32" y="32" width="448" height="448" rx="16" ry="16"
24
- fill="none" stroke="#00ff88" stroke-width="3" opacity="0.8"/>
25
-
26
- <!-- Terminal header bar -->
27
- <rect x="32" y="32" width="448" height="40" rx="16" ry="16" fill="#161b22"/>
28
- <circle cx="56" cy="52" r="6" fill="#ff5f56"/>
29
- <circle cx="76" cy="52" r="6" fill="#ffbd2e"/>
30
- <circle cx="96" cy="52" r="6" fill="#27c93f"/>
31
-
32
- <!-- Main HyperList logo design -->
33
- <g filter="url(#glow)">
34
- <!-- Large H that forms into list structure -->
35
- <!-- Left vertical of H -->
36
- <rect x="96" y="120" width="24" height="200" fill="url(#grad)"/>
37
- <!-- Right vertical of H -->
38
- <rect x="200" y="120" width="24" height="200" fill="url(#grad)"/>
39
- <!-- Horizontal crossbar of H -->
40
- <rect x="96" y="208" width="128" height="24" fill="url(#grad)"/>
41
-
42
- <!-- List items branching from H -->
43
- <!-- Main branch -->
44
- <rect x="224" y="208" width="160" height="4" fill="#00ff88" opacity="0.8"/>
45
-
46
- <!-- List item 1 -->
47
- <rect x="260" y="160" width="8" height="8" fill="#00ff88"/>
48
- <rect x="276" y="162" width="100" height="4" fill="#00ff88" opacity="0.6"/>
49
-
50
- <!-- List item 2 with checkbox -->
51
- <rect x="260" y="190" width="12" height="12" fill="none" stroke="#00ff88" stroke-width="2"/>
52
- <polyline points="263,196 266,199 270,193" stroke="#00ff88" stroke-width="2" fill="none"/>
53
- <rect x="280" y="194" width="96" height="4" fill="#00ff88" opacity="0.6"/>
54
-
55
- <!-- List item 3 -->
56
- <rect x="260" y="220" width="8" height="8" fill="#00ff88"/>
57
- <rect x="276" y="222" width="100" height="4" fill="#00ff88" opacity="0.6"/>
58
-
59
- <!-- Nested item -->
60
- <rect x="288" y="250" width="6" height="6" fill="#00cc66"/>
61
- <rect x="300" y="251" width="76" height="3" fill="#00cc66" opacity="0.5"/>
62
-
63
- <!-- List item 4 -->
64
- <rect x="260" y="280" width="8" height="8" fill="#00ff88"/>
65
- <rect x="276" y="282" width="100" height="4" fill="#00ff88" opacity="0.6"/>
66
-
67
- <!-- Terminal cursor blink -->
68
- <rect x="384" y="280" width="16" height="24" fill="#00ff88">
69
- <animate attributeName="opacity" values="1;0;1" dur="1s" repeatCount="indefinite"/>
70
- </rect>
71
- </g>
72
-
73
- <!-- Subtle terminal prompt at bottom -->
74
- <text x="96" y="380" font-family="monospace" font-size="14" fill="#00ff88" opacity="0.4">
75
- hyperlist v1.0.0 $
76
- </text>
77
- </svg>
data/sample.hl CHANGED
@@ -81,5 +81,29 @@ HyperList Sample Document
81
81
  _Underlined text example_
82
82
  Combined: *bold and /italic/ together*
83
83
  Hash tags: #important #urgent #review
84
+ Multi-line Items (HyperList spec)
85
+ + This is a multi-line item that continues
86
+ on the next line with a space prefix
87
+ + Items with the plus sign can span multiple lines
88
+ making it easy to write longer descriptions
89
+ without creating separate items
90
+ Consistency Rule Example
91
+ 1 When one item at a level has a starter (identifier)
92
+ all items at that level need starters
93
+ 2 This second item also needs an identifier
94
+ + Or you can use a plus sign as starter
95
+ Nested Multi-line Items
96
+ + Parent item that spans multiple lines
97
+ with detailed information on the first level
98
+ + Child item also spanning multiple lines
99
+ providing more specific details
100
+ Regular child item after multi-line
101
+ Real-world Examples
102
+ + Meeting notes from project kickoff: Discussed timeline,
103
+ budget constraints, resource allocation, and identified
104
+ key stakeholders who need to be involved
105
+ + Action item: Research competitive solutions in the market,
106
+ compile findings into a report, and present recommendations
107
+ to the management team by end of month
84
108
 
85
109
  ((fold_level=3))
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.4.2
4
+ version: 1.4.4
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-29 00:00:00.000000000 Z
11
+ date: 2025-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rcurses
@@ -79,7 +79,6 @@ files:
79
79
  - img/screenshot_help.png
80
80
  - img/screenshot_sample.png
81
81
  - sample.hl
82
- - test.hl
83
82
  homepage: https://github.com/isene/HyperList
84
83
  licenses:
85
84
  - Unlicense
data/test.hl DELETED
@@ -1,154 +0,0 @@
1
- HyperList Ruby TUI Test Suite v1.0
2
- About This Test Suite
3
- Purpose: Test all features of the HyperList Ruby TUI application
4
- Run with: ./hyperlist test.hl
5
- Navigate through and test each feature systematically
6
- SYNTAX HIGHLIGHTING & COLORS
7
- Check: Property in red; Dates like 2025-08-12 should be red
8
- AND: OR: IF: THEN: Operators should appear in blue
9
- [?] Conditional checkboxes should be green
10
- #hashtags should appear in yellow/orange
11
- <references> should appear in magenta
12
- (parentheses content) should be in cyan
13
- "quoted strings" should be in bright cyan
14
- *bold text* should appear bold
15
- /italic text/ should appear italic
16
- _underlined text_ should appear underlined
17
- Templates with = markers for filling
18
- NAVIGATION TESTS
19
- Basic Movement
20
- j/↓ Move down through items
21
- k/↑ Move up through items
22
- h Go to parent item (move cursor here and press h)
23
- l Go to first child (press l from parent)
24
- g/Home Go to top of document
25
- G/End Go to bottom of document
26
- PgUp/PgDn Page up and down
27
- Search
28
- Slash key: Search for text (try searching for "test")
29
- n Jump to next search match
30
- Marks and Jumps
31
- ma Set mark 'a' at current position
32
- 'a Jump back to mark 'a'
33
- '' Jump to previous position
34
- R Jump to reference (place cursor on <NAVIGATION TESTS> and press R)
35
- Template Navigation
36
- First template =
37
- Second template =
38
- Third template =
39
- Press N to jump to next template marker and edit
40
- FOLDING TESTS
41
- Space Toggle fold on this item
42
- This child should hide/show when parent is folded
43
- And this grandchild too
44
- Even deeper nesting works
45
- za Toggle all folds recursively
46
- zo Open fold / zc Close fold
47
- zR Open all folds / zM Close all folds
48
- 1-9 Expand to specific level (try pressing 2)
49
- Level 2 item
50
- Level 3 item
51
- Level 4 item
52
- Level 5 item
53
- 0 Multi-digit fold level (press 0 then 1 then 2 for level 12)
54
- EDITING TESTS
55
- Basic Editing
56
- i/Enter Edit current line
57
- o Insert new line below
58
- O Insert new line above
59
- a Insert child item
60
- Delete Operations
61
- D Delete and yank current line only
62
- C-D Delete and yank item with all descendants
63
- Copy/Paste
64
- y Copy current line only
65
- Y Copy item with descendants
66
- p Paste yanked content
67
- Undo/Redo
68
- u Undo last change
69
- r/C-R Redo undone change
70
- . Repeat last action
71
- Moving Items
72
- S-UP/S-DOWN Move single item up/down
73
- C-UP/C-DOWN Move item with descendants up/down
74
- Indentation
75
- Tab Indent item and children
76
- S-Tab Unindent item and children
77
- → Indent item only
78
- ← Unindent item only
79
- CHECKBOX TESTS
80
- [ ] Press v to add/toggle checkbox
81
- [X] Completed checkbox (green)
82
- [O] In progress checkbox (bright green bold)
83
- [-] Partial checkbox (dim green)
84
- [?] Conditional checkbox (green)
85
- [3] Numbered checkbox
86
- [2025-08-12 14:30] Date checkbox
87
- Press V to toggle with timestamp
88
- SPECIAL FEATURES
89
- Presentation Mode
90
- Press P to enter presentation mode
91
- Current item and ancestors shown, rest hidden
92
- Press P again to exit
93
- Underline Toggle
94
- \u Toggle underlining of state/transition items
95
- S: State items (descriptive)
96
- T: Transition items (actions)
97
- File Operations
98
- F Open file reference (place cursor on file path)
99
- <sample.hl> Try opening this reference with F
100
- FILE COMMANDS
101
- :w Save file
102
- :q Quit (asks to save if modified)
103
- :wq Save and quit
104
- Q Force quit without saving
105
- :e filename Open another file
106
- :recent Show recent files
107
- :export md Export to Markdown
108
- :export html Export to HTML
109
- :export txt Export to plain text
110
- :graph Generate PNG graph visualization
111
- :vsplit Split view vertically
112
- ww Switch between split panes
113
- :as on Enable autosave
114
- :as off Disable autosave
115
- :as 30 Set autosave interval to 30 seconds
116
- ADVANCED TESTS
117
- Complex Nesting
118
- Parent 1
119
- Child 1.1
120
- Grandchild 1.1.1
121
- Great-grandchild 1.1.1.1
122
- Great-great-grandchild 1.1.1.1.1
123
- Child 1.2
124
- Grandchild 1.2.1
125
- Grandchild 1.2.2
126
- Parent 2
127
- Child 2.1
128
- Child 2.2
129
- Mixed Formatting
130
- Item with *bold* and /italic/ and _underline_
131
- Item with #multiple #hash #tags
132
- Item with <reference> and (parentheses) and "quotes"
133
- AND: Operator with [X] checkbox and 2025-08-12: date
134
- Long Lines
135
- This is a very long line that should wrap properly in the terminal display and maintain proper indentation when it continues on the next visual line without breaking the hierarchical structure of the HyperList
136
- Special Characters
137
- Item with special chars: @#$%^&*(){}[]|\\:;"'<>,.?/
138
- Unicode: ★ ☆ ♠ ♣ ♥ ♦ → ← ↑ ↓
139
- Emojis: 😀 🎉 ✓ ✗ ⚠️ 📝
140
- HELP SYSTEM
141
- ? Show help screen with key bindings
142
- ?? Show full documentation
143
- Use UP/DOWN to scroll through help
144
- Press any other key to exit help
145
- TEST COMPLETION
146
- [ ] All syntax highlighting verified
147
- [ ] All navigation features tested
148
- [ ] All folding operations work
149
- [ ] All editing functions tested
150
- [ ] Checkboxes functioning correctly
151
- [ ] Special features operational
152
- [ ] File commands working
153
- [ ] Help system accessible
154
- If all tests pass, the Ruby HyperList TUI is working correctly!