rcurses 2.0 → 2.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rcurses.rb CHANGED
@@ -5,708 +5,14 @@
5
5
  # Web_site: http://isene.com/
6
6
  # Github: https://github.com/isene/rcurses
7
7
  # License: Public domain
8
- # Version: 2.0: Complete code refactoring
8
+ # Version: 2.1: New gem structure
9
9
 
10
10
  require 'io/console' # Basic gem for rcurses
11
11
  require 'io/wait' # stdin handling
12
- begin
13
- require 'clipboard' # Ensure the 'clipboard' gem is installed
14
- rescue LoadError
15
- # Define a fallback Clipboard module
16
- module Clipboard
17
- def self.copy(_text)
18
- # Clipboard functionality is not available
19
- puts "Clipboard functionality is not available. Please install the 'clipboard' gem to enable it."
20
- end
21
- end
22
- end
23
12
 
24
- class String
25
- # Add coloring to strings (with escaping for Readline)
26
- def fg(fg) ; color(self, "\e[38;5;#{fg}m", "\e[0m") ; end
27
- def bg(bg) ; color(self, "\e[48;5;#{bg}m", "\e[0m") ; end
28
- def fb(fg, bg); color(self, "\e[38;5;#{fg};48;5;#{bg}m"); end
29
- def b ; color(self, "\e[1m", "\e[22m") ; end
30
- def i ; color(self, "\e[3m", "\e[23m") ; end
31
- def u ; color(self, "\e[4m", "\e[24m") ; end
32
- def l ; color(self, "\e[5m", "\e[25m") ; end
33
- def r ; color(self, "\e[7m", "\e[27m") ; end
34
- # Internal function
35
- def color(text, sp, ep = "\e[0m"); "#{sp}#{text}#{ep}"; end
36
-
37
- # Use format "TEST".c("204,45,bui") to print "TEST" in bold, underline italic, fg=204 and bg=45
38
- def c(code)
39
- prop = "\e["
40
- prop += "38;5;#{code.match(/^\d+/).to_s};" if code.match(/^\d+/)
41
- prop += "48;5;#{code.match(/(?<=,)\d+/).to_s};" if code.match(/(?<=,)\d+/)
42
- prop += "1;" if code.include?('b')
43
- prop += "3;" if code.include?('i')
44
- prop += "4;" if code.include?('u')
45
- prop += "5;" if code.include?('l')
46
- prop += "7;" if code.include?('r')
47
- prop.chop! if prop[-1] == ";"
48
- prop += "m"
49
- prop += self
50
- prop += "\e[0m"
51
- prop
52
- end
53
-
54
- def pure
55
- self.gsub(/\e\[\d+(?:;\d+)*m/, '')
56
- end
57
- end
58
-
59
- module Rcurses
60
- module Cursor
61
- # Terminal cursor movement ANSI codes (inspired by https://github.com/piotrmurach/tty-cursor)
62
- module_function
63
- ESC = "\e".freeze
64
- CSI = "\e[".freeze
65
- def save # Save current position
66
- print(Gem.win_platform? ? CSI + 's' : ESC + '7')
67
- end
68
- def restore # Restore cursor position
69
- print(Gem.win_platform? ? CSI + 'u' : ESC + '8')
70
- end
71
- def pos # Query cursor current position
72
- res = ''
73
- $stdin.raw do |stdin|
74
- $stdout << CSI + '6n' # The actual ANSI get-position
75
- $stdout.flush
76
- while (c = stdin.getc) != 'R'
77
- res << c if c
78
- end
79
- end
80
- m = res.match(/(?<row>\d+);(?<col>\d+)/)
81
- return m[:row].to_i, m[:col].to_i
82
- end
83
- def rowget
84
- row, _col = pos
85
- row
86
- end
87
- def colget
88
- _row, col = pos
89
- col
90
- end
91
- def up(n = 1) # Move cursor up by n
92
- print(CSI + "#{(n || 1)}A")
93
- end
94
- def down(n = 1) # Move the cursor down by n
95
- print(CSI + "#{(n || 1)}B")
96
- end
97
- def left(n = 1) # Move the cursor backward by n
98
- print(CSI + "#{n || 1}D")
99
- end
100
- def right(n = 1) # Move the cursor forward by n
101
- print(CSI + "#{n || 1}C")
102
- end
103
- def col(n = 1) # Cursor moves to nth position horizontally in the current line
104
- print(CSI + "#{n || 1}G")
105
- end
106
- def row(n = 1) # Cursor moves to the nth position vertically in the current column
107
- print(CSI + "#{n || 1}d")
108
- end
109
- def next_line # Move cursor down to beginning of next line
110
- print(CSI + 'E' + CSI + "1G")
111
- end
112
- def prev_line # Move cursor up to beginning of previous line
113
- print(CSI + 'A' + CSI + "1G")
114
- end
115
- def clear_char(n = 1) # Erase n characters from the current cursor position
116
- print(CSI + "#{n}X")
117
- end
118
- def clear_line # Erase the entire current line and return to beginning of the line
119
- print(CSI + '2K' + CSI + "1G")
120
- end
121
- def clear_line_before # Erase from the beginning of the line up to and including the current cursor position.
122
- print(CSI + '1K')
123
- end
124
- def clear_line_after # Erase from the current position (inclusive) to the end of the line
125
- print(CSI + '0K')
126
- end
127
- def clear_screen_down # Clear screen down from current row
128
- print(CSI + 'J')
129
- end
130
- def scroll_up # Scroll display up one line
131
- print(ESC + 'M')
132
- end
133
- def scroll_down # Scroll display down one line
134
- print(ESC + 'D')
135
- end
136
- end
137
-
138
- module Rinput
139
- def getchr
140
- # Function to process key presses
141
- c = $stdin.getch
142
- case c
143
- when "\e" # ANSI escape sequences
144
- return "ESC" if !$stdin.ready?
145
- second_char = $stdin.getc
146
- case second_char
147
- when '[' # CSI
148
- third_char = $stdin.getc
149
- case third_char
150
- when 'A' then chr = "UP"
151
- when 'B' then chr = "DOWN"
152
- when 'C' then chr = "RIGHT"
153
- when 'D' then chr = "LEFT"
154
- when 'Z' then chr = "S-TAB"
155
- when '2'
156
- fourth_char = $stdin.getc
157
- chr = fourth_char == '~' ? "INS" : ""
158
- when '3'
159
- fourth_char = $stdin.getc
160
- chr = fourth_char == '~' ? "DEL" : ""
161
- when '5'
162
- fourth_char = $stdin.getc
163
- chr = fourth_char == '~' ? "PgUP" : ""
164
- when '6'
165
- fourth_char = $stdin.getc
166
- chr = fourth_char == '~' ? "PgDOWN" : ""
167
- when '1', '7'
168
- fourth_char = $stdin.getc
169
- chr = fourth_char == '~' ? "HOME" : ""
170
- when '4', '8'
171
- fourth_char = $stdin.getc
172
- chr = fourth_char == '~' ? "END" : ""
173
- else chr = ""
174
- end
175
- when 'O' # Function keys
176
- third_char = $stdin.getc
177
- case third_char
178
- when 'a' then chr = "C-UP"
179
- when 'b' then chr = "C-DOWN"
180
- when 'c' then chr = "C-RIGHT"
181
- when 'd' then chr = "C-LEFT"
182
- else chr = ""
183
- end
184
- else
185
- chr = ""
186
- end
187
- when "\r" then chr = "ENTER"
188
- when "\t" then chr = "TAB"
189
- when "\u007F", "\b" then chr = "BACK"
190
- when "\u0001" then chr = "C-A"
191
- when "\u0002" then chr = "C-B"
192
- when "\u0003" then chr = "C-C"
193
- when "\u0004" then chr = "C-D"
194
- when "\u0005" then chr = "C-E"
195
- when "\u0006" then chr = "C-F"
196
- when "\u0007" then chr = "C-G"
197
- when "\u000B" then chr = "C-K"
198
- when "\u000C" then chr = "C-L"
199
- when "\u000D" then chr = "C-M"
200
- when "\u000E" then chr = "C-N"
201
- when "\u000F" then chr = "C-O"
202
- when "\u0010" then chr = "C-P"
203
- when "\u0011" then chr = "C-Q"
204
- when "\u0012" then chr = "C-R"
205
- when "\u0014" then chr = "C-T"
206
- when "\u0015" then chr = "C-U"
207
- when "\u0016" then chr = "C-V"
208
- when "\u0018" then chr = "C-X"
209
- when "\u0019" then chr = "C-Y"
210
- when "\u001A" then chr = "C-Z"
211
- when "\u0017" then chr = "WBACK"
212
- when "\u0013" then chr = "C-S"
213
- when /[[:print:]]/ then chr = c
214
- else chr = ""
215
- end
216
- chr
217
- end
218
- end
219
-
220
- class Pane
221
- include Rcurses::Cursor
222
- include Rcurses::Rinput
223
- attr_accessor :startx, :starty, :width, :height, :fg, :bg
224
- attr_accessor :x, :y, :w, :h
225
- attr_accessor :border, :scroll, :text, :ix, :align, :prompt
226
-
227
- def initialize(startx = 1, starty = 1, width = 1, height = 1, fg = nil, bg = nil)
228
- # Using Procs or Lambdas instead of eval
229
- @startx = startx.is_a?(Proc) ? startx : -> { startx }
230
- @starty = starty.is_a?(Proc) ? starty : -> { starty }
231
- @width = width.is_a?(Proc) ? width : -> { width }
232
- @height = height.is_a?(Proc) ? height : -> { height }
233
- @fg, @bg = fg, bg
234
- @text = "" # Initialize text variable
235
- @align = "l" # Default alignment
236
- @scroll = true # Initialize scroll indicators to true
237
- @prompt = "" # Initialize prompt for editline
238
- @ix = 0 # Text index (starting text line in pane)
239
- @max_h, @max_w = IO.console.winsize
240
- end
241
-
242
- def move(x, y)
243
- @startx = -> { @x + x }
244
- @starty = -> { @y + y }
245
- refresh
246
- end
247
-
248
- def refresh(cont = @text)
249
- @max_h, @max_w = IO.console.winsize
250
-
251
- # Define the core of the ANSI escape sequence handling
252
- def split_line_with_ansi(line, w)
253
- # Define opening and closing sequences
254
- open_sequences = {
255
- "\e[1m" => "\e[22m",
256
- "\e[3m" => "\e[23m",
257
- "\e[4m" => "\e[24m",
258
- "\e[5m" => "\e[25m",
259
- "\e[7m" => "\e[27m" }
260
- # All known closing sequences
261
- close_sequences = open_sequences.values + ["\e[0m"]
262
- # Regex to match ANSI escape sequences
263
- ansi_regex = /\e\[[0-9;]*m/
264
- result = []
265
- # Tokenize the line into ANSI sequences and plain text
266
- tokens = line.scan(/(\e\[[0-9;]*m|[^\e]+)/).flatten.compact
267
- current_line = ''
268
- current_line_length = 0
269
- active_sequences = []
270
- tokens.each do |token|
271
- if token.match?(ansi_regex)
272
- # It's an ANSI sequence
273
- current_line << token
274
- if close_sequences.include?(token)
275
- # It's a closing sequence
276
- if token == "\e[0m"
277
- # Reset all sequences
278
- active_sequences.clear
279
- else
280
- # Remove the corresponding opening sequence
281
- corresponding_open = open_sequences.key(token)
282
- active_sequences.delete(corresponding_open)
283
- end
284
- else
285
- # It's an opening sequence (or any other ANSI sequence)
286
- active_sequences << token
287
- end
288
- else
289
- # It's plain text, split into words and spaces
290
- words = token.scan(/\S+\s*/)
291
- words.each do |word|
292
- word_length = word.gsub(ansi_regex, '').length
293
- if current_line_length + word_length <= w
294
- # Append word to current line
295
- current_line << word
296
- current_line_length += word_length
297
- else
298
- # Word doesn't fit in the current line
299
- if current_line_length > 0
300
- # Finish the current line and start a new one
301
- result << current_line
302
- # Start new line with active ANSI sequences
303
- current_line = active_sequences.join
304
- current_line_length = 0
305
- end
306
- # Handle long words that might need splitting
307
- while word_length > w
308
- # Split the word
309
- part = word[0, w]
310
- current_line << part
311
- result << current_line
312
- # Update word and lengths
313
- word = word[w..-1]
314
- word_length = word.gsub(ansi_regex, '').length
315
- # Start new line
316
- current_line = active_sequences.join
317
- current_line_length = 0
318
- end
319
- # Append any remaining part of the word
320
- if word_length > 0
321
- current_line << word
322
- current_line_length += word_length
323
- end
324
- end
325
- end
326
- end
327
- end
328
- # Append any remaining text in the current line
329
- result << current_line unless current_line.empty?
330
- result
331
- end
332
-
333
- # Define the main textformat function
334
- def textformat(cont)
335
- # Split the content by '\n'
336
- lines = cont.split("\n")
337
- result = []
338
- lines.each do |line|
339
- split_lines = split_line_with_ansi(line, @w)
340
- result.concat(split_lines)
341
- end
342
- result
343
- end
344
-
345
- # Start the actual refresh
346
- o_row, o_col = pos
347
- @x = @startx.call
348
- @y = @starty.call
349
- @w = @width.call
350
- @h = @height.call
351
-
352
- # Adjust pane dimensions and positions
353
- if @border # Keep panes inside screen
354
- @w = @max_w - 2 if @w > @max_w - 2
355
- @h = @max_h - 2 if @h > @max_h - 2
356
- @x = 2 if @x < 2; @x = @max_w - @w if @x + @w > @max_w
357
- @y = 2 if @y < 2; @y = @max_h - @h if @y + @h > @max_h
358
- else
359
- @w = @max_w if @w > @max_w
360
- @h = @max_h if @h > @max_h
361
- @x = 1 if @x < 1; @x = @max_w - @w + 1 if @x + @w > @max_w + 1
362
- @y = 1 if @y < 1; @y = @max_h - @h + 1 if @y + @h > @max_h + 1
363
- end
364
-
365
- col(@x); row(@y) # Cursor to start of pane
366
- fmt = [@fg, @bg].compact.join(',') # Format for printing in pane (fg,bg)
367
- @txt = cont.split("\n") # Split content into array
368
- @txt = textformat(cont) if @txt.any? { |line| line.pure.length >= @w } # Reformat lines if necessary
369
- @ix = @txt.length - 1 if @ix > @txt.length - 1; @ix = 0 if @ix < 0 # Ensure no out-of-bounds
370
-
371
- @h.times do |i| # Print pane content
372
- l = @ix + i # The current line to be printed
373
- if @txt[l].to_s != "" # Print the text line
374
- # Get padding width and half width
375
- pl = @w - @txt[l].pure.length
376
- pl = 0 if pl < 0
377
- hl = pl / 2
378
- case @align
379
- when "l"
380
- print @txt[l].c(fmt) + " ".c(fmt) * pl
381
- when "r"
382
- print " ".c(fmt) * pl + @txt[l].c(fmt)
383
- when "c"
384
- print " ".c(fmt) * hl + @txt[l].c(fmt) + " ".c(fmt) * (pl - hl)
385
- end
386
- else
387
- print " ".c(fmt) * @w
388
- end
389
- col(@x) # Cursor to start of pane
390
- row(@y + i + 1)
391
- end
392
-
393
- if @ix > 0 and @scroll # Print "more" marker at top
394
- col(@x + @w - 1); row(@y)
395
- print "▲".c(fmt)
396
- end
397
-
398
- if @txt.length - @ix > @h and @scroll # Print bottom "more" marker
399
- col(@x + @w - 1); row(@y + @h - 1)
400
- print "▼".c(fmt)
401
- end
402
-
403
- if @border # Print border if @border is set to true
404
- row(@y - 1); col(@x - 1)
405
- print ("┌" + "─" * @w + "┐").c(fmt)
406
- @h.times do |i|
407
- row(@y + i); col(@x - 1)
408
- print "│".c(fmt)
409
- col(@x + @w)
410
- print "│".c(fmt)
411
- end
412
- row(@y + @h); col(@x - 1)
413
- print ("└" + "─" * @w + "┘").c(fmt)
414
- end
415
-
416
- row(o_row)
417
- col(o_col)
418
- @txt
419
- end
420
-
421
- def textformat(cont)
422
- # Split the content by '\n'
423
- lines = cont.split("\n")
424
- result = []
425
- lines.each do |line|
426
- split_lines = split_line_with_ansi(line, @w)
427
- result.concat(split_lines)
428
- end
429
- result
430
- end
431
-
432
- def right
433
- if @pos < @txt[@ix + @line].length
434
- @pos += 1
435
- if @pos == @w
436
- @pos = 0
437
- if @line == @h - 1
438
- @ix += 1
439
- else
440
- @line += 1
441
- end
442
- end
443
- else
444
- if @line == @h - 1
445
- @ix += 1 unless @ix >= @txt.length - @h
446
- @pos = 0
447
- elsif @line + @ix + 1 < @txt.length
448
- @line += 1
449
- @pos = 0
450
- end
451
- end
452
- end
453
- def left
454
- if @pos == 0
455
- if @line == 0
456
- unless @ix == 0
457
- @ix -= 1
458
- @pos = @txt[@ix + @line].length
459
- end
460
- else
461
- @line -= 1
462
- @pos = @txt[@ix + @line].length
463
- end
464
- else
465
- @pos -= 1
466
- end
467
- end
468
- def up
469
- if @line == 0
470
- @ix -= 1 unless @ix == 0
471
- else
472
- @line -= 1
473
- end
474
- begin
475
- @pos = [@pos, @txt[@ix + @line].length].min
476
- rescue
477
- end
478
- end
479
- def down
480
- if @line == @h - 1
481
- @ix += 1 unless @ix + @line >= @txt.length - 1
482
- elsif @line + @ix + 1 < @txt.length
483
- @line += 1
484
- end
485
- begin
486
- @pos = [@pos, @txt[@ix + @line].length].min
487
- rescue
488
- end
489
- end
490
-
491
- def parse(cont)
492
- cont.gsub!(/\*(.+?)\*/, '\1'.b)
493
- cont.gsub!(/\/(.+?)\//, '\1'.i)
494
- cont.gsub!(/_(.+?)_/, '\1'.u)
495
- cont.gsub!(/#(.+?)#/, '\1'.r)
496
- cont.gsub!(/<([^|]+)\|([^>]+)>/) do
497
- text = $2; codes = $1
498
- text.c(codes)
499
- end
500
- cont
501
- end
502
-
503
- def edit
504
- begin
505
- # Switch to raw mode without echoing input
506
- STDIN.raw!
507
- # Prepare content for editing, replacing newlines with a placeholder
508
- content = @text.pure.gsub("\n", "¬\n")
509
- # Initialize cursor position and indices
510
- @ix = 0 # Starting index of text lines displayed in the pane
511
- @line = 0 # Current line number relative to the pane's visible area
512
- @pos = 0 # Position within the current line (character index)
513
- @txt = refresh(content)
514
- input_char = ''
515
-
516
- while input_char != 'ESC' # Continue until ESC is pressed
517
- row(@y + @line) # Move cursor to the correct row
518
- col(@x + @pos) # Move cursor to the correct column
519
-
520
- input_char = getchr # Read user input
521
- case input_char
522
- when 'C-L' # Left justify
523
- @align = 'l'
524
- when 'C-R' # Right justify
525
- @align = 'r'
526
- when 'C-C' # Center justify
527
- @align = 'c'
528
- when 'C-Y' # Copy pane content to clipboard
529
- Clipboard.copy(@text.pure)
530
- when 'C-S' # Save edited text back to @text and exit
531
- content = content.gsub('¬', "\n")
532
- content = parse(content)
533
- @text = content
534
- input_char = 'ESC'
535
- when 'DEL' # Delete character at current position
536
- posx = calculate_posx
537
- content.slice!(posx)
538
- when 'BACK' # Backspace (delete character before current position)
539
- if @pos > 0
540
- left
541
- posx = calculate_posx
542
- content.slice!(posx)
543
- end
544
- when 'WBACK' # Word backspace
545
- while @pos > 0 && content[calculate_posx - 1] != ' '
546
- left
547
- posx = calculate_posx
548
- content.slice!(posx)
549
- end
550
- when 'C-K' # Kill line (delete from cursor to end of line)
551
- line_start_pos = calculate_line_start_pos
552
- line_length = @txt[@ix + @line]&.length || 0
553
- content.slice!(line_start_pos + @pos, line_length - @pos)
554
- when 'UP' # Move cursor up one line
555
- up
556
- when 'DOWN' # Move cursor down one line
557
- down
558
- when 'RIGHT' # Move cursor right one character
559
- right
560
- when 'LEFT' # Move cursor left one character
561
- left
562
- when 'HOME' # Move to start of line
563
- @pos = 0
564
- when 'END' # Move to end of line
565
- current_line_length = @txt[@ix + @line]&.length || 0
566
- @pos = current_line_length
567
- when 'C-HOME' # Move to start of pane
568
- @ix = 0
569
- @line = 0
570
- @pos = 0
571
- when 'C-END' # Move to end of pane
572
- total_lines = @txt.length
573
- @ix = [total_lines - @h, 0].max
574
- @line = [@h - 1, total_lines - @ix - 1].min
575
- current_line_length = @txt[@ix + @line]&.length || 0
576
- @pos = current_line_length
577
- when 'ENTER' # Insert newline at current position
578
- posx = calculate_posx
579
- content.insert(posx, "¬\n")
580
- right
581
- when /^.$/ # Insert character at current position
582
- posx = calculate_posx
583
- content.insert(posx, input_char)
584
- right
585
- else
586
- # Handle unrecognized input if necessary
587
- end
588
-
589
- # Handle pasted input (additional characters in the buffer)
590
- while IO.select([$stdin], nil, nil, 0)
591
- input_char = $stdin.read_nonblock(1) rescue nil
592
- break unless input_char
593
- posx = calculate_posx
594
- content.insert(posx, input_char)
595
- right
596
- end
597
-
598
- @txt = refresh(content) # Refresh the pane with the current content
599
- end
600
- ensure
601
- # Restore terminal mode
602
- STDIN.cooked!
603
- end
604
- end
605
-
606
- def editline
607
- begin
608
- # Switch to raw mode without echo
609
- STDIN.raw!
610
-
611
- # Initialize position and dimensions
612
- @x = @startx.call
613
- @y = @starty.call
614
- @w = @width.call
615
- @h = @height.call
616
- # Ensure pane is within screen bounds
617
- @x = [[@x, 1].max, @max_w - @w + 1].min
618
- @y = [[@y, 1].max, @max_h - @h + 1].min
619
-
620
- @scroll = false
621
- row(@y)
622
-
623
- fmt = [@fg, @bg].compact.join(',')
624
- col(@x)
625
- print @prompt.c(fmt) # Print prompt at the pane's starting position
626
-
627
- prompt_len = @prompt.pure.length
628
- content_len = @w - prompt_len
629
- cont = @text.pure.slice(0, content_len)
630
- @pos = cont.length # Set initial cursor position at the end of content
631
- chr = ''
632
-
633
- while chr != 'ESC' # Continue until ESC is pressed
634
- col(@x + prompt_len) # Set cursor at start of content
635
- cont = cont.slice(0, content_len) # Trim content to max length
636
- print cont.ljust(content_len).c(fmt) # Print content, left-justified
637
- col(@x + prompt_len + @pos) # Set cursor to current position
638
-
639
- chr = getchr # Read user input
640
- case chr
641
- when 'LEFT'
642
- @pos -= 1 if @pos > 0
643
- when 'RIGHT'
644
- @pos += 1 if @pos < cont.length
645
- when 'HOME'
646
- @pos = 0
647
- when 'END'
648
- @pos = cont.length
649
- when 'DEL'
650
- cont[@pos] = '' if @pos < cont.length
651
- when 'BACK'
652
- if @pos > 0
653
- @pos -= 1
654
- cont[@pos] = ''
655
- end
656
- when 'WBACK'
657
- while @pos > 0 && cont[@pos - 1] != ' '
658
- @pos -= 1
659
- cont[@pos] = ''
660
- end
661
- when 'C-K'
662
- cont = ''
663
- @pos = 0
664
- when 'ENTER'
665
- @text = parse(cont)
666
- chr = 'ESC'
667
- when /^.$/
668
- if @pos < content_len
669
- cont.insert(@pos, chr)
670
- @pos += 1
671
- end
672
- end
673
-
674
- # Handle pasted input
675
- while IO.select([$stdin], nil, nil, 0)
676
- chr = $stdin.read_nonblock(1) rescue nil
677
- break unless chr
678
- if @pos < content_len
679
- cont.insert(@pos, chr)
680
- @pos += 1
681
- end
682
- end
683
- end
684
- ensure
685
- # Restore terminal mode
686
- STDIN.cooked!
687
- end
688
- end
689
-
690
- private
691
-
692
- # Calculates the position in the content string corresponding to the current cursor position
693
- def calculate_posx
694
- total_length = 0
695
- (@ix + @line).times do |i|
696
- total_length += @txt[i].pure.length + 1 # +1 for the newline character
697
- end
698
- total_length += @pos
699
- total_length
700
- end
701
- # Calculates the starting position of the current line in the content string
702
- def calculate_line_start_pos
703
- total_length = 0
704
- (@ix + @line).times do |i|
705
- total_length += @txt[i].pure.length + 1 # +1 for the newline character
706
- end
707
- total_length
708
- end
709
- end
710
- end
13
+ require_relative 'string_extensions'
14
+ require_relative 'rcurses/cursor'
15
+ require_relative 'rcurses/input'
16
+ require_relative 'rcurses/pane'
711
17
 
712
18
  # vim: set sw=2 sts=2 et filetype=ruby fdm=syntax fdn=2 fcs=fold\:\ :