hyperlist 1.8.0 → 1.9.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.
- checksums.yaml +4 -4
- data/README.md +67 -15
- data/hyperlist +694 -145
- data/hyperlist.gemspec +2 -2
- metadata +6 -7
- data/test_visibility.hl +0 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f638bfa971829555f7f078ed1c2e5a498a3e0d4026dea67fd547bc881fdec61
|
|
4
|
+
data.tar.gz: 3cbb6c08d664e5ed7111384ef275293c9616c8c213f3cc189beaf1e3054fbfac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a71a5cfa04c8e52652f990d970f6fc7a1e65a5a89a9869b558937a016ae7c8e2aa0017910a23941158abbbfb6c8d92a2f0b8d50075cb3cb303d06c04f62c876d
|
|
7
|
+
data.tar.gz: cccf1614ea64a35296d0de28b35d3ca3098fc31d68296cb271ef93ca5e085ffc2801fac04b98df96c72340b00adb39003fbcb55651f5561fe7e3a36ecf63a9e6
|
data/README.md
CHANGED
|
@@ -29,17 +29,55 @@ For historical context and the original VIM implementation, see: [hyperlist.vim]
|
|
|
29
29
|
### Help Screen
|
|
30
30
|

|
|
31
31
|
|
|
32
|
-
## What's New in v1.
|
|
33
|
-
|
|
34
|
-
###
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
32
|
+
## What's New in v1.9.0
|
|
33
|
+
|
|
34
|
+
### 🏷️ Item Tagging & Batch Operations
|
|
35
|
+
- **Tag items**: Press 't' to tag/untag items for batch operations
|
|
36
|
+
- **Auto-advance**: Cursor automatically moves to next item after tagging for fast consecutive tagging
|
|
37
|
+
- **Visual feedback**: Tagged items show dark blue background, lighter blue when selected
|
|
38
|
+
- **Status indicator**: Shows `[T:N]` in status line with count of tagged items
|
|
39
|
+
- **Clear tags**: Press 'u' to clear all tags
|
|
40
|
+
- **Regex tagging**: Press 'C-T' to tag all items matching a regex pattern
|
|
41
|
+
- **Batch operations**: Delete (D/C-D), yank (y/Y), and indent (Tab/S-Tab) work on all tagged items
|
|
42
|
+
- Tag consecutive or non-consecutive items, then perform operations on the entire set
|
|
43
|
+
|
|
44
|
+
### ✏️ External Editor Support
|
|
45
|
+
- **Edit in $EDITOR**: Press 'E' to spawn your preferred editor (vim, nano, emacs, etc.)
|
|
46
|
+
- **Seamless workflow**: File saved automatically, editor launched, changes reloaded on exit
|
|
47
|
+
- **Terminal management**: Terminal state properly saved and restored
|
|
48
|
+
- Uses `$EDITOR` environment variable (defaults to vi if not set)
|
|
49
|
+
|
|
50
|
+
### 📋 Enhanced Paste & Navigation
|
|
51
|
+
- **Paste above**: Press 'P' to paste above current item (vim-style)
|
|
52
|
+
- **Paste below**: Press 'p' to paste below current item (existing)
|
|
53
|
+
- **Presentation mode**: Moved to 'C-P' (was 'P') for consistency
|
|
54
|
+
- **Templates**: Moved to 'T' key (was 't')
|
|
55
|
+
- **Undo**: Moved to 'U' key (was 'u') - consistent with RTFM
|
|
56
|
+
|
|
57
|
+
### 🎯 Smart Modified Flag
|
|
58
|
+
- **Intelligent tracking**: `[+]` indicator automatically removed when undoing back to original file state
|
|
59
|
+
- **Clean status**: No false "modified" indicator after complete undo to original
|
|
60
|
+
|
|
61
|
+
## Previous Release: v1.8.0
|
|
62
|
+
|
|
63
|
+
### 📋 Multi-Line Paste Support
|
|
64
|
+
- **Paste multiple lines**: When pasting multi-line content into item insertion prompts ('o', 'O', 'a', 'A'), each line becomes a separate item
|
|
65
|
+
- **Visual feedback**: Shows `[+N lines]` indicator during multi-line paste
|
|
66
|
+
- **Smart insertion**: All pasted lines inserted as siblings at the same level
|
|
67
|
+
- Great for importing bullet lists from PDFs, emails, or other documents
|
|
68
|
+
- Requires rcurses 6.1.5+
|
|
69
|
+
|
|
70
|
+
### 📄 PDF/LaTeX Export
|
|
71
|
+
- **Export to PDF**: `:export pdf filename.pdf` - Full LaTeX-based PDF generation
|
|
72
|
+
- **Export to LaTeX**: `:export latex filename.tex` - Get the LaTeX source
|
|
73
|
+
- **Professional output**: Color-coded elements, table of contents, headers
|
|
74
|
+
- **Complete HyperList support**: All syntax elements rendered beautifully
|
|
75
|
+
- Requires: texlive-latex-base and texlive-latex-extra packages
|
|
76
|
+
|
|
77
|
+
### 📋 System Clipboard Integration
|
|
78
|
+
- **Yank to clipboard**: 'y' and 'Y' now copy to system clipboard
|
|
79
|
+
- **Middle-click paste**: Yanked items can be pasted into other terminals
|
|
80
|
+
- **Preserves indentation**: Copied text maintains proper structure
|
|
43
81
|
|
|
44
82
|
## Previous Version Features (v1.4.0)
|
|
45
83
|
|
|
@@ -149,6 +187,8 @@ All `:set` commands automatically update the file's configuration line.
|
|
|
149
187
|
- **Dates**: `2025-08-12` or `2025-08-12 14:30`
|
|
150
188
|
|
|
151
189
|
### Export Formats
|
|
190
|
+
- **PDF**: Professional LaTeX-based PDF with color coding and TOC
|
|
191
|
+
- **LaTeX**: Source .tex files for customization
|
|
152
192
|
- **HTML**: Full-featured HTML with syntax highlighting
|
|
153
193
|
- **Markdown**: GitHub-flavored Markdown
|
|
154
194
|
- **Plain Text**: Clean text output
|
|
@@ -213,7 +253,17 @@ hyperlist file.txt # Open any text file
|
|
|
213
253
|
- `D` - Delete and yank line
|
|
214
254
|
- `C-D` - Delete and yank item with descendants
|
|
215
255
|
- `y/Y` - Copy line/tree
|
|
216
|
-
- `p` - Paste
|
|
256
|
+
- `p` - Paste below
|
|
257
|
+
- `P` - Paste above
|
|
258
|
+
- `U` - Undo
|
|
259
|
+
- `r` or `C-R` - Redo
|
|
260
|
+
- `E` - Edit in $EDITOR
|
|
261
|
+
|
|
262
|
+
#### Tagging & Batch Operations
|
|
263
|
+
- `t` - Tag/untag current item
|
|
264
|
+
- `u` - Clear all tags
|
|
265
|
+
- `C-T` - Tag items matching regex pattern
|
|
266
|
+
- Operations (D/C-D, y/Y, Tab/S-Tab) work on all tagged items when tags exist
|
|
217
267
|
|
|
218
268
|
#### Folding
|
|
219
269
|
- `Space` - Toggle fold
|
|
@@ -227,8 +277,8 @@ hyperlist file.txt # Open any text file
|
|
|
227
277
|
- `C-E` - Encrypt/decrypt current line
|
|
228
278
|
- `R` - Go to reference
|
|
229
279
|
- `F` - Open file reference
|
|
230
|
-
- `P` - Presentation mode (with auto-collapse)
|
|
231
|
-
- `
|
|
280
|
+
- `C-P` - Presentation mode (with auto-collapse)
|
|
281
|
+
- `T` - Insert template (built-in or custom)
|
|
232
282
|
- `?` - Help screen
|
|
233
283
|
|
|
234
284
|
#### File Commands
|
|
@@ -236,9 +286,11 @@ hyperlist file.txt # Open any text file
|
|
|
236
286
|
- `:q` - Quit
|
|
237
287
|
- `:wq` or `W` - Save and quit
|
|
238
288
|
- `:e file` - Open file
|
|
289
|
+
- `:export pdf` - Export to PDF (requires LaTeX)
|
|
290
|
+
- `:export latex` - Export to LaTeX source
|
|
239
291
|
- `:export html` - Export to HTML
|
|
240
292
|
- `:export md` - Export to Markdown
|
|
241
|
-
- `:graph` - Export to PNG
|
|
293
|
+
- `:graph` - Export to PNG graph
|
|
242
294
|
|
|
243
295
|
#### Template Commands
|
|
244
296
|
- `:st` - Save current document as template
|
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.9.0 - 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.9.0"
|
|
56
56
|
exit 0
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -66,13 +66,14 @@ require 'digest'
|
|
|
66
66
|
require 'base64'
|
|
67
67
|
require 'fileutils'
|
|
68
68
|
require 'json'
|
|
69
|
+
require 'shellwords'
|
|
69
70
|
|
|
70
71
|
class HyperListApp
|
|
71
72
|
include Rcurses
|
|
72
73
|
include Rcurses::Input
|
|
73
74
|
include Rcurses::Cursor
|
|
74
75
|
|
|
75
|
-
VERSION = "1.
|
|
76
|
+
VERSION = "1.9.0"
|
|
76
77
|
|
|
77
78
|
def initialize(filename = nil)
|
|
78
79
|
@filename = filename ? File.expand_path(filename) : nil
|
|
@@ -91,6 +92,8 @@ class HyperListApp
|
|
|
91
92
|
@show_numbers = false # Line numbers disabled by default
|
|
92
93
|
@command_history = load_command_history # Command history for : commands
|
|
93
94
|
@clipboard = nil
|
|
95
|
+
@tagged_items = [] # Items tagged for batch operations
|
|
96
|
+
@original_items = nil # Track original file state for smart modified flag
|
|
94
97
|
@undo_stack = []
|
|
95
98
|
@undo_position = [] # Stack of cursor positions for undo
|
|
96
99
|
@redo_stack = []
|
|
@@ -223,7 +226,8 @@ class HyperListApp
|
|
|
223
226
|
@items = []
|
|
224
227
|
@encrypted_lines = {}
|
|
225
228
|
@config_line = nil # Reset config line before loading
|
|
226
|
-
|
|
229
|
+
@tagged_items = [] # Clear tags when loading new file
|
|
230
|
+
|
|
227
231
|
# Read file content
|
|
228
232
|
content = File.read(file) rescue ""
|
|
229
233
|
|
|
@@ -354,6 +358,9 @@ class HyperListApp
|
|
|
354
358
|
|
|
355
359
|
# Update recent files list
|
|
356
360
|
add_to_recent_files(File.expand_path(file)) if file
|
|
361
|
+
|
|
362
|
+
# Save original state for smart modified flag
|
|
363
|
+
@original_items = @items.map { |item| item.dup }
|
|
357
364
|
end
|
|
358
365
|
|
|
359
366
|
def auto_fold_deep_levels(max_level)
|
|
@@ -703,6 +710,7 @@ class HyperListApp
|
|
|
703
710
|
end
|
|
704
711
|
|
|
705
712
|
@modified = false
|
|
713
|
+
@original_items = @items.map { |item| item.dup } # Save state after save
|
|
706
714
|
@last_auto_save = Time.now if @auto_save_enabled
|
|
707
715
|
end
|
|
708
716
|
|
|
@@ -724,10 +732,12 @@ class HyperListApp
|
|
|
724
732
|
when 'md', 'markdown' then '.md'
|
|
725
733
|
when 'html' then '.html'
|
|
726
734
|
when 'txt', 'text' then '.txt'
|
|
735
|
+
when 'latex', 'tex' then '.tex'
|
|
736
|
+
when 'pdf' then '.pdf'
|
|
727
737
|
end
|
|
728
738
|
export_file = base + extension
|
|
729
739
|
end
|
|
730
|
-
|
|
740
|
+
|
|
731
741
|
content = case format
|
|
732
742
|
when 'md', 'markdown'
|
|
733
743
|
export_to_markdown
|
|
@@ -735,10 +745,39 @@ class HyperListApp
|
|
|
735
745
|
export_to_html
|
|
736
746
|
when 'txt', 'text'
|
|
737
747
|
export_to_text
|
|
748
|
+
when 'latex', 'tex'
|
|
749
|
+
export_to_latex
|
|
750
|
+
when 'pdf'
|
|
751
|
+
export_to_latex
|
|
738
752
|
end
|
|
739
|
-
|
|
753
|
+
|
|
740
754
|
File.open(export_file, 'w') { |f| f.write(content) }
|
|
741
|
-
|
|
755
|
+
|
|
756
|
+
# For PDF, compile the LaTeX file
|
|
757
|
+
if format == 'pdf'
|
|
758
|
+
tex_file = export_file.sub(/\.pdf$/, '.tex')
|
|
759
|
+
File.open(tex_file, 'w') { |f| f.write(content) }
|
|
760
|
+
|
|
761
|
+
# Compile LaTeX to PDF
|
|
762
|
+
@message = "Compiling LaTeX to PDF..."
|
|
763
|
+
render_footer
|
|
764
|
+
|
|
765
|
+
result = system("pdflatex -interaction=nonstopmode -output-directory=#{File.dirname(tex_file)} #{tex_file} > /dev/null 2>&1")
|
|
766
|
+
if result
|
|
767
|
+
# Run twice for TOC
|
|
768
|
+
system("pdflatex -interaction=nonstopmode -output-directory=#{File.dirname(tex_file)} #{tex_file} > /dev/null 2>&1")
|
|
769
|
+
@message = "Exported to #{export_file}"
|
|
770
|
+
# Clean up auxiliary files
|
|
771
|
+
['aux', 'log', 'out', 'toc'].each do |ext|
|
|
772
|
+
aux_file = tex_file.sub(/\.tex$/, ".#{ext}")
|
|
773
|
+
File.delete(aux_file) if File.exist?(aux_file)
|
|
774
|
+
end
|
|
775
|
+
else
|
|
776
|
+
@message = "PDF compilation failed. Check #{tex_file}"
|
|
777
|
+
end
|
|
778
|
+
else
|
|
779
|
+
@message = "Exported to #{export_file}"
|
|
780
|
+
end
|
|
742
781
|
rescue => e
|
|
743
782
|
@message = "Export failed: #{e.message}"
|
|
744
783
|
end
|
|
@@ -948,7 +987,151 @@ class HyperListApp
|
|
|
948
987
|
end
|
|
949
988
|
lines.join("\n")
|
|
950
989
|
end
|
|
951
|
-
|
|
990
|
+
|
|
991
|
+
def escape_latex(text)
|
|
992
|
+
# Escape special LaTeX characters
|
|
993
|
+
text = text.gsub('\\', '\\textbackslash{}')
|
|
994
|
+
text = text.gsub('&', '\\&')
|
|
995
|
+
text = text.gsub('%', '\\%')
|
|
996
|
+
text = text.gsub('$', '\\$')
|
|
997
|
+
text = text.gsub('#', '\\#')
|
|
998
|
+
text = text.gsub('_', '\\_')
|
|
999
|
+
text = text.gsub('^', '\\textasciicircum{}')
|
|
1000
|
+
text = text.gsub('~', '\\textasciitilde{}')
|
|
1001
|
+
text
|
|
1002
|
+
end
|
|
1003
|
+
|
|
1004
|
+
def export_to_latex
|
|
1005
|
+
lines = []
|
|
1006
|
+
|
|
1007
|
+
# LaTeX header
|
|
1008
|
+
lines << "% Generated by HyperList - LaTeX Export"
|
|
1009
|
+
lines << "% Export date: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
1010
|
+
lines << ""
|
|
1011
|
+
lines << "\\documentclass[11pt,a4paper]{article}"
|
|
1012
|
+
lines << "\\usepackage[utf8]{inputenc}"
|
|
1013
|
+
lines << "\\usepackage[T1]{fontenc}"
|
|
1014
|
+
lines << "\\usepackage[english]{babel}"
|
|
1015
|
+
lines << "\\usepackage[margin=2.5cm]{geometry}"
|
|
1016
|
+
lines << "\\usepackage{xcolor}"
|
|
1017
|
+
lines << "\\usepackage{enumitem}"
|
|
1018
|
+
lines << "\\usepackage{fancyhdr}"
|
|
1019
|
+
lines << "\\usepackage{hyperref}"
|
|
1020
|
+
lines << "\\usepackage{tcolorbox}"
|
|
1021
|
+
lines << "\\usepackage{fontawesome5}"
|
|
1022
|
+
lines << ""
|
|
1023
|
+
lines << "% HyperList color definitions"
|
|
1024
|
+
lines << "\\definecolor{hloperator}{RGB}{41,128,185}"
|
|
1025
|
+
lines << "\\definecolor{hlproperty}{RGB}{231,76,60}"
|
|
1026
|
+
lines << "\\definecolor{hlqualifier}{RGB}{39,174,96}"
|
|
1027
|
+
lines << "\\definecolor{hlhashtag}{RGB}{243,156,18}"
|
|
1028
|
+
lines << "\\definecolor{hlreference}{RGB}{155,89,182}"
|
|
1029
|
+
lines << "\\definecolor{hlcomment}{RGB}{127,140,141}"
|
|
1030
|
+
lines << "\\definecolor{hlquote}{RGB}{22,160,133}"
|
|
1031
|
+
lines << "\\definecolor{hlstate}{RGB}{46,204,113}"
|
|
1032
|
+
lines << "\\definecolor{hltransition}{RGB}{230,126,34}"
|
|
1033
|
+
lines << ""
|
|
1034
|
+
lines << "% HyperList commands"
|
|
1035
|
+
lines << "\\newcommand{\\hloperator}[1]{\\textcolor{hloperator}{\\textbf{#1}}}"
|
|
1036
|
+
lines << "\\newcommand{\\hlproperty}[1]{\\textcolor{hlproperty}{\\textit{#1}}}"
|
|
1037
|
+
lines << "\\newcommand{\\hlqualifier}[1]{\\textcolor{hlqualifier}{\\texttt{#1}}}"
|
|
1038
|
+
lines << "\\newcommand{\\hlhashtag}[1]{\\textcolor{hlhashtag}{\\textbf{#1}}}"
|
|
1039
|
+
lines << "\\newcommand{\\hlreference}[1]{\\textcolor{hlreference}{#1}}"
|
|
1040
|
+
lines << "\\newcommand{\\hlcomment}[1]{\\textcolor{hlcomment}{\\textit{#1}}}"
|
|
1041
|
+
lines << "\\newcommand{\\hlquote}[1]{\\textcolor{hlquote}{#1}}"
|
|
1042
|
+
lines << "\\newcommand{\\hlstate}[1]{\\begin{tcolorbox}[colback=hlstate!10,colframe=hlstate,title=State]#1\\end{tcolorbox}}"
|
|
1043
|
+
lines << "\\newcommand{\\hltransition}[1]{\\begin{tcolorbox}[colback=hltransition!10,colframe=hltransition,title=Action]#1\\end{tcolorbox}}"
|
|
1044
|
+
lines << "\\newcommand{\\hlsubstitution}[1]{\\textcolor{hlqualifier}{#1}}"
|
|
1045
|
+
lines << "\\newcommand{\\checkbox}{\\faSquare[regular]}"
|
|
1046
|
+
lines << "\\newcommand{\\checkboxdone}{\\faCheckSquare}"
|
|
1047
|
+
lines << "\\newcommand{\\checkboxprogress}{\\faMinusSquare[regular]}"
|
|
1048
|
+
lines << ""
|
|
1049
|
+
lines << "% Document setup"
|
|
1050
|
+
title = @filename ? File.basename(@filename, '.*') : 'HyperList Export'
|
|
1051
|
+
lines << "\\title{#{escape_latex(title)}}"
|
|
1052
|
+
lines << "\\author{Generated by HyperList}"
|
|
1053
|
+
lines << "\\date{#{Time.now.strftime('%Y-%m-%d')}}"
|
|
1054
|
+
lines << ""
|
|
1055
|
+
lines << "\\pagestyle{fancy}"
|
|
1056
|
+
lines << "\\fancyhf{}"
|
|
1057
|
+
lines << "\\fancyhead[R]{\\thepage}"
|
|
1058
|
+
lines << "\\fancyhead[L]{#{escape_latex(title)}}"
|
|
1059
|
+
lines << ""
|
|
1060
|
+
lines << "\\begin{document}"
|
|
1061
|
+
lines << "\\maketitle"
|
|
1062
|
+
lines << "\\tableofcontents"
|
|
1063
|
+
lines << "\\newpage"
|
|
1064
|
+
lines << ""
|
|
1065
|
+
lines << "\\begin{itemize}[leftmargin=0pt,itemsep=2pt,parsep=0pt]"
|
|
1066
|
+
|
|
1067
|
+
# Process each item
|
|
1068
|
+
prev_level = 0
|
|
1069
|
+
@items.each do |item|
|
|
1070
|
+
text = item["text"]
|
|
1071
|
+
level = item["level"]
|
|
1072
|
+
|
|
1073
|
+
# Handle level changes
|
|
1074
|
+
if level > prev_level
|
|
1075
|
+
(level - prev_level).times do
|
|
1076
|
+
lines << "\\begin{itemize}"
|
|
1077
|
+
end
|
|
1078
|
+
elsif level < prev_level
|
|
1079
|
+
(prev_level - level).times do
|
|
1080
|
+
lines << "\\end{itemize}"
|
|
1081
|
+
end
|
|
1082
|
+
end
|
|
1083
|
+
|
|
1084
|
+
# Escape LaTeX special characters first
|
|
1085
|
+
text = escape_latex(text)
|
|
1086
|
+
|
|
1087
|
+
# Convert checkboxes
|
|
1088
|
+
text = text.gsub(/\[_\]/, '\\checkbox{}')
|
|
1089
|
+
text = text.gsub(/\[x\]|\[X\]/, '\\checkboxdone{}')
|
|
1090
|
+
text = text.gsub(/\[O\]/, '\\checkboxprogress{}')
|
|
1091
|
+
|
|
1092
|
+
# Convert HyperList markup (must be after escaping)
|
|
1093
|
+
text = text.gsub(/ \*([^*]+)\* /, ' \\textbf{\1} ')
|
|
1094
|
+
text = text.gsub(/ \/([^\/]+)\/ /, ' \\textit{\1} ')
|
|
1095
|
+
text = text.gsub(/ _([^_]+)_ /, ' \\underline{\1} ')
|
|
1096
|
+
|
|
1097
|
+
# Convert HyperList elements
|
|
1098
|
+
text = text.gsub(/\[([^\]]+)\]/, '\\hlqualifier{[\1]}')
|
|
1099
|
+
text = text.gsub(/\{([^\}]+)\}/, '\\hlsubstitution{\{\1\}}')
|
|
1100
|
+
text = text.gsub(/(#[a-zA-Z0-9.:\/\\_&?%=\-*]+)/, '\\hlhashtag{\1}')
|
|
1101
|
+
text = text.gsub(/(<{1,2}[a-zA-Z0-9.:\/\\_&?%=\-* ]+>{1,2})/, '\\hlreference{\1}')
|
|
1102
|
+
text = text.gsub(/(\([^)]*\))/, '\\hlcomment{\1}')
|
|
1103
|
+
text = text.gsub(/(\"[^\"]*\")/, '\\hlquote{\1}')
|
|
1104
|
+
|
|
1105
|
+
# Convert States and Transitions
|
|
1106
|
+
if text =~ /^S: (.+)/
|
|
1107
|
+
text = "\\hlstate{State: #{$1}}"
|
|
1108
|
+
elsif text =~ /^T: (.+)/
|
|
1109
|
+
text = "\\hltransition{Action: #{$1}}"
|
|
1110
|
+
elsif text =~ /^\| (.+)/
|
|
1111
|
+
text = "\\hlstate{State: #{$1}}"
|
|
1112
|
+
elsif text =~ /^\/ (.+)/
|
|
1113
|
+
text = "\\hltransition{Action: #{$1}}"
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1116
|
+
# Convert Operators and Properties
|
|
1117
|
+
text = text.gsub(/([A-Z_\-() \/]{2,}): /, '\\hloperator{\1:} ')
|
|
1118
|
+
text = text.gsub(/([a-zA-Z0-9,._&?%=\-\/+<>#'()*:]{2,}): /, '\\hlproperty{\1:} ')
|
|
1119
|
+
|
|
1120
|
+
lines << "\\item #{text}"
|
|
1121
|
+
prev_level = level
|
|
1122
|
+
end
|
|
1123
|
+
|
|
1124
|
+
# Close any remaining nested lists
|
|
1125
|
+
prev_level.times do
|
|
1126
|
+
lines << "\\end{itemize}"
|
|
1127
|
+
end
|
|
1128
|
+
|
|
1129
|
+
lines << "\\end{itemize}"
|
|
1130
|
+
lines << "\\end{document}"
|
|
1131
|
+
|
|
1132
|
+
lines.join("\n")
|
|
1133
|
+
end
|
|
1134
|
+
|
|
952
1135
|
def render
|
|
953
1136
|
render_main
|
|
954
1137
|
render_split_pane if @split_view
|
|
@@ -1122,20 +1305,31 @@ class HyperListApp
|
|
|
1122
1305
|
end
|
|
1123
1306
|
end
|
|
1124
1307
|
|
|
1125
|
-
# Apply
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1308
|
+
# Apply background highlighting based on tagged/selected state
|
|
1309
|
+
real_idx = get_real_index(item)
|
|
1310
|
+
is_tagged = real_idx && @tagged_items.include?(real_idx)
|
|
1311
|
+
is_selected = idx == @current
|
|
1312
|
+
|
|
1313
|
+
bg_color = nil
|
|
1314
|
+
if is_tagged && is_selected
|
|
1315
|
+
# Tagged AND selected - lighter blue
|
|
1316
|
+
bg_color = "21" unless @presentation_mode && is_item_in_presentation_focus?(item)
|
|
1317
|
+
elsif is_tagged
|
|
1318
|
+
# Only tagged - dark blue
|
|
1319
|
+
bg_color = "17"
|
|
1320
|
+
elsif is_selected
|
|
1321
|
+
# Only selected - normal gray
|
|
1322
|
+
unless @presentation_mode && is_item_in_presentation_focus?(item)
|
|
1129
1323
|
bg_color = (!@split_view || @active_pane == :main) ? "237" : "234"
|
|
1130
|
-
if bg_color
|
|
1131
|
-
# Pad line to full width and apply background
|
|
1132
|
-
padded_line = line + " " * [@cols - line.pure.length, 0].max
|
|
1133
|
-
bg_code = "\e[48;5;#{bg_color}m"
|
|
1134
|
-
reset_bg = "\e[49m"
|
|
1135
|
-
line = bg_code + padded_line.gsub(/\e\[49m/, '') + reset_bg
|
|
1136
|
-
end
|
|
1137
1324
|
end
|
|
1138
1325
|
end
|
|
1326
|
+
|
|
1327
|
+
if bg_color
|
|
1328
|
+
padded_line = line + " " * [@cols - line.pure.length, 0].max
|
|
1329
|
+
bg_code = "\e[48;5;#{bg_color}m"
|
|
1330
|
+
reset_bg = "\e[49m"
|
|
1331
|
+
line = bg_code + padded_line.gsub(/\e\[49m/, '') + reset_bg
|
|
1332
|
+
end
|
|
1139
1333
|
|
|
1140
1334
|
lines << line
|
|
1141
1335
|
end
|
|
@@ -1504,16 +1698,19 @@ class HyperListApp
|
|
|
1504
1698
|
|
|
1505
1699
|
# Auto-save indicator
|
|
1506
1700
|
auto_save_indicator = @auto_save_enabled ? "[A]" : ""
|
|
1507
|
-
|
|
1508
|
-
# Split view indicator
|
|
1701
|
+
|
|
1702
|
+
# Split view indicator
|
|
1509
1703
|
split_indicator = @split_view ? "[#{@active_pane.upcase}]" : ""
|
|
1510
|
-
|
|
1704
|
+
|
|
1705
|
+
# Tagged items indicator
|
|
1706
|
+
tagged_indicator = @tagged_items.any? ? "[T:#{@tagged_items.length}]" : ""
|
|
1707
|
+
|
|
1511
1708
|
# Build status line components
|
|
1512
1709
|
# Use full path with ~ for home directory
|
|
1513
1710
|
full_path = @filename ? @filename.gsub(ENV['HOME'], '~') : "New HyperList"
|
|
1514
1711
|
file_part = "#{full_path}#{modified_indicator}"
|
|
1515
1712
|
stats_part = "L#{pos} W:#{word_count}"
|
|
1516
|
-
indicators = "#{auto_save_indicator}#{split_indicator}"
|
|
1713
|
+
indicators = "#{auto_save_indicator}#{split_indicator}#{tagged_indicator}"
|
|
1517
1714
|
right_side = "? help #{version_text}"
|
|
1518
1715
|
|
|
1519
1716
|
# Combine left elements
|
|
@@ -2251,9 +2448,24 @@ class HyperListApp
|
|
|
2251
2448
|
|
|
2252
2449
|
# Clear cache since items changed
|
|
2253
2450
|
clear_cache
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
@
|
|
2451
|
+
|
|
2452
|
+
# Check if we've returned to original state
|
|
2453
|
+
if @original_items && items_match?(@items, @original_items)
|
|
2454
|
+
@modified = false
|
|
2455
|
+
@message = "Undone to original state (#{@undo_stack.length} undo levels, #{@redo_stack.length} redo available)"
|
|
2456
|
+
else
|
|
2457
|
+
@modified = true
|
|
2458
|
+
@message = "Undone (#{@undo_stack.length} undo levels, #{@redo_stack.length} redo available)"
|
|
2459
|
+
end
|
|
2460
|
+
end
|
|
2461
|
+
|
|
2462
|
+
def items_match?(items1, items2)
|
|
2463
|
+
return false if items1.length != items2.length
|
|
2464
|
+
items1.each_with_index do |item, idx|
|
|
2465
|
+
return false if item["text"] != items2[idx]["text"] ||
|
|
2466
|
+
item["level"] != items2[idx]["level"]
|
|
2467
|
+
end
|
|
2468
|
+
true
|
|
2257
2469
|
end
|
|
2258
2470
|
|
|
2259
2471
|
def record_last_action(type, data = nil)
|
|
@@ -2332,19 +2544,41 @@ class HyperListApp
|
|
|
2332
2544
|
|
|
2333
2545
|
def insert_line
|
|
2334
2546
|
@mode = :insert
|
|
2335
|
-
|
|
2547
|
+
|
|
2336
2548
|
input = @footer.ask("New item: ", "")
|
|
2337
|
-
|
|
2549
|
+
|
|
2338
2550
|
if input && !input.strip.empty?
|
|
2339
|
-
|
|
2551
|
+
# Collect all lines (first line + buffer)
|
|
2552
|
+
all_lines = [input]
|
|
2553
|
+
if @footer.multiline_buffer && !@footer.multiline_buffer.empty?
|
|
2554
|
+
all_lines += @footer.multiline_buffer.reject { |l| l.strip.empty? }
|
|
2555
|
+
@footer.multiline_buffer = []
|
|
2556
|
+
end
|
|
2557
|
+
|
|
2558
|
+
# Insert all lines as siblings at the same level
|
|
2559
|
+
all_lines.each do |line|
|
|
2560
|
+
insert_line_with_text(line.strip)
|
|
2561
|
+
@current -= 1 # Keep cursor at same position so next insert is sibling
|
|
2562
|
+
end
|
|
2563
|
+
@current += 1 # Move to last inserted item
|
|
2564
|
+
|
|
2340
2565
|
record_last_action(:insert_line, input)
|
|
2341
2566
|
end
|
|
2342
|
-
|
|
2567
|
+
|
|
2343
2568
|
@mode = :normal
|
|
2344
2569
|
@footer.clear # Clear footer immediately
|
|
2345
2570
|
@footer.refresh
|
|
2346
2571
|
end
|
|
2347
|
-
|
|
2572
|
+
|
|
2573
|
+
# Alias for 'o' key
|
|
2574
|
+
alias insert_line_below insert_line
|
|
2575
|
+
|
|
2576
|
+
# Method for 'O' key - insert line above
|
|
2577
|
+
def insert_line_above
|
|
2578
|
+
@current -= 1 if @current > 0
|
|
2579
|
+
insert_line
|
|
2580
|
+
end
|
|
2581
|
+
|
|
2348
2582
|
def insert_line_with_text(text)
|
|
2349
2583
|
return unless text && !text.strip.empty?
|
|
2350
2584
|
|
|
@@ -2371,14 +2605,27 @@ class HyperListApp
|
|
|
2371
2605
|
|
|
2372
2606
|
def insert_child
|
|
2373
2607
|
@mode = :insert
|
|
2374
|
-
|
|
2608
|
+
|
|
2375
2609
|
input = @footer.ask("New child item: ", "")
|
|
2376
|
-
|
|
2610
|
+
|
|
2377
2611
|
if input && !input.strip.empty?
|
|
2378
|
-
|
|
2612
|
+
# Collect all lines (first line + buffer)
|
|
2613
|
+
all_lines = [input]
|
|
2614
|
+
if @footer.multiline_buffer && !@footer.multiline_buffer.empty?
|
|
2615
|
+
all_lines += @footer.multiline_buffer.reject { |l| l.strip.empty? }
|
|
2616
|
+
@footer.multiline_buffer = []
|
|
2617
|
+
end
|
|
2618
|
+
|
|
2619
|
+
# Insert all lines as siblings at the same level
|
|
2620
|
+
all_lines.each do |line|
|
|
2621
|
+
insert_child_with_text(line.strip)
|
|
2622
|
+
@current -= 1 # Keep cursor at same position so next insert is sibling
|
|
2623
|
+
end
|
|
2624
|
+
@current += 1 # Move to last inserted item
|
|
2625
|
+
|
|
2379
2626
|
record_last_action(:insert_child, input)
|
|
2380
2627
|
end
|
|
2381
|
-
|
|
2628
|
+
|
|
2382
2629
|
@mode = :normal
|
|
2383
2630
|
@footer.clear # Clear footer immediately
|
|
2384
2631
|
@footer.refresh
|
|
@@ -2386,14 +2633,43 @@ class HyperListApp
|
|
|
2386
2633
|
|
|
2387
2634
|
def insert_outdented
|
|
2388
2635
|
@mode = :insert
|
|
2389
|
-
|
|
2636
|
+
|
|
2390
2637
|
input = @footer.ask("New outdented item: ", "")
|
|
2391
|
-
|
|
2638
|
+
|
|
2392
2639
|
if input && !input.strip.empty?
|
|
2393
|
-
|
|
2640
|
+
# Collect all lines (first line + buffer)
|
|
2641
|
+
all_lines = [input]
|
|
2642
|
+
if @footer.multiline_buffer && !@footer.multiline_buffer.empty?
|
|
2643
|
+
all_lines += @footer.multiline_buffer.reject { |l| l.strip.empty? }
|
|
2644
|
+
@footer.multiline_buffer = []
|
|
2645
|
+
end
|
|
2646
|
+
|
|
2647
|
+
# Calculate target level BEFORE inserting anything
|
|
2648
|
+
visible = get_visible_items
|
|
2649
|
+
if @current < visible.length
|
|
2650
|
+
current_level = visible[@current]["level"]
|
|
2651
|
+
target_level = [current_level - 1, 0].max # Outdented level
|
|
2652
|
+
else
|
|
2653
|
+
target_level = 0
|
|
2654
|
+
end
|
|
2655
|
+
|
|
2656
|
+
# Insert first line normally
|
|
2657
|
+
insert_outdented_with_text(all_lines[0].strip)
|
|
2658
|
+
|
|
2659
|
+
# Insert remaining lines at same level as the first
|
|
2660
|
+
if all_lines.length > 1
|
|
2661
|
+
all_lines[1..-1].each do |line|
|
|
2662
|
+
visible = get_visible_items
|
|
2663
|
+
real_idx = get_real_index(visible[@current])
|
|
2664
|
+
@items.insert(real_idx + 1, {"text" => line.strip, "level" => target_level, "fold" => false})
|
|
2665
|
+
@current += 1
|
|
2666
|
+
end
|
|
2667
|
+
@modified = true
|
|
2668
|
+
end
|
|
2669
|
+
|
|
2394
2670
|
record_last_action(:insert_outdented, input)
|
|
2395
2671
|
end
|
|
2396
|
-
|
|
2672
|
+
|
|
2397
2673
|
@mode = :normal
|
|
2398
2674
|
@footer.clear # Clear footer immediately
|
|
2399
2675
|
@footer.refresh
|
|
@@ -2523,82 +2799,146 @@ class HyperListApp
|
|
|
2523
2799
|
visible = get_visible_items
|
|
2524
2800
|
return if visible.empty?
|
|
2525
2801
|
return if @current >= visible.length
|
|
2526
|
-
|
|
2802
|
+
|
|
2527
2803
|
save_undo_state # Save state before modification
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2804
|
+
|
|
2805
|
+
# Handle tagged items
|
|
2806
|
+
if @tagged_items.any?
|
|
2807
|
+
@clipboard = []
|
|
2808
|
+
@clipboard_is_tree = false
|
|
2809
|
+
|
|
2810
|
+
# Sort tagged indices in reverse to delete from end to start
|
|
2811
|
+
@tagged_items.sort.reverse.each do |real_idx|
|
|
2812
|
+
next if real_idx >= @items.length
|
|
2813
|
+
@clipboard.unshift(@items[real_idx].dup)
|
|
2814
|
+
if with_children
|
|
2815
|
+
# Delete item and children
|
|
2816
|
+
level = @items[real_idx]["level"]
|
|
2817
|
+
delete_count = 1
|
|
2818
|
+
((real_idx + 1)...@items.length).each do |i|
|
|
2819
|
+
if @items[i]["level"] > level
|
|
2820
|
+
@clipboard << @items[i].dup
|
|
2821
|
+
delete_count += 1
|
|
2822
|
+
else
|
|
2823
|
+
break
|
|
2824
|
+
end
|
|
2825
|
+
end
|
|
2826
|
+
delete_count.times { @items.delete_at(real_idx) }
|
|
2547
2827
|
else
|
|
2548
|
-
|
|
2828
|
+
@items.delete_at(real_idx)
|
|
2549
2829
|
end
|
|
2550
2830
|
end
|
|
2831
|
+
|
|
2832
|
+
@message = "Deleted and yanked #{@tagged_items.length} tagged item(s)"
|
|
2833
|
+
@tagged_items = [] # Clear tags after operation
|
|
2551
2834
|
else
|
|
2552
|
-
#
|
|
2553
|
-
|
|
2554
|
-
|
|
2835
|
+
# Normal single item delete
|
|
2836
|
+
item = visible[@current]
|
|
2837
|
+
real_idx = get_real_index(item)
|
|
2838
|
+
|
|
2839
|
+
# First, yank the item(s) to clipboard
|
|
2840
|
+
@clipboard = []
|
|
2841
|
+
@clipboard << item.dup
|
|
2842
|
+
|
|
2843
|
+
# Determine what to delete
|
|
2844
|
+
level = item["level"]
|
|
2845
|
+
delete_count = 1
|
|
2846
|
+
|
|
2847
|
+
if with_children
|
|
2848
|
+
# Delete item and its children (C-D was used)
|
|
2849
|
+
@clipboard_is_tree = true # Mark as tree for paste behavior
|
|
2850
|
+
((real_idx + 1)...@items.length).each do |i|
|
|
2851
|
+
if @items[i]["level"] > level
|
|
2852
|
+
@clipboard << @items[i].dup # Also add children to clipboard
|
|
2853
|
+
delete_count += 1
|
|
2854
|
+
else
|
|
2855
|
+
break
|
|
2856
|
+
end
|
|
2857
|
+
end
|
|
2858
|
+
else
|
|
2859
|
+
# For single line delete (D key - delete only the current line)
|
|
2860
|
+
@clipboard_is_tree = false # Mark as single/adaptive for paste behavior
|
|
2861
|
+
# Don't include children - delete_count stays at 1
|
|
2862
|
+
end
|
|
2863
|
+
|
|
2864
|
+
# Delete the items
|
|
2865
|
+
# Remember the level of the deleted item for renumbering
|
|
2866
|
+
deleted_level = item["level"]
|
|
2867
|
+
|
|
2868
|
+
delete_count.times { @items.delete_at(real_idx) }
|
|
2869
|
+
|
|
2870
|
+
# Renumber siblings at the deleted item's level
|
|
2871
|
+
renumber_siblings(deleted_level) unless @items.length == 1
|
|
2872
|
+
|
|
2873
|
+
@message = "Deleted and yanked #{@clipboard.length} item(s)"
|
|
2555
2874
|
end
|
|
2556
|
-
|
|
2557
|
-
# Delete the items
|
|
2558
|
-
# Remember the level of the deleted item for renumbering
|
|
2559
|
-
deleted_level = item["level"]
|
|
2560
|
-
|
|
2561
|
-
delete_count.times { @items.delete_at(real_idx) }
|
|
2562
|
-
|
|
2875
|
+
|
|
2563
2876
|
@items = [{"text" => "Empty", "level" => 0, "fold" => false}] if @items.empty?
|
|
2564
|
-
|
|
2565
|
-
# Renumber siblings at the deleted item's level
|
|
2566
|
-
renumber_siblings(deleted_level) unless @items.length == 1
|
|
2567
|
-
|
|
2877
|
+
|
|
2568
2878
|
@current = [@current, get_visible_items.length - 1].min
|
|
2569
2879
|
@current = 0 if @current < 0
|
|
2570
2880
|
@modified = true
|
|
2571
|
-
|
|
2572
|
-
# Show message
|
|
2573
|
-
@message = "Deleted and yanked #{@clipboard.length} item(s)"
|
|
2574
|
-
|
|
2881
|
+
|
|
2575
2882
|
record_last_action(:delete_line, with_children)
|
|
2576
2883
|
end
|
|
2577
2884
|
|
|
2578
2885
|
def yank_line(with_children = false)
|
|
2579
2886
|
visible = get_visible_items
|
|
2580
2887
|
return if @current >= visible.length
|
|
2581
|
-
|
|
2582
|
-
item = visible[@current]
|
|
2583
|
-
real_idx = get_real_index(item)
|
|
2584
|
-
|
|
2888
|
+
|
|
2585
2889
|
@clipboard = []
|
|
2586
|
-
@
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
if
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2890
|
+
@clipboard_is_tree = with_children
|
|
2891
|
+
|
|
2892
|
+
# Handle tagged items
|
|
2893
|
+
if @tagged_items.any?
|
|
2894
|
+
@tagged_items.sort.each do |real_idx|
|
|
2895
|
+
next if real_idx >= @items.length
|
|
2896
|
+
@clipboard << @items[real_idx].dup
|
|
2897
|
+
if with_children
|
|
2898
|
+
# Copy children too
|
|
2899
|
+
level = @items[real_idx]["level"]
|
|
2900
|
+
((real_idx + 1)...@items.length).each do |i|
|
|
2901
|
+
if @items[i]["level"] > level
|
|
2902
|
+
@clipboard << @items[i].dup
|
|
2903
|
+
else
|
|
2904
|
+
break
|
|
2905
|
+
end
|
|
2906
|
+
end
|
|
2907
|
+
end
|
|
2908
|
+
end
|
|
2909
|
+
|
|
2910
|
+
@message = "Yanked #{@tagged_items.length} tagged item(s)"
|
|
2911
|
+
@tagged_items = [] # Clear tags after operation
|
|
2912
|
+
else
|
|
2913
|
+
# Normal single item yank
|
|
2914
|
+
item = visible[@current]
|
|
2915
|
+
real_idx = get_real_index(item)
|
|
2916
|
+
|
|
2917
|
+
@clipboard << item.dup
|
|
2918
|
+
|
|
2919
|
+
# Copy children if requested
|
|
2920
|
+
if with_children
|
|
2921
|
+
level = item["level"]
|
|
2922
|
+
((real_idx + 1)...@items.length).each do |i|
|
|
2923
|
+
if @items[i]["level"] > level
|
|
2924
|
+
@clipboard << @items[i].dup
|
|
2925
|
+
else
|
|
2926
|
+
break
|
|
2927
|
+
end
|
|
2597
2928
|
end
|
|
2598
2929
|
end
|
|
2930
|
+
|
|
2931
|
+
@message = "Yanked #{@clipboard.length} item(s)"
|
|
2599
2932
|
end
|
|
2600
|
-
|
|
2601
|
-
|
|
2933
|
+
|
|
2934
|
+
# Copy to system clipboard for middle-click paste
|
|
2935
|
+
begin
|
|
2936
|
+
text_to_copy = @clipboard.map { |it| " " * it["level"] + it["text"] }.join("\n")
|
|
2937
|
+
Clipboard.copy(text_to_copy)
|
|
2938
|
+
rescue => e
|
|
2939
|
+
# Silently fail if clipboard gem not available
|
|
2940
|
+
end
|
|
2941
|
+
|
|
2602
2942
|
record_last_action(:yank_line, with_children)
|
|
2603
2943
|
end
|
|
2604
2944
|
|
|
@@ -2641,6 +2981,137 @@ class HyperListApp
|
|
|
2641
2981
|
record_last_action(:paste, nil)
|
|
2642
2982
|
end
|
|
2643
2983
|
|
|
2984
|
+
def paste_above
|
|
2985
|
+
return unless @clipboard && !@clipboard.empty?
|
|
2986
|
+
|
|
2987
|
+
save_undo_state # Save state before modification
|
|
2988
|
+
|
|
2989
|
+
visible = get_visible_items
|
|
2990
|
+
if @current < visible.length
|
|
2991
|
+
real_idx = get_real_index(visible[@current])
|
|
2992
|
+
|
|
2993
|
+
if @clipboard_is_tree
|
|
2994
|
+
# For tree paste (C-D or Y), maintain original indentation structure
|
|
2995
|
+
@clipboard.reverse.each do |item|
|
|
2996
|
+
new_item = item.dup
|
|
2997
|
+
@items.insert(real_idx, new_item)
|
|
2998
|
+
end
|
|
2999
|
+
else
|
|
3000
|
+
# For single/adaptive paste (D or y), adjust to match context
|
|
3001
|
+
base_level = visible[@current]["level"]
|
|
3002
|
+
level_diff = base_level - @clipboard[0]["level"]
|
|
3003
|
+
|
|
3004
|
+
@clipboard.reverse.each do |item|
|
|
3005
|
+
new_item = item.dup
|
|
3006
|
+
new_item["level"] = item["level"] + level_diff
|
|
3007
|
+
@items.insert(real_idx, new_item)
|
|
3008
|
+
end
|
|
3009
|
+
end
|
|
3010
|
+
else
|
|
3011
|
+
# Pasting at end of list - maintain original levels
|
|
3012
|
+
@clipboard.each do |item|
|
|
3013
|
+
@items << item.dup
|
|
3014
|
+
end
|
|
3015
|
+
end
|
|
3016
|
+
|
|
3017
|
+
@modified = true
|
|
3018
|
+
@message = "Pasted #{@clipboard.length} item(s) above"
|
|
3019
|
+
record_last_action(:paste_above, nil)
|
|
3020
|
+
end
|
|
3021
|
+
|
|
3022
|
+
def toggle_tag
|
|
3023
|
+
visible = get_visible_items
|
|
3024
|
+
return if @current >= visible.length
|
|
3025
|
+
|
|
3026
|
+
real_idx = get_real_index(visible[@current])
|
|
3027
|
+
|
|
3028
|
+
if @tagged_items.include?(real_idx)
|
|
3029
|
+
@tagged_items.delete(real_idx)
|
|
3030
|
+
@message = "Untagged item (#{@tagged_items.length} tagged)"
|
|
3031
|
+
else
|
|
3032
|
+
@tagged_items << real_idx
|
|
3033
|
+
@message = "Tagged item (#{@tagged_items.length} tagged)"
|
|
3034
|
+
# Auto-advance cursor for easy consecutive tagging
|
|
3035
|
+
@current += 1 if @current < visible.length - 1
|
|
3036
|
+
end
|
|
3037
|
+
end
|
|
3038
|
+
|
|
3039
|
+
def clear_tags
|
|
3040
|
+
count = @tagged_items.length
|
|
3041
|
+
@tagged_items = []
|
|
3042
|
+
@message = "Cleared #{count} tagged item(s)"
|
|
3043
|
+
end
|
|
3044
|
+
|
|
3045
|
+
def tag_by_regex
|
|
3046
|
+
pattern = @footer.ask("Tag pattern (regex): ", "")
|
|
3047
|
+
return if pattern.nil? || pattern.strip.empty?
|
|
3048
|
+
|
|
3049
|
+
begin
|
|
3050
|
+
regex = Regexp.new(pattern, Regexp::IGNORECASE)
|
|
3051
|
+
count = 0
|
|
3052
|
+
|
|
3053
|
+
@items.each_with_index do |item, idx|
|
|
3054
|
+
if item["text"] =~ regex
|
|
3055
|
+
@tagged_items << idx unless @tagged_items.include?(idx)
|
|
3056
|
+
count += 1
|
|
3057
|
+
end
|
|
3058
|
+
end
|
|
3059
|
+
|
|
3060
|
+
@tagged_items.uniq!
|
|
3061
|
+
@message = "Tagged #{count} item(s) matching /#{pattern}/"
|
|
3062
|
+
rescue RegexpError => e
|
|
3063
|
+
@message = "Invalid regex: #{e.message}"
|
|
3064
|
+
end
|
|
3065
|
+
end
|
|
3066
|
+
|
|
3067
|
+
def spawn_editor
|
|
3068
|
+
return unless @filename
|
|
3069
|
+
|
|
3070
|
+
# Save file before editing
|
|
3071
|
+
save_file
|
|
3072
|
+
|
|
3073
|
+
# Save terminal state
|
|
3074
|
+
system("stty -g < /dev/tty > /tmp/hyperlist_stty_$$")
|
|
3075
|
+
|
|
3076
|
+
# Reset terminal to cooked mode for editor
|
|
3077
|
+
system('stty sane < /dev/tty')
|
|
3078
|
+
system('clear < /dev/tty > /dev/tty')
|
|
3079
|
+
Rcurses::Cursor.show
|
|
3080
|
+
|
|
3081
|
+
# Launch editor
|
|
3082
|
+
editor = ENV.fetch('EDITOR', 'vi')
|
|
3083
|
+
system("#{editor} #{Shellwords.escape(@filename)}")
|
|
3084
|
+
|
|
3085
|
+
# Restore terminal state
|
|
3086
|
+
system("stty $(cat /tmp/hyperlist_stty_$$) < /dev/tty")
|
|
3087
|
+
system("rm -f /tmp/hyperlist_stty_$$")
|
|
3088
|
+
|
|
3089
|
+
# Flush input and reset stdin
|
|
3090
|
+
$stdin.iflush if $stdin.respond_to?(:iflush)
|
|
3091
|
+
system('stty raw -echo isig < /dev/tty')
|
|
3092
|
+
$stdin.raw!
|
|
3093
|
+
$stdin.echo = false
|
|
3094
|
+
|
|
3095
|
+
# Reinitialize rcurses
|
|
3096
|
+
Rcurses.init!
|
|
3097
|
+
Rcurses::Cursor.hide
|
|
3098
|
+
Rcurses.clear_screen
|
|
3099
|
+
|
|
3100
|
+
# Reload the file
|
|
3101
|
+
load_file(@filename)
|
|
3102
|
+
@current = 0
|
|
3103
|
+
@offset = 0
|
|
3104
|
+
|
|
3105
|
+
@message = "File reloaded from $EDITOR"
|
|
3106
|
+
|
|
3107
|
+
# Force complete screen refresh
|
|
3108
|
+
clear_cache
|
|
3109
|
+
@main.full_refresh if @main
|
|
3110
|
+
@footer.full_refresh if @footer
|
|
3111
|
+
@split_pane.full_refresh if @split_pane && @split_view
|
|
3112
|
+
render
|
|
3113
|
+
end
|
|
3114
|
+
|
|
2644
3115
|
def calculate_level_for_position(target_real_idx)
|
|
2645
3116
|
# Calculate appropriate level for an item at target_real_idx position
|
|
2646
3117
|
# based on surrounding items in the @items array
|
|
@@ -2905,64 +3376,122 @@ class HyperListApp
|
|
|
2905
3376
|
def indent_right(with_children = true)
|
|
2906
3377
|
visible = get_visible_items
|
|
2907
3378
|
return if @current >= visible.length
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
3379
|
+
|
|
3380
|
+
save_undo_state # Save state before modification
|
|
3381
|
+
|
|
3382
|
+
# Handle tagged items
|
|
3383
|
+
if @tagged_items.any?
|
|
3384
|
+
@tagged_items.sort.each do |real_idx|
|
|
3385
|
+
next if real_idx >= @items.length
|
|
3386
|
+
next if real_idx == 0 # Can't indent first item
|
|
3387
|
+
|
|
3388
|
+
original_level = @items[real_idx]["level"]
|
|
3389
|
+
@items[real_idx]["level"] += 1
|
|
3390
|
+
|
|
3391
|
+
# Also indent children if requested
|
|
3392
|
+
if with_children
|
|
3393
|
+
((real_idx + 1)...@items.length).each do |i|
|
|
3394
|
+
if @items[i]["level"] > original_level
|
|
3395
|
+
@items[i]["level"] += 1
|
|
3396
|
+
else
|
|
3397
|
+
break
|
|
3398
|
+
end
|
|
2925
3399
|
end
|
|
2926
3400
|
end
|
|
2927
3401
|
end
|
|
2928
|
-
|
|
2929
|
-
# Ensure the moved item remains visible
|
|
2930
|
-
force_item_visible(real_idx)
|
|
2931
3402
|
|
|
3403
|
+
@message = "Indented #{@tagged_items.length} tagged item(s)"
|
|
3404
|
+
@tagged_items = [] # Clear tags after operation
|
|
2932
3405
|
@modified = true
|
|
2933
|
-
|
|
3406
|
+
else
|
|
3407
|
+
# Normal single item indent
|
|
3408
|
+
item = visible[@current]
|
|
3409
|
+
real_idx = get_real_index(item)
|
|
3410
|
+
|
|
3411
|
+
# Can only indent if there's a previous item at same or higher level
|
|
3412
|
+
if real_idx > 0
|
|
3413
|
+
original_level = @items[real_idx]["level"]
|
|
3414
|
+
@items[real_idx]["level"] += 1
|
|
3415
|
+
|
|
3416
|
+
# Also indent children if requested
|
|
3417
|
+
if with_children
|
|
3418
|
+
((real_idx + 1)...@items.length).each do |i|
|
|
3419
|
+
if @items[i]["level"] > original_level
|
|
3420
|
+
@items[i]["level"] += 1
|
|
3421
|
+
else
|
|
3422
|
+
break
|
|
3423
|
+
end
|
|
3424
|
+
end
|
|
3425
|
+
end
|
|
3426
|
+
|
|
3427
|
+
# Ensure the moved item remains visible
|
|
3428
|
+
force_item_visible(real_idx)
|
|
3429
|
+
|
|
3430
|
+
@modified = true
|
|
3431
|
+
end
|
|
2934
3432
|
end
|
|
3433
|
+
|
|
3434
|
+
record_last_action(:indent_right, with_children)
|
|
2935
3435
|
end
|
|
2936
3436
|
|
|
2937
3437
|
def indent_left(with_children = true)
|
|
2938
3438
|
visible = get_visible_items
|
|
2939
3439
|
return if @current >= visible.length
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
if @
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
3440
|
+
|
|
3441
|
+
save_undo_state # Save state before modification
|
|
3442
|
+
|
|
3443
|
+
# Handle tagged items
|
|
3444
|
+
if @tagged_items.any?
|
|
3445
|
+
@tagged_items.sort.each do |real_idx|
|
|
3446
|
+
next if real_idx >= @items.length
|
|
3447
|
+
next if @items[real_idx]["level"] == 0 # Already at leftmost
|
|
3448
|
+
|
|
3449
|
+
original_level = @items[real_idx]["level"]
|
|
3450
|
+
@items[real_idx]["level"] -= 1
|
|
3451
|
+
|
|
3452
|
+
# Also unindent children if requested
|
|
3453
|
+
if with_children
|
|
3454
|
+
((real_idx + 1)...@items.length).each do |i|
|
|
3455
|
+
if @items[i]["level"] > original_level
|
|
3456
|
+
@items[i]["level"] -= 1
|
|
3457
|
+
else
|
|
3458
|
+
break
|
|
3459
|
+
end
|
|
2956
3460
|
end
|
|
2957
3461
|
end
|
|
2958
3462
|
end
|
|
2959
|
-
|
|
2960
|
-
# Ensure the moved item remains visible
|
|
2961
|
-
force_item_visible(real_idx)
|
|
2962
3463
|
|
|
3464
|
+
@message = "Unindented #{@tagged_items.length} tagged item(s)"
|
|
3465
|
+
@tagged_items = [] # Clear tags after operation
|
|
2963
3466
|
@modified = true
|
|
2964
|
-
|
|
3467
|
+
else
|
|
3468
|
+
# Normal single item unindent
|
|
3469
|
+
item = visible[@current]
|
|
3470
|
+
real_idx = get_real_index(item)
|
|
3471
|
+
|
|
3472
|
+
if @items[real_idx]["level"] > 0
|
|
3473
|
+
original_level = @items[real_idx]["level"]
|
|
3474
|
+
@items[real_idx]["level"] -= 1
|
|
3475
|
+
|
|
3476
|
+
# Also unindent children if requested
|
|
3477
|
+
if with_children
|
|
3478
|
+
((real_idx + 1)...@items.length).each do |i|
|
|
3479
|
+
if @items[i]["level"] > original_level
|
|
3480
|
+
@items[i]["level"] -= 1
|
|
3481
|
+
else
|
|
3482
|
+
break
|
|
3483
|
+
end
|
|
3484
|
+
end
|
|
3485
|
+
end
|
|
3486
|
+
|
|
3487
|
+
# Ensure the moved item remains visible
|
|
3488
|
+
force_item_visible(real_idx)
|
|
3489
|
+
|
|
3490
|
+
@modified = true
|
|
3491
|
+
end
|
|
2965
3492
|
end
|
|
3493
|
+
|
|
3494
|
+
record_last_action(:indent_left, with_children)
|
|
2966
3495
|
end
|
|
2967
3496
|
|
|
2968
3497
|
def toggle_checkbox
|
|
@@ -3197,8 +3726,9 @@ class HyperListApp
|
|
|
3197
3726
|
help_lines << help_line("#{"gU".fg("10")}", "Uppercase line", "#{"gu".fg("10")}", "Lowercase line")
|
|
3198
3727
|
help_lines << help_line("#{"I".fg("10")}", "Cycle indent (2-5)")
|
|
3199
3728
|
help_lines << help_line("#{"D".fg("10")}", "Delete+yank line", "#{"C-D".fg("10")}", "Delete+yank item&descendants")
|
|
3200
|
-
help_lines << help_line("#{"y".fg("10")}" + "/".fg("10") + "#{"Y".fg("10")}", "Copy line/tree", "#{"p".fg("10")}", "Paste")
|
|
3201
|
-
help_lines << help_line("#{"
|
|
3729
|
+
help_lines << help_line("#{"y".fg("10")}" + "/".fg("10") + "#{"Y".fg("10")}", "Copy line/tree", "#{"p".fg("10")}", "Paste below")
|
|
3730
|
+
help_lines << help_line("#{"P".fg("10")}", "Paste above", "#{"E".fg("10")}", "Edit in $EDITOR")
|
|
3731
|
+
help_lines << help_line("#{"U".fg("10")}", "Undo", "#{".".fg("10")}", "Repeat last action")
|
|
3202
3732
|
help_lines << help_line("#{"r".fg("10")}" + ", ".fg("10") + "#{"C-R".fg("10")}", "Redo")
|
|
3203
3733
|
help_lines << help_line("#{"S-UP".fg("10")}", "Move item up", "#{"S-DOWN".fg("10")}", "Move item down")
|
|
3204
3734
|
help_lines << help_line("#{"C-UP".fg("10")}", "Move up in visible list", "#{"C-DOWN".fg("10")}", "Move down in visible list")
|
|
@@ -3209,20 +3739,27 @@ class HyperListApp
|
|
|
3209
3739
|
help_lines << help_line("#{"v".fg("10")}", "Toggle checkbox", "#{"V".fg("10")}", "Checkbox with date")
|
|
3210
3740
|
help_lines << help_line("#{"C-X".fg("10")}", "Remove checkbox", "", "")
|
|
3211
3741
|
help_lines << help_line("#{"C-E".fg("10")}", "Encrypt/decrypt line", "#{"C-U".fg("10")}", "Toggle State/Trans underline")
|
|
3212
|
-
help_lines << help_line("#{"P".fg("10")}", "Presentation mode", "#{"Tab/S-Tab".fg("10")}", "Next/prev sibling (in P)")
|
|
3742
|
+
help_lines << help_line("#{"C-P".fg("10")}", "Presentation mode", "#{"Tab/S-Tab".fg("10")}", "Next/prev sibling (in P)")
|
|
3213
3743
|
help_lines << help_line("#{"Ma".fg("10")}", "Record macro 'a'", "#{"@a".fg("10")}", "Play macro 'a'")
|
|
3214
3744
|
help_lines << help_line("#{"w".fg("10")}", "Switch panes (split view)")
|
|
3215
3745
|
help_lines << ""
|
|
3216
3746
|
help_lines << "#{"FILE OPERATIONS".fg("14")}"
|
|
3217
3747
|
help_lines << help_line("#{":w".fg("10")}", "Save", "#{":q".fg("10")}", "Quit")
|
|
3218
3748
|
help_lines << help_line("#{":wq".fg("10")}", "Save and quit", "#{":e file".fg("10")}", "Open file")
|
|
3219
|
-
help_lines << help_line("#{":recent".fg("10")}", "Recent files", "#{":export :ex".fg("10")}", "Export md|html|txt")
|
|
3749
|
+
help_lines << help_line("#{":recent".fg("10")}", "Recent files", "#{":export :ex".fg("10")}", "Export md|html|txt|pdf")
|
|
3220
3750
|
help_lines << help_line("#{":graph :g".fg("10")}", "Export to PNG graph", "#{":vsplit :vs".fg("10")}", "Split view")
|
|
3221
3751
|
help_lines << help_line("#{":as on".fg("10")}", "Enable autosave", "#{":as off".fg("10")}", "Disable autosave")
|
|
3222
3752
|
help_lines << help_line("#{":as N".fg("10")}", "Set interval (secs)", "#{":as".fg("10")}", "Show autosave status")
|
|
3223
3753
|
help_lines << ""
|
|
3754
|
+
help_lines << "#{"TAGGING & BATCH OPS".fg("14")}"
|
|
3755
|
+
help_lines << help_line("#{"t".fg("10")}", "Tag/untag item", "#{"u".fg("10")}", "Clear all tags")
|
|
3756
|
+
help_lines << help_line("#{"C-T".fg("10")}", "Tag by regex pattern", "", "")
|
|
3757
|
+
help_lines << help_line("#{"[T:N]".fg("245")}", "Status shows N tagged", "#{"Blue bg".fg("245")}", "Tagged items")
|
|
3758
|
+
help_lines << help_line("#{"D/y/Tab".fg("245")}", "Ops work on tagged items")
|
|
3759
|
+
help_lines << ""
|
|
3760
|
+
|
|
3224
3761
|
help_lines << "#{"TEMPLATES".fg("14")}"
|
|
3225
|
-
help_lines << help_line("#{"
|
|
3762
|
+
help_lines << help_line("#{"T".fg("10")}", "Insert template", "#{":st".fg("10")}", "Save as template")
|
|
3226
3763
|
help_lines << help_line("#{":dt".fg("10")}", "Delete template", "#{":lt".fg("10")}", "List user templates")
|
|
3227
3764
|
help_lines << ""
|
|
3228
3765
|
|
|
@@ -3900,12 +4437,12 @@ class HyperListApp
|
|
|
3900
4437
|
@current = 0
|
|
3901
4438
|
@offset = 0
|
|
3902
4439
|
@modified = false
|
|
3903
|
-
when /^(export|ex)\s+(md|markdown|html|txt|text)\s*(.*)$/
|
|
4440
|
+
when /^(export|ex)\s+(md|markdown|html|txt|text|latex|tex|pdf)\s*(.*)$/
|
|
3904
4441
|
format = $2
|
|
3905
4442
|
export_file = $3.empty? ? nil : $3
|
|
3906
4443
|
export_to(format, export_file)
|
|
3907
4444
|
when /^(export|ex)$/
|
|
3908
|
-
@message = "Usage: :export [md|html|txt] [filename] (or :ex)"
|
|
4445
|
+
@message = "Usage: :export [md|html|txt|latex|pdf] [filename] (or :ex)"
|
|
3909
4446
|
when "recent", "r"
|
|
3910
4447
|
show_recent_files
|
|
3911
4448
|
when "autosave on", "as on"
|
|
@@ -4865,7 +5402,7 @@ class HyperListApp
|
|
|
4865
5402
|
@items << {"text" => "="*50, "level" => 0, "fold" => false, "raw" => true}
|
|
4866
5403
|
|
|
4867
5404
|
# Add built-in templates section
|
|
4868
|
-
@items << {"text" => "BUILT-IN TEMPLATES:", "level" => 0, "fold" => false, "raw" => true}
|
|
5405
|
+
@items << {"text" => "BUILT-IN TEMPLATES:".fg("39"), "level" => 0, "fold" => false, "raw" => true}
|
|
4869
5406
|
template_list.select { |t| t[2] == "built-in" }.each_with_index do |(key, desc, type), idx|
|
|
4870
5407
|
@items << {
|
|
4871
5408
|
"text" => " #{idx+1}. #{key.capitalize}: #{desc}",
|
|
@@ -4879,7 +5416,7 @@ class HyperListApp
|
|
|
4879
5416
|
# Add user templates section if any exist
|
|
4880
5417
|
if user_templates.any?
|
|
4881
5418
|
@items << {"text" => "", "level" => 0, "fold" => false, "raw" => true}
|
|
4882
|
-
@items << {"text" => "USER TEMPLATES:", "level" => 0, "fold" => false, "raw" => true}
|
|
5419
|
+
@items << {"text" => "USER TEMPLATES:".fg("39"), "level" => 0, "fold" => false, "raw" => true}
|
|
4883
5420
|
template_list.select { |t| t[2] == "user" }.each_with_index do |(key, desc, type), idx|
|
|
4884
5421
|
@items << {
|
|
4885
5422
|
"text" => " #{key}: #{desc}",
|
|
@@ -5110,6 +5647,8 @@ class HyperListApp
|
|
|
5110
5647
|
when " "
|
|
5111
5648
|
toggle_fold
|
|
5112
5649
|
when "u"
|
|
5650
|
+
clear_tags
|
|
5651
|
+
when "U"
|
|
5113
5652
|
undo
|
|
5114
5653
|
when "/"
|
|
5115
5654
|
# Skip search in macros for now
|
|
@@ -6015,11 +6554,17 @@ class HyperListApp
|
|
|
6015
6554
|
# Cycle through indentation sizes (2-5 spaces)
|
|
6016
6555
|
cycle_indent_size
|
|
6017
6556
|
when "t"
|
|
6557
|
+
toggle_tag
|
|
6558
|
+
when "T"
|
|
6018
6559
|
show_templates
|
|
6560
|
+
when "C-T"
|
|
6561
|
+
tag_by_regex
|
|
6019
6562
|
when "D" # Delete line only (without children)
|
|
6020
6563
|
delete_line(false) # Delete current line only
|
|
6021
6564
|
when "C-D" # Delete line and all descendants explicitly
|
|
6022
6565
|
delete_line(true)
|
|
6566
|
+
when "E"
|
|
6567
|
+
spawn_editor
|
|
6023
6568
|
when "C-E" # Toggle line encryption
|
|
6024
6569
|
toggle_line_encryption
|
|
6025
6570
|
when "y" # Yank/copy single line
|
|
@@ -6065,6 +6610,8 @@ class HyperListApp
|
|
|
6065
6610
|
end
|
|
6066
6611
|
end
|
|
6067
6612
|
when "u"
|
|
6613
|
+
clear_tags
|
|
6614
|
+
when "U"
|
|
6068
6615
|
undo
|
|
6069
6616
|
when "\x12" # Ctrl-R for redo (0x12 is Ctrl-R ASCII code)
|
|
6070
6617
|
redo_change
|
|
@@ -6083,6 +6630,8 @@ class HyperListApp
|
|
|
6083
6630
|
when "N"
|
|
6084
6631
|
jump_to_next_template_marker
|
|
6085
6632
|
when "P"
|
|
6633
|
+
paste_above
|
|
6634
|
+
when "C-P"
|
|
6086
6635
|
toggle_presentation_mode
|
|
6087
6636
|
when "\x15" # Ctrl-U for State/Transition underline toggle
|
|
6088
6637
|
# Cycle through underline modes: 0 (none) -> 1 (states) -> 2 (transitions) -> 0
|
data/hyperlist.gemspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Gem::Specification.new do |spec|
|
|
2
2
|
spec.name = "hyperlist"
|
|
3
|
-
spec.version = "1.
|
|
3
|
+
spec.version = "1.9.0"
|
|
4
4
|
spec.authors = ["Geir Isene"]
|
|
5
5
|
spec.email = ["g@isene.com"]
|
|
6
6
|
|
|
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
|
|
|
28
28
|
spec.require_paths = ["."]
|
|
29
29
|
|
|
30
30
|
# Runtime dependencies
|
|
31
|
-
spec.add_runtime_dependency "rcurses", "~>
|
|
31
|
+
spec.add_runtime_dependency "rcurses", "~> 6.1", ">= 6.1.5"
|
|
32
32
|
|
|
33
33
|
# Development dependencies
|
|
34
34
|
spec.add_development_dependency "minitest", "~> 5.0"
|
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
|
+
version: 1.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Geir Isene
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: "."
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-10-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rcurses
|
|
@@ -16,20 +16,20 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '6.1'
|
|
20
20
|
- - ">="
|
|
21
21
|
- !ruby/object:Gem::Version
|
|
22
|
-
version:
|
|
22
|
+
version: 6.1.5
|
|
23
23
|
type: :runtime
|
|
24
24
|
prerelease: false
|
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
26
26
|
requirements:
|
|
27
27
|
- - "~>"
|
|
28
28
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '
|
|
29
|
+
version: '6.1'
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version:
|
|
32
|
+
version: 6.1.5
|
|
33
33
|
- !ruby/object:Gem::Dependency
|
|
34
34
|
name: minitest
|
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -79,7 +79,6 @@ files:
|
|
|
79
79
|
- img/screenshot_help.png
|
|
80
80
|
- img/screenshot_sample.png
|
|
81
81
|
- sample.hl
|
|
82
|
-
- test_visibility.hl
|
|
83
82
|
homepage: https://github.com/isene/HyperList
|
|
84
83
|
licenses:
|
|
85
84
|
- Unlicense
|