hyperlist 1.7.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +25 -12
- data/hyperlist +288 -27
- data/hyperlist.gemspec +2 -2
- metadata +6 -7
- data/test_resize.hl +0 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3ab71a37a8c51bc72f5a944172d2fae808da9ed42e4a21e13f937d2a4410596a
|
|
4
|
+
data.tar.gz: 346c2edd59e6a6e4a116d05a506ded75b313996baa06f938def3fb0947c05850
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b66f71b652663dbf1fcbcee37fa7478b718813a7dd234081aad77e9110d1e01c472f2ce3d2a325ad6cd1e922b2efacb398c531fab79cd594999c5e2db20ad039
|
|
7
|
+
data.tar.gz: 3f2a1573bb24704f3f3df797e8cd3766e78d265aa27c68ca41fc7326976a23ec361c9fc93c814b9a8afc7b9003921b7e0c3111e6bfbbc9df0ebf8efddee3401e
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the HyperList Ruby TUI will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.8.0] - 2025-09-22
|
|
6
|
+
|
|
7
|
+
### Enhanced
|
|
8
|
+
- **Guaranteed item visibility after all movement operations**
|
|
9
|
+
- Current item now ALWAYS remains visible after TAB/S-TAB indentation operations
|
|
10
|
+
- Added visibility enforcement to `indent_right()` and `indent_left()` functions
|
|
11
|
+
- Added visibility enforcement to split-view indentation operations
|
|
12
|
+
- Prevents items from becoming invisible when moved under collapsed parents
|
|
13
|
+
- Complements existing visibility protection for C-UP/C-DOWN movements
|
|
14
|
+
|
|
5
15
|
## [1.7.0] - 2025-09-18
|
|
6
16
|
|
|
7
17
|
### Added
|
data/README.md
CHANGED
|
@@ -29,17 +29,26 @@ 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.8.0
|
|
33
|
+
|
|
34
|
+
### 📋 Multi-Line Paste Support
|
|
35
|
+
- **Paste multiple lines**: When pasting multi-line content into item insertion prompts ('o', 'O', 'a', 'A'), each line becomes a separate item
|
|
36
|
+
- **Visual feedback**: Shows `[+N lines]` indicator during multi-line paste
|
|
37
|
+
- **Smart insertion**: All pasted lines inserted as siblings at the same level
|
|
38
|
+
- Great for importing bullet lists from PDFs, emails, or other documents
|
|
39
|
+
- Requires rcurses 6.1.5+
|
|
40
|
+
|
|
41
|
+
### 📄 PDF/LaTeX Export
|
|
42
|
+
- **Export to PDF**: `:export pdf filename.pdf` - Full LaTeX-based PDF generation
|
|
43
|
+
- **Export to LaTeX**: `:export latex filename.tex` - Get the LaTeX source
|
|
44
|
+
- **Professional output**: Color-coded elements, table of contents, headers
|
|
45
|
+
- **Complete HyperList support**: All syntax elements rendered beautifully
|
|
46
|
+
- Requires: texlive-latex-base and texlive-latex-extra packages
|
|
47
|
+
|
|
48
|
+
### 📋 System Clipboard Integration
|
|
49
|
+
- **Yank to clipboard**: 'y' and 'Y' now copy to system clipboard
|
|
50
|
+
- **Middle-click paste**: Yanked items can be pasted into other terminals
|
|
51
|
+
- **Preserves indentation**: Copied text maintains proper structure
|
|
43
52
|
|
|
44
53
|
## Previous Version Features (v1.4.0)
|
|
45
54
|
|
|
@@ -149,6 +158,8 @@ All `:set` commands automatically update the file's configuration line.
|
|
|
149
158
|
- **Dates**: `2025-08-12` or `2025-08-12 14:30`
|
|
150
159
|
|
|
151
160
|
### Export Formats
|
|
161
|
+
- **PDF**: Professional LaTeX-based PDF with color coding and TOC
|
|
162
|
+
- **LaTeX**: Source .tex files for customization
|
|
152
163
|
- **HTML**: Full-featured HTML with syntax highlighting
|
|
153
164
|
- **Markdown**: GitHub-flavored Markdown
|
|
154
165
|
- **Plain Text**: Clean text output
|
|
@@ -236,9 +247,11 @@ hyperlist file.txt # Open any text file
|
|
|
236
247
|
- `:q` - Quit
|
|
237
248
|
- `:wq` or `W` - Save and quit
|
|
238
249
|
- `:e file` - Open file
|
|
250
|
+
- `:export pdf` - Export to PDF (requires LaTeX)
|
|
251
|
+
- `:export latex` - Export to LaTeX source
|
|
239
252
|
- `:export html` - Export to HTML
|
|
240
253
|
- `:export md` - Export to Markdown
|
|
241
|
-
- `:graph` - Export to PNG
|
|
254
|
+
- `:graph` - Export to PNG graph
|
|
242
255
|
|
|
243
256
|
#### Template Commands
|
|
244
257
|
- `: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.8.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.8.0"
|
|
56
56
|
exit 0
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -72,7 +72,7 @@ class HyperListApp
|
|
|
72
72
|
include Rcurses::Input
|
|
73
73
|
include Rcurses::Cursor
|
|
74
74
|
|
|
75
|
-
VERSION = "1.
|
|
75
|
+
VERSION = "1.8.0"
|
|
76
76
|
|
|
77
77
|
def initialize(filename = nil)
|
|
78
78
|
@filename = filename ? File.expand_path(filename) : nil
|
|
@@ -724,10 +724,12 @@ class HyperListApp
|
|
|
724
724
|
when 'md', 'markdown' then '.md'
|
|
725
725
|
when 'html' then '.html'
|
|
726
726
|
when 'txt', 'text' then '.txt'
|
|
727
|
+
when 'latex', 'tex' then '.tex'
|
|
728
|
+
when 'pdf' then '.pdf'
|
|
727
729
|
end
|
|
728
730
|
export_file = base + extension
|
|
729
731
|
end
|
|
730
|
-
|
|
732
|
+
|
|
731
733
|
content = case format
|
|
732
734
|
when 'md', 'markdown'
|
|
733
735
|
export_to_markdown
|
|
@@ -735,10 +737,39 @@ class HyperListApp
|
|
|
735
737
|
export_to_html
|
|
736
738
|
when 'txt', 'text'
|
|
737
739
|
export_to_text
|
|
740
|
+
when 'latex', 'tex'
|
|
741
|
+
export_to_latex
|
|
742
|
+
when 'pdf'
|
|
743
|
+
export_to_latex
|
|
738
744
|
end
|
|
739
|
-
|
|
745
|
+
|
|
740
746
|
File.open(export_file, 'w') { |f| f.write(content) }
|
|
741
|
-
|
|
747
|
+
|
|
748
|
+
# For PDF, compile the LaTeX file
|
|
749
|
+
if format == 'pdf'
|
|
750
|
+
tex_file = export_file.sub(/\.pdf$/, '.tex')
|
|
751
|
+
File.open(tex_file, 'w') { |f| f.write(content) }
|
|
752
|
+
|
|
753
|
+
# Compile LaTeX to PDF
|
|
754
|
+
@message = "Compiling LaTeX to PDF..."
|
|
755
|
+
render_footer
|
|
756
|
+
|
|
757
|
+
result = system("pdflatex -interaction=nonstopmode -output-directory=#{File.dirname(tex_file)} #{tex_file} > /dev/null 2>&1")
|
|
758
|
+
if result
|
|
759
|
+
# Run twice for TOC
|
|
760
|
+
system("pdflatex -interaction=nonstopmode -output-directory=#{File.dirname(tex_file)} #{tex_file} > /dev/null 2>&1")
|
|
761
|
+
@message = "Exported to #{export_file}"
|
|
762
|
+
# Clean up auxiliary files
|
|
763
|
+
['aux', 'log', 'out', 'toc'].each do |ext|
|
|
764
|
+
aux_file = tex_file.sub(/\.tex$/, ".#{ext}")
|
|
765
|
+
File.delete(aux_file) if File.exist?(aux_file)
|
|
766
|
+
end
|
|
767
|
+
else
|
|
768
|
+
@message = "PDF compilation failed. Check #{tex_file}"
|
|
769
|
+
end
|
|
770
|
+
else
|
|
771
|
+
@message = "Exported to #{export_file}"
|
|
772
|
+
end
|
|
742
773
|
rescue => e
|
|
743
774
|
@message = "Export failed: #{e.message}"
|
|
744
775
|
end
|
|
@@ -948,7 +979,151 @@ class HyperListApp
|
|
|
948
979
|
end
|
|
949
980
|
lines.join("\n")
|
|
950
981
|
end
|
|
951
|
-
|
|
982
|
+
|
|
983
|
+
def escape_latex(text)
|
|
984
|
+
# Escape special LaTeX characters
|
|
985
|
+
text = text.gsub('\\', '\\textbackslash{}')
|
|
986
|
+
text = text.gsub('&', '\\&')
|
|
987
|
+
text = text.gsub('%', '\\%')
|
|
988
|
+
text = text.gsub('$', '\\$')
|
|
989
|
+
text = text.gsub('#', '\\#')
|
|
990
|
+
text = text.gsub('_', '\\_')
|
|
991
|
+
text = text.gsub('^', '\\textasciicircum{}')
|
|
992
|
+
text = text.gsub('~', '\\textasciitilde{}')
|
|
993
|
+
text
|
|
994
|
+
end
|
|
995
|
+
|
|
996
|
+
def export_to_latex
|
|
997
|
+
lines = []
|
|
998
|
+
|
|
999
|
+
# LaTeX header
|
|
1000
|
+
lines << "% Generated by HyperList - LaTeX Export"
|
|
1001
|
+
lines << "% Export date: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
1002
|
+
lines << ""
|
|
1003
|
+
lines << "\\documentclass[11pt,a4paper]{article}"
|
|
1004
|
+
lines << "\\usepackage[utf8]{inputenc}"
|
|
1005
|
+
lines << "\\usepackage[T1]{fontenc}"
|
|
1006
|
+
lines << "\\usepackage[english]{babel}"
|
|
1007
|
+
lines << "\\usepackage[margin=2.5cm]{geometry}"
|
|
1008
|
+
lines << "\\usepackage{xcolor}"
|
|
1009
|
+
lines << "\\usepackage{enumitem}"
|
|
1010
|
+
lines << "\\usepackage{fancyhdr}"
|
|
1011
|
+
lines << "\\usepackage{hyperref}"
|
|
1012
|
+
lines << "\\usepackage{tcolorbox}"
|
|
1013
|
+
lines << "\\usepackage{fontawesome5}"
|
|
1014
|
+
lines << ""
|
|
1015
|
+
lines << "% HyperList color definitions"
|
|
1016
|
+
lines << "\\definecolor{hloperator}{RGB}{41,128,185}"
|
|
1017
|
+
lines << "\\definecolor{hlproperty}{RGB}{231,76,60}"
|
|
1018
|
+
lines << "\\definecolor{hlqualifier}{RGB}{39,174,96}"
|
|
1019
|
+
lines << "\\definecolor{hlhashtag}{RGB}{243,156,18}"
|
|
1020
|
+
lines << "\\definecolor{hlreference}{RGB}{155,89,182}"
|
|
1021
|
+
lines << "\\definecolor{hlcomment}{RGB}{127,140,141}"
|
|
1022
|
+
lines << "\\definecolor{hlquote}{RGB}{22,160,133}"
|
|
1023
|
+
lines << "\\definecolor{hlstate}{RGB}{46,204,113}"
|
|
1024
|
+
lines << "\\definecolor{hltransition}{RGB}{230,126,34}"
|
|
1025
|
+
lines << ""
|
|
1026
|
+
lines << "% HyperList commands"
|
|
1027
|
+
lines << "\\newcommand{\\hloperator}[1]{\\textcolor{hloperator}{\\textbf{#1}}}"
|
|
1028
|
+
lines << "\\newcommand{\\hlproperty}[1]{\\textcolor{hlproperty}{\\textit{#1}}}"
|
|
1029
|
+
lines << "\\newcommand{\\hlqualifier}[1]{\\textcolor{hlqualifier}{\\texttt{#1}}}"
|
|
1030
|
+
lines << "\\newcommand{\\hlhashtag}[1]{\\textcolor{hlhashtag}{\\textbf{#1}}}"
|
|
1031
|
+
lines << "\\newcommand{\\hlreference}[1]{\\textcolor{hlreference}{#1}}"
|
|
1032
|
+
lines << "\\newcommand{\\hlcomment}[1]{\\textcolor{hlcomment}{\\textit{#1}}}"
|
|
1033
|
+
lines << "\\newcommand{\\hlquote}[1]{\\textcolor{hlquote}{#1}}"
|
|
1034
|
+
lines << "\\newcommand{\\hlstate}[1]{\\begin{tcolorbox}[colback=hlstate!10,colframe=hlstate,title=State]#1\\end{tcolorbox}}"
|
|
1035
|
+
lines << "\\newcommand{\\hltransition}[1]{\\begin{tcolorbox}[colback=hltransition!10,colframe=hltransition,title=Action]#1\\end{tcolorbox}}"
|
|
1036
|
+
lines << "\\newcommand{\\hlsubstitution}[1]{\\textcolor{hlqualifier}{#1}}"
|
|
1037
|
+
lines << "\\newcommand{\\checkbox}{\\faSquare[regular]}"
|
|
1038
|
+
lines << "\\newcommand{\\checkboxdone}{\\faCheckSquare}"
|
|
1039
|
+
lines << "\\newcommand{\\checkboxprogress}{\\faMinusSquare[regular]}"
|
|
1040
|
+
lines << ""
|
|
1041
|
+
lines << "% Document setup"
|
|
1042
|
+
title = @filename ? File.basename(@filename, '.*') : 'HyperList Export'
|
|
1043
|
+
lines << "\\title{#{escape_latex(title)}}"
|
|
1044
|
+
lines << "\\author{Generated by HyperList}"
|
|
1045
|
+
lines << "\\date{#{Time.now.strftime('%Y-%m-%d')}}"
|
|
1046
|
+
lines << ""
|
|
1047
|
+
lines << "\\pagestyle{fancy}"
|
|
1048
|
+
lines << "\\fancyhf{}"
|
|
1049
|
+
lines << "\\fancyhead[R]{\\thepage}"
|
|
1050
|
+
lines << "\\fancyhead[L]{#{escape_latex(title)}}"
|
|
1051
|
+
lines << ""
|
|
1052
|
+
lines << "\\begin{document}"
|
|
1053
|
+
lines << "\\maketitle"
|
|
1054
|
+
lines << "\\tableofcontents"
|
|
1055
|
+
lines << "\\newpage"
|
|
1056
|
+
lines << ""
|
|
1057
|
+
lines << "\\begin{itemize}[leftmargin=0pt,itemsep=2pt,parsep=0pt]"
|
|
1058
|
+
|
|
1059
|
+
# Process each item
|
|
1060
|
+
prev_level = 0
|
|
1061
|
+
@items.each do |item|
|
|
1062
|
+
text = item["text"]
|
|
1063
|
+
level = item["level"]
|
|
1064
|
+
|
|
1065
|
+
# Handle level changes
|
|
1066
|
+
if level > prev_level
|
|
1067
|
+
(level - prev_level).times do
|
|
1068
|
+
lines << "\\begin{itemize}"
|
|
1069
|
+
end
|
|
1070
|
+
elsif level < prev_level
|
|
1071
|
+
(prev_level - level).times do
|
|
1072
|
+
lines << "\\end{itemize}"
|
|
1073
|
+
end
|
|
1074
|
+
end
|
|
1075
|
+
|
|
1076
|
+
# Escape LaTeX special characters first
|
|
1077
|
+
text = escape_latex(text)
|
|
1078
|
+
|
|
1079
|
+
# Convert checkboxes
|
|
1080
|
+
text = text.gsub(/\[_\]/, '\\checkbox{}')
|
|
1081
|
+
text = text.gsub(/\[x\]|\[X\]/, '\\checkboxdone{}')
|
|
1082
|
+
text = text.gsub(/\[O\]/, '\\checkboxprogress{}')
|
|
1083
|
+
|
|
1084
|
+
# Convert HyperList markup (must be after escaping)
|
|
1085
|
+
text = text.gsub(/ \*([^*]+)\* /, ' \\textbf{\1} ')
|
|
1086
|
+
text = text.gsub(/ \/([^\/]+)\/ /, ' \\textit{\1} ')
|
|
1087
|
+
text = text.gsub(/ _([^_]+)_ /, ' \\underline{\1} ')
|
|
1088
|
+
|
|
1089
|
+
# Convert HyperList elements
|
|
1090
|
+
text = text.gsub(/\[([^\]]+)\]/, '\\hlqualifier{[\1]}')
|
|
1091
|
+
text = text.gsub(/\{([^\}]+)\}/, '\\hlsubstitution{\{\1\}}')
|
|
1092
|
+
text = text.gsub(/(#[a-zA-Z0-9.:\/\\_&?%=\-*]+)/, '\\hlhashtag{\1}')
|
|
1093
|
+
text = text.gsub(/(<{1,2}[a-zA-Z0-9.:\/\\_&?%=\-* ]+>{1,2})/, '\\hlreference{\1}')
|
|
1094
|
+
text = text.gsub(/(\([^)]*\))/, '\\hlcomment{\1}')
|
|
1095
|
+
text = text.gsub(/(\"[^\"]*\")/, '\\hlquote{\1}')
|
|
1096
|
+
|
|
1097
|
+
# Convert States and Transitions
|
|
1098
|
+
if text =~ /^S: (.+)/
|
|
1099
|
+
text = "\\hlstate{State: #{$1}}"
|
|
1100
|
+
elsif text =~ /^T: (.+)/
|
|
1101
|
+
text = "\\hltransition{Action: #{$1}}"
|
|
1102
|
+
elsif text =~ /^\| (.+)/
|
|
1103
|
+
text = "\\hlstate{State: #{$1}}"
|
|
1104
|
+
elsif text =~ /^\/ (.+)/
|
|
1105
|
+
text = "\\hltransition{Action: #{$1}}"
|
|
1106
|
+
end
|
|
1107
|
+
|
|
1108
|
+
# Convert Operators and Properties
|
|
1109
|
+
text = text.gsub(/([A-Z_\-() \/]{2,}): /, '\\hloperator{\1:} ')
|
|
1110
|
+
text = text.gsub(/([a-zA-Z0-9,._&?%=\-\/+<>#'()*:]{2,}): /, '\\hlproperty{\1:} ')
|
|
1111
|
+
|
|
1112
|
+
lines << "\\item #{text}"
|
|
1113
|
+
prev_level = level
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1116
|
+
# Close any remaining nested lists
|
|
1117
|
+
prev_level.times do
|
|
1118
|
+
lines << "\\end{itemize}"
|
|
1119
|
+
end
|
|
1120
|
+
|
|
1121
|
+
lines << "\\end{itemize}"
|
|
1122
|
+
lines << "\\end{document}"
|
|
1123
|
+
|
|
1124
|
+
lines.join("\n")
|
|
1125
|
+
end
|
|
1126
|
+
|
|
952
1127
|
def render
|
|
953
1128
|
render_main
|
|
954
1129
|
render_split_pane if @split_view
|
|
@@ -1674,6 +1849,10 @@ class HyperListApp
|
|
|
1674
1849
|
end
|
|
1675
1850
|
end
|
|
1676
1851
|
|
|
1852
|
+
# Ensure the moved item remains visible (find its index in main items array)
|
|
1853
|
+
main_real_idx = @items.index(item)
|
|
1854
|
+
force_item_visible(main_real_idx) if main_real_idx
|
|
1855
|
+
|
|
1677
1856
|
@modified = true
|
|
1678
1857
|
@message = "Indented in split pane"
|
|
1679
1858
|
end
|
|
@@ -1702,6 +1881,10 @@ class HyperListApp
|
|
|
1702
1881
|
end
|
|
1703
1882
|
end
|
|
1704
1883
|
|
|
1884
|
+
# Ensure the moved item remains visible (find its index in main items array)
|
|
1885
|
+
main_real_idx = @items.index(item)
|
|
1886
|
+
force_item_visible(main_real_idx) if main_real_idx
|
|
1887
|
+
|
|
1705
1888
|
@modified = true
|
|
1706
1889
|
@message = "Unindented in split pane"
|
|
1707
1890
|
end
|
|
@@ -2324,19 +2507,41 @@ class HyperListApp
|
|
|
2324
2507
|
|
|
2325
2508
|
def insert_line
|
|
2326
2509
|
@mode = :insert
|
|
2327
|
-
|
|
2510
|
+
|
|
2328
2511
|
input = @footer.ask("New item: ", "")
|
|
2329
|
-
|
|
2512
|
+
|
|
2330
2513
|
if input && !input.strip.empty?
|
|
2331
|
-
|
|
2514
|
+
# Collect all lines (first line + buffer)
|
|
2515
|
+
all_lines = [input]
|
|
2516
|
+
if @footer.multiline_buffer && !@footer.multiline_buffer.empty?
|
|
2517
|
+
all_lines += @footer.multiline_buffer.reject { |l| l.strip.empty? }
|
|
2518
|
+
@footer.multiline_buffer = []
|
|
2519
|
+
end
|
|
2520
|
+
|
|
2521
|
+
# Insert all lines as siblings at the same level
|
|
2522
|
+
all_lines.each do |line|
|
|
2523
|
+
insert_line_with_text(line.strip)
|
|
2524
|
+
@current -= 1 # Keep cursor at same position so next insert is sibling
|
|
2525
|
+
end
|
|
2526
|
+
@current += 1 # Move to last inserted item
|
|
2527
|
+
|
|
2332
2528
|
record_last_action(:insert_line, input)
|
|
2333
2529
|
end
|
|
2334
|
-
|
|
2530
|
+
|
|
2335
2531
|
@mode = :normal
|
|
2336
2532
|
@footer.clear # Clear footer immediately
|
|
2337
2533
|
@footer.refresh
|
|
2338
2534
|
end
|
|
2339
|
-
|
|
2535
|
+
|
|
2536
|
+
# Alias for 'o' key
|
|
2537
|
+
alias insert_line_below insert_line
|
|
2538
|
+
|
|
2539
|
+
# Method for 'O' key - insert line above
|
|
2540
|
+
def insert_line_above
|
|
2541
|
+
@current -= 1 if @current > 0
|
|
2542
|
+
insert_line
|
|
2543
|
+
end
|
|
2544
|
+
|
|
2340
2545
|
def insert_line_with_text(text)
|
|
2341
2546
|
return unless text && !text.strip.empty?
|
|
2342
2547
|
|
|
@@ -2363,14 +2568,27 @@ class HyperListApp
|
|
|
2363
2568
|
|
|
2364
2569
|
def insert_child
|
|
2365
2570
|
@mode = :insert
|
|
2366
|
-
|
|
2571
|
+
|
|
2367
2572
|
input = @footer.ask("New child item: ", "")
|
|
2368
|
-
|
|
2573
|
+
|
|
2369
2574
|
if input && !input.strip.empty?
|
|
2370
|
-
|
|
2575
|
+
# Collect all lines (first line + buffer)
|
|
2576
|
+
all_lines = [input]
|
|
2577
|
+
if @footer.multiline_buffer && !@footer.multiline_buffer.empty?
|
|
2578
|
+
all_lines += @footer.multiline_buffer.reject { |l| l.strip.empty? }
|
|
2579
|
+
@footer.multiline_buffer = []
|
|
2580
|
+
end
|
|
2581
|
+
|
|
2582
|
+
# Insert all lines as siblings at the same level
|
|
2583
|
+
all_lines.each do |line|
|
|
2584
|
+
insert_child_with_text(line.strip)
|
|
2585
|
+
@current -= 1 # Keep cursor at same position so next insert is sibling
|
|
2586
|
+
end
|
|
2587
|
+
@current += 1 # Move to last inserted item
|
|
2588
|
+
|
|
2371
2589
|
record_last_action(:insert_child, input)
|
|
2372
2590
|
end
|
|
2373
|
-
|
|
2591
|
+
|
|
2374
2592
|
@mode = :normal
|
|
2375
2593
|
@footer.clear # Clear footer immediately
|
|
2376
2594
|
@footer.refresh
|
|
@@ -2378,14 +2596,43 @@ class HyperListApp
|
|
|
2378
2596
|
|
|
2379
2597
|
def insert_outdented
|
|
2380
2598
|
@mode = :insert
|
|
2381
|
-
|
|
2599
|
+
|
|
2382
2600
|
input = @footer.ask("New outdented item: ", "")
|
|
2383
|
-
|
|
2601
|
+
|
|
2384
2602
|
if input && !input.strip.empty?
|
|
2385
|
-
|
|
2603
|
+
# Collect all lines (first line + buffer)
|
|
2604
|
+
all_lines = [input]
|
|
2605
|
+
if @footer.multiline_buffer && !@footer.multiline_buffer.empty?
|
|
2606
|
+
all_lines += @footer.multiline_buffer.reject { |l| l.strip.empty? }
|
|
2607
|
+
@footer.multiline_buffer = []
|
|
2608
|
+
end
|
|
2609
|
+
|
|
2610
|
+
# Calculate target level BEFORE inserting anything
|
|
2611
|
+
visible = get_visible_items
|
|
2612
|
+
if @current < visible.length
|
|
2613
|
+
current_level = visible[@current]["level"]
|
|
2614
|
+
target_level = [current_level - 1, 0].max # Outdented level
|
|
2615
|
+
else
|
|
2616
|
+
target_level = 0
|
|
2617
|
+
end
|
|
2618
|
+
|
|
2619
|
+
# Insert first line normally
|
|
2620
|
+
insert_outdented_with_text(all_lines[0].strip)
|
|
2621
|
+
|
|
2622
|
+
# Insert remaining lines at same level as the first
|
|
2623
|
+
if all_lines.length > 1
|
|
2624
|
+
all_lines[1..-1].each do |line|
|
|
2625
|
+
visible = get_visible_items
|
|
2626
|
+
real_idx = get_real_index(visible[@current])
|
|
2627
|
+
@items.insert(real_idx + 1, {"text" => line.strip, "level" => target_level, "fold" => false})
|
|
2628
|
+
@current += 1
|
|
2629
|
+
end
|
|
2630
|
+
@modified = true
|
|
2631
|
+
end
|
|
2632
|
+
|
|
2386
2633
|
record_last_action(:insert_outdented, input)
|
|
2387
2634
|
end
|
|
2388
|
-
|
|
2635
|
+
|
|
2389
2636
|
@mode = :normal
|
|
2390
2637
|
@footer.clear # Clear footer immediately
|
|
2391
2638
|
@footer.refresh
|
|
@@ -2570,14 +2817,14 @@ class HyperListApp
|
|
|
2570
2817
|
def yank_line(with_children = false)
|
|
2571
2818
|
visible = get_visible_items
|
|
2572
2819
|
return if @current >= visible.length
|
|
2573
|
-
|
|
2820
|
+
|
|
2574
2821
|
item = visible[@current]
|
|
2575
2822
|
real_idx = get_real_index(item)
|
|
2576
|
-
|
|
2823
|
+
|
|
2577
2824
|
@clipboard = []
|
|
2578
2825
|
@clipboard << item.dup
|
|
2579
2826
|
@clipboard_is_tree = with_children # Remember if this is a tree copy (Y) or single (y)
|
|
2580
|
-
|
|
2827
|
+
|
|
2581
2828
|
# Copy children if requested
|
|
2582
2829
|
if with_children
|
|
2583
2830
|
level = item["level"]
|
|
@@ -2589,7 +2836,15 @@ class HyperListApp
|
|
|
2589
2836
|
end
|
|
2590
2837
|
end
|
|
2591
2838
|
end
|
|
2592
|
-
|
|
2839
|
+
|
|
2840
|
+
# Copy to system clipboard for middle-click paste
|
|
2841
|
+
begin
|
|
2842
|
+
text_to_copy = @clipboard.map { |it| " " * it["level"] + it["text"] }.join("\n")
|
|
2843
|
+
Clipboard.copy(text_to_copy)
|
|
2844
|
+
rescue => e
|
|
2845
|
+
# Silently fail if clipboard gem not available
|
|
2846
|
+
end
|
|
2847
|
+
|
|
2593
2848
|
@message = "Yanked #{@clipboard.length} item(s)"
|
|
2594
2849
|
record_last_action(:yank_line, with_children)
|
|
2595
2850
|
end
|
|
@@ -2918,6 +3173,9 @@ class HyperListApp
|
|
|
2918
3173
|
end
|
|
2919
3174
|
end
|
|
2920
3175
|
|
|
3176
|
+
# Ensure the moved item remains visible
|
|
3177
|
+
force_item_visible(real_idx)
|
|
3178
|
+
|
|
2921
3179
|
@modified = true
|
|
2922
3180
|
record_last_action(:indent_right, with_children)
|
|
2923
3181
|
end
|
|
@@ -2946,6 +3204,9 @@ class HyperListApp
|
|
|
2946
3204
|
end
|
|
2947
3205
|
end
|
|
2948
3206
|
|
|
3207
|
+
# Ensure the moved item remains visible
|
|
3208
|
+
force_item_visible(real_idx)
|
|
3209
|
+
|
|
2949
3210
|
@modified = true
|
|
2950
3211
|
record_last_action(:indent_left, with_children)
|
|
2951
3212
|
end
|
|
@@ -3202,7 +3463,7 @@ class HyperListApp
|
|
|
3202
3463
|
help_lines << "#{"FILE OPERATIONS".fg("14")}"
|
|
3203
3464
|
help_lines << help_line("#{":w".fg("10")}", "Save", "#{":q".fg("10")}", "Quit")
|
|
3204
3465
|
help_lines << help_line("#{":wq".fg("10")}", "Save and quit", "#{":e file".fg("10")}", "Open file")
|
|
3205
|
-
help_lines << help_line("#{":recent".fg("10")}", "Recent files", "#{":export :ex".fg("10")}", "Export md|html|txt")
|
|
3466
|
+
help_lines << help_line("#{":recent".fg("10")}", "Recent files", "#{":export :ex".fg("10")}", "Export md|html|txt|pdf")
|
|
3206
3467
|
help_lines << help_line("#{":graph :g".fg("10")}", "Export to PNG graph", "#{":vsplit :vs".fg("10")}", "Split view")
|
|
3207
3468
|
help_lines << help_line("#{":as on".fg("10")}", "Enable autosave", "#{":as off".fg("10")}", "Disable autosave")
|
|
3208
3469
|
help_lines << help_line("#{":as N".fg("10")}", "Set interval (secs)", "#{":as".fg("10")}", "Show autosave status")
|
|
@@ -3886,12 +4147,12 @@ class HyperListApp
|
|
|
3886
4147
|
@current = 0
|
|
3887
4148
|
@offset = 0
|
|
3888
4149
|
@modified = false
|
|
3889
|
-
when /^(export|ex)\s+(md|markdown|html|txt|text)\s*(.*)$/
|
|
4150
|
+
when /^(export|ex)\s+(md|markdown|html|txt|text|latex|tex|pdf)\s*(.*)$/
|
|
3890
4151
|
format = $2
|
|
3891
4152
|
export_file = $3.empty? ? nil : $3
|
|
3892
4153
|
export_to(format, export_file)
|
|
3893
4154
|
when /^(export|ex)$/
|
|
3894
|
-
@message = "Usage: :export [md|html|txt] [filename] (or :ex)"
|
|
4155
|
+
@message = "Usage: :export [md|html|txt|latex|pdf] [filename] (or :ex)"
|
|
3895
4156
|
when "recent", "r"
|
|
3896
4157
|
show_recent_files
|
|
3897
4158
|
when "autosave on", "as on"
|
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.8.1"
|
|
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.8.1
|
|
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-13 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_resize.hl
|
|
83
82
|
homepage: https://github.com/isene/HyperList
|
|
84
83
|
licenses:
|
|
85
84
|
- Unlicense
|
data/test_resize.hl
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
Test HyperList for Terminal Resize
|
|
2
|
-
This is a test file
|
|
3
|
-
To verify terminal resize works
|
|
4
|
-
When you resize the terminal
|
|
5
|
-
The display should update correctly
|
|
6
|
-
Items to check:
|
|
7
|
-
Main content area adjusts
|
|
8
|
-
Footer stays at bottom
|
|
9
|
-
Split view (if active) rebalances
|
|
10
|
-
No visual corruption
|
|
11
|
-
Test scenarios:
|
|
12
|
-
Make terminal wider
|
|
13
|
-
Make terminal narrower
|
|
14
|
-
Make terminal taller
|
|
15
|
-
Make terminal shorter
|
|
16
|
-
Quick multiple resizes
|