hyperlist 1.10.0 → 1.12.0

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.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +1 -1
  4. data/hyperlist +75 -88
  5. data/hyperlist.gemspec +1 -1
  6. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8d42e5386dc58dbdc20cd58047ccb03bbcb29d56980770a40a076a540a507cc
4
- data.tar.gz: 7afd2752e67c25575a5b3e76b7d567eab6d4520dfb322049b5b21f099e24d7ce
3
+ metadata.gz: 64ff18ad8beee2946cfb3bb9f0c4cf8fd33d345787101a08099a0bd725e5a3c2
4
+ data.tar.gz: e05e082cc9f8fabfd60ebce6c74038d72eba81578e1481e7e4a2a5f549c0f724
5
5
  SHA512:
6
- metadata.gz: 731e6c85e60c11b12e2d6b57769c49ca64c40a7250280548a43adcd82bab9a4e18d5cf3048c5e5a81955f16b919e287efb9e64b00c99925539326b578576bd60
7
- data.tar.gz: fd2ed40ca6aacf449f154c821774af8009b02cfbcbf0c387e27d547994274bfc490835973e01082c4ca3029171720fe4cd32d0105aa2b834964df7bf2a6c8051
6
+ metadata.gz: 462d8c4a69e7aac8ab66640e97719d0acf7839e60358b341bfd8dac5626202627b55dbf26ee9288735c73811743f53c9a9f2d3f6b52e3da2582c4474ed4453f7
7
+ data.tar.gz: e9697289fa0a566dbf32377b38f3b53a0b7a5ca62519795d80bd3355006d8c743e05b23a942c892920746de882a41dffe32f6118a84ba1466726f094b63218a1
data/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  All notable changes to the HyperList Ruby TUI will be documented in this file.
4
4
 
5
+ ## [1.12.0] - 2026-10-26
6
+
7
+ ### Changed
8
+ - Wrap mode follows HyperList 2.9: when one Item runs past the line, every
9
+ Item in its group (same indent, same parent) gets a `+` Starter, and the
10
+ wrapped part is indented two spaces.
11
+ - A Starter is `+ ` or an Identifier such as `1 ` or `1.2 `. `-` is plain
12
+ text again and no longer coloured.
13
+ - Lists whose continuation lines are indented one space (older files) or
14
+ two spaces (2.8 on) both load as multi-line Items.
15
+
16
+ ### Fixed
17
+ - `indent_size` in a config line crashed the app; it is read as a number now.
18
+
19
+ ## [1.11.0] - 2026-08-13
20
+
21
+ ### Changed
22
+ - Starters are magenta rather than red. `+` and `-` are line-leading
23
+ structure markers, the same role a numbering Identifier plays, so they
24
+ share its colour instead of sitting with Properties and dates.
25
+ - The `-` Starter is now coloured as well, not only `+`.
26
+
5
27
  ## [1.10.0] - 2026-08-11
6
28
 
7
29
  ### Changed
data/README.md CHANGED
@@ -119,7 +119,7 @@ For historical context and the original VIM implementation, see: [hyperlist.vim]
119
119
  ### Configuration Lines & Theming
120
120
  - **Configuration Lines**: Add settings at the bottom of HyperList files using `((option=value, option2=value))`
121
121
  - **Theme Support**: Three color themes - `light` (bright colors), `normal` (standard), `dark` (for light terminals)
122
- - **Line Wrapping**: Enable with `wrap=yes` - wrapped lines use `+` prefix per HyperList spec
122
+ - **Line Wrapping**: Enable with `wrap=yes` - a long Item and the rest of its group get a `+` Starter (HyperList 2.9)
123
123
  - **Line Numbers**: Enable with `show_numbers=yes`
124
124
  - **Manual Configuration**: Use `:set option=value` to change settings on the fly
125
125
  - **View Settings**: Use `:set` to see all settings, `:set option` to see one setting
data/hyperlist CHANGED
@@ -73,7 +73,7 @@ class HyperListApp
73
73
  include Rcurses::Input
74
74
  include Rcurses::Cursor
75
75
 
76
- VERSION = "1.10.0"
76
+ VERSION = "1.12.0"
77
77
 
78
78
  def initialize(filename = nil)
79
79
  @filename = filename ? File.expand_path(filename) : nil
@@ -291,18 +291,14 @@ class HyperListApp
291
291
  is_continuation = false
292
292
  if @items.length > 0
293
293
  last_item = @items.last
294
- # Check if previous item started with + (multi-line indicator)
295
- if last_item["text"].strip.start_with?("+")
296
- # For continuation lines, we expect: same indent level + one space prefix
297
- # Calculate the expected spaces for a continuation line
298
- expected_spaces = last_item["level"] * @indent_size + 1
299
- actual_spaces = line[/^ */].length
300
-
301
- # Check if this matches the continuation pattern
302
- if actual_spaces == expected_spaces
303
- # This is a continuation line - append it to the previous item
304
- continuation_text = line[expected_spaces..-1] || ""
305
- last_item["text"] += "\n " + continuation_text
294
+ # Only an Item with a Starter spans lines. Its following lines sit
295
+ # two spaces beyond its indent (HyperList 2.8 on), or one in older
296
+ # files. With a two-space indent, two spaces is a child instead.
297
+ if starter?(last_item["text"])
298
+ indent = last_item["level"] * @indent_size
299
+ extra = line[/^ */].length - indent
300
+ if extra == 1 || (extra == 2 && @indent_size >= 3)
301
+ last_item["text"] += "\n" + line[indent..]
306
302
  next # Skip adding as a new item
307
303
  end
308
304
  end
@@ -427,7 +423,7 @@ class HyperListApp
427
423
  value = $2.strip
428
424
  # Convert values to appropriate types
429
425
  case key
430
- when "fold_level", "auto_save_interval", "tab_width"
426
+ when "fold_level", "auto_save_interval", "tab_width", "indent_size"
431
427
  config_options[key] = value.to_i
432
428
  when "auto_save", "wrap", "show_numbers", "highlight_current", "checkbox_date", "backup", "encrypt"
433
429
  config_options[key] = value.downcase == "true" || value.downcase == "yes"
@@ -1205,70 +1201,56 @@ class HyperListApp
1205
1201
  end
1206
1202
 
1207
1203
 
1208
- def wrap_line(text, width, indent_level)
1209
- # Wrap a line per HyperList spec:
1210
- # Multi-line items start with a Neutral Starter ('+' or '-')
1211
- # Continuation lines have just a space prefix
1204
+ # An Item's Starter (HyperList 2.9): "+ " or an Identifier such as
1205
+ # "1 ", "1.2 " or "1A1A ".
1206
+ def starter?(text)
1207
+ text.lstrip.match?(/\A(\+|[0-9][0-9A-Z.]*)\s/)
1208
+ end
1209
+
1210
+ # Wrap a line per HyperList 2.9: an Item that runs past the line takes a
1211
+ # Starter ("+ " unless it has one), and so does every Item of its group
1212
+ # (`group`). Its following lines are indented two spaces. A line the file
1213
+ # already carries as a continuation (`cont`) takes no Starter.
1214
+ def wrap_line(text, width, indent_level, group: false, cont: false)
1212
1215
  return [text] unless @wrap
1213
-
1216
+
1214
1217
  # Calculate effective width (account for indent and fold indicators)
1215
1218
  indent_width = @indent_size * indent_level + 2 # +2 for fold indicator
1216
- effective_width = width - indent_width - 5 # Extra margin for readability
1217
-
1218
- return [text] if text.length <= effective_width || effective_width <= 10
1219
-
1220
- wrapped = []
1221
- remaining = text.dup
1222
- first_line = true
1223
-
1224
- # Check if line already starts with + (multi-line indicator)
1225
- has_plus = text.strip.start_with?('+')
1226
- if has_plus
1227
- # Remove the + for processing, we'll add it back
1228
- remaining = remaining.sub(/^\s*\+\s*/, '')
1229
- end
1230
-
1231
- while remaining && !remaining.empty?
1232
- if first_line
1233
- # First line gets the multi-line indicator if needed
1234
- if remaining.length <= effective_width
1235
- # If line already had +, it was removed earlier, so we add it back
1236
- # If line didn't have + but needs wrapping (wrapped.any?), add +
1237
- # Otherwise, leave as is
1238
- if has_plus
1239
- wrapped << "+ #{remaining}"
1240
- elsif wrapped.any?
1241
- wrapped << "+ #{remaining}"
1242
- else
1243
- wrapped << remaining
1244
- end
1245
- break
1246
- else
1247
- # Find a good break point (prefer spaces)
1248
- break_point = remaining[0...effective_width].rindex(' ') || effective_width
1249
- line_text = remaining[0...break_point].rstrip
1250
- # Add + to first line of multi-line items
1251
- wrapped << "+ #{line_text}"
1252
- remaining = remaining[break_point..-1].lstrip
1253
- first_line = false
1254
- end
1255
- else
1256
- # Continuation lines get just a space prefix per HyperList spec
1257
- cont_width = effective_width - 1 # Account for space prefix
1258
- if remaining.length <= cont_width
1259
- wrapped << " #{remaining}"
1260
- break
1261
- else
1262
- break_point = remaining[0...cont_width].rindex(' ') || cont_width
1263
- wrapped << " #{remaining[0...break_point].rstrip}"
1264
- remaining = remaining[break_point..-1].lstrip
1265
- end
1266
- end
1219
+ room = width - indent_width - 5 # Extra margin for readability
1220
+ return [text] if room <= 10
1221
+
1222
+ text = "+ #{text}" if !cont && (group || text.length > room) && !starter?(text)
1223
+ lines = []
1224
+ rest = text
1225
+ while rest.length > room
1226
+ cut = rest[0...room].rindex(' ')
1227
+ cut = room if cut.nil? || cut.zero?
1228
+ lines << rest[0...cut].rstrip
1229
+ rest = rest[cut..].lstrip
1230
+ room -= 2 if lines.length == 1 # the two-space lead below
1231
+ end
1232
+ lines << rest
1233
+ [lines[0], *lines[1..].map { |l| " #{l}" }]
1234
+ end
1235
+
1236
+ # For each visible Item: does its group (the Items at its indent under
1237
+ # its parent) hold an Item that spans more than one line at `width`?
1238
+ def wrap_groups(items, width)
1239
+ group_of = []
1240
+ parents = [] # [level, index] of the Items above, nearest last
1241
+ spans = Hash.new(false)
1242
+ items.each_with_index do |item, i|
1243
+ next unless item
1244
+ parents.pop while parents.any? && parents.last[0] >= item["level"]
1245
+ group_of[i] = [parents.last&.last, item["level"]]
1246
+ parents << [item["level"], i]
1247
+ room = width - (@indent_size * item["level"] + 2) - 5
1248
+ lines = item["text"].split("\n")
1249
+ spans[group_of[i]] = true if lines.length > 1 || lines.any? { |l| l.length > room }
1267
1250
  end
1268
-
1269
- wrapped
1251
+ group_of.map { |g| g && spans[g] }
1270
1252
  end
1271
-
1253
+
1272
1254
  def render_main
1273
1255
  visible_items = get_visible_items
1274
1256
 
@@ -1280,6 +1262,7 @@ class HyperListApp
1280
1262
  # Track which line number each item starts at (for scroll calculation)
1281
1263
  item_line_starts = {}
1282
1264
 
1265
+ groups = @wrap ? wrap_groups(visible_items, @cols) : []
1283
1266
  visible_items.each_with_index do |item, idx|
1284
1267
  next unless item
1285
1268
 
@@ -1291,10 +1274,10 @@ class HyperListApp
1291
1274
  embedded_lines = item["text"].split("\n")
1292
1275
  text_lines = []
1293
1276
 
1294
- embedded_lines.each do |embedded_line|
1277
+ embedded_lines.each_with_index do |embedded_line, k|
1295
1278
  if @wrap
1296
1279
  # Wrap each embedded line separately
1297
- wrapped = wrap_line(embedded_line, @cols, item["level"])
1280
+ wrapped = wrap_line(embedded_line, @cols, item["level"], group: groups[idx], cont: k.positive?)
1298
1281
  text_lines.concat(wrapped)
1299
1282
  else
1300
1283
  text_lines << embedded_line
@@ -1575,10 +1558,10 @@ class HyperListApp
1575
1558
  result = result.sub(/^[0-9][0-9A-Z.]*\s/, identifier.fg(colors["magenta"])) # Magenta for identifiers
1576
1559
  end
1577
1560
 
1578
- # Handle multi-line indicator at the beginning (+ with space)
1579
- # Based on hyperlist.vim: '^\(\t\|\*\)*+ '
1561
+ # Handle the Starter at the beginning ("+" with a space). Magenta, the same as a
1562
+ # numbering Identifier: both are line-leading structure markers.
1580
1563
  if result =~ /^\+\s/
1581
- result = result.sub(/^(\+\s)/, "+".fg(colors["red"]) + " ") # Red for multi-line indicator
1564
+ result = result.sub(/^\+\s/) { "+".fg(colors["magenta"]) + " " }
1582
1565
  end
1583
1566
 
1584
1567
  # Handle continuation markers (+ at start of indented lines in References section)
@@ -3948,10 +3931,11 @@ class HyperListApp
3948
3931
  A HyperList Item consists of these parts in sequence:
3949
3932
 
3950
3933
  1. #{"Starter".fg("5")} (optional)
3951
- • Neutral Starter: "+ " or "- " — marks where an Item begins
3934
+ • Neutral Starter: "+ " — marks where an Item begins
3952
3935
  • Identifier: Numbers (1.1.1) or mixed (1A1A) — also unique
3953
- Required only when an Item spans more than one line; its following
3954
- lines are indented two spaces beyond the Item's own indent
3936
+ Required when an Item spans more than one line; its following
3937
+ lines are indented two spaces beyond the Item's own indent. Then
3938
+ every Item of its group (same indent, same parent) takes one too
3955
3939
 
3956
3940
  2. #{"Type".fg("5")} (optional)
3957
3941
  • State: S: or |
@@ -4195,15 +4179,17 @@ class HyperListApp
4195
4179
  HyperList
4196
4180
  [1+] HyperList Item
4197
4181
  [?] Starter; OR:
4198
- Neutral Starter = "+ " or "- "
4182
+ + Neutral Starter = "+ "
4199
4183
  Marks where an Item begins and carries no further meaning
4200
- Identifier (Numbers: Format = "1.1.1.1", Mixed: Format = "1A1A")
4184
+ + Identifier (Numbers: Format = "1.1.1.1", Mixed: Format = "1A1A")
4201
4185
  Unique, so the Item can be referred to; also acts as a Starter
4202
4186
  + An Item spanning more than one line must have a Starter, and its
4203
4187
  following lines are indented two spaces beyond the Item's own indent
4204
- One level of indentation is at least three columns wide, so a
4205
- continuation line can never be read as a child
4206
- No requirement falls on the other Items at the same indent
4188
+ + One level of indentation is at least three columns wide, so a
4189
+ continuation line can never be read as a child
4190
+ + When one Item in a group spans more than one line, every Item
4191
+ in that group takes a Starter. A group is the Items at the
4192
+ same indent under the same parent
4207
4193
  [?] Type
4208
4194
  OR:
4209
4195
  State = "S:" or "|"
@@ -5883,6 +5869,7 @@ class HyperListApp
5883
5869
  # Track which line number each item starts at (for scroll calculation)
5884
5870
  item_line_starts = {}
5885
5871
 
5872
+ groups = @wrap ? wrap_groups(visible_items, @split_pane.w - 10) : []
5886
5873
  visible_items.each_with_index do |item, idx|
5887
5874
  next unless item
5888
5875
 
@@ -5896,10 +5883,10 @@ class HyperListApp
5896
5883
  embedded_lines = item["text"].split("\n")
5897
5884
  text_lines = []
5898
5885
 
5899
- embedded_lines.each do |embedded_line|
5886
+ embedded_lines.each_with_index do |embedded_line, k|
5900
5887
  if @wrap
5901
5888
  # Wrap each embedded line separately
5902
- wrapped = wrap_line(embedded_line, @split_pane.w - 10, item["level"])
5889
+ wrapped = wrap_line(embedded_line, @split_pane.w - 10, item["level"], group: groups[idx], cont: k.positive?)
5903
5890
  text_lines.concat(wrapped)
5904
5891
  else
5905
5892
  text_lines << embedded_line
data/hyperlist.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "hyperlist"
3
- spec.version = "1.10.0"
3
+ spec.version = "1.12.0"
4
4
  spec.authors = ["Geir Isene"]
5
5
  spec.email = ["g@isene.com"]
6
6
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hyperlist
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.0
4
+ version: 1.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geir Isene