rcurses 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +197 -0
  3. data/extconf.rb +33 -0
  4. data/lib/rcurses.rb +528 -0
  5. metadata +51 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e0a752aa8312d1c3b9ec5683f490da7666ca4e82599d7157105fcdfcfa7ce526
4
+ data.tar.gz: a86545340dd98c390fca814a0e5d5521fc3def15ec56ba9c13d6ab12766109ee
5
+ SHA512:
6
+ metadata.gz: '038fb306d29aab4f30fa355a637cbd2338970458432fddca29f2e92faca3404a670b4641220e72cffd7f821bf6514c3f0838d7a764a6899eb00b8d84cd502120'
7
+ data.tar.gz: 51e91e08b86d542d9b5d15050218decc2f0ed0debbe1d9c9eaacc2033b2b9c1cf0dd9ec39f27003670298590639bc5c6b66a730057e1f0af491a9858ab2f2e25
data/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # rcurses - An alternative curses library written in pure Ruby
2
+
3
+ ![Ruby](https://img.shields.io/badge/language-Ruby-red) ![Unlicense](https://img.shields.io/badge/license-Unlicense-green) ![Stay Amazing](https://img.shields.io/badge/Stay-Amazing-important)
4
+
5
+ <img src="img/rcurses-logo.png" width="150" height="150">
6
+
7
+
8
+ # Why?
9
+ Having struggled with the venerable curses library and the ruby interface to it for many years, I finally got around to write an alternative - in pure Ruby.
10
+
11
+ # Design principles
12
+ Simple. One file. No external requirements.
13
+
14
+ # Installation
15
+ Clone this repo and drop `lib/rcurses.rb` into `/usr/lib/ruby/vendor_ruby/`
16
+
17
+ Or simply `gem install rcurses` (when I get around to create the gem and this comment is removed).
18
+
19
+ To use this library do:
20
+ ```
21
+ require 'rcurses'
22
+ ```
23
+
24
+ # Features
25
+ * Create panes (with the colors and(or border), manipulate the panes and add content
26
+ * Dress up text (in panes or anywhere in the terminal) in bold, italic, underline, reverse color, blink and in any 256 terminal colors for foreground and background
27
+ * Use a simple editor to let users edit text in panes
28
+ * Left, right or center align text in panes
29
+ * Cursor movement around the terminal
30
+
31
+ # The elements
32
+ `rcurses` gives you the following elements:
33
+ * The class `Pane` to create and manilpulate panes/boxes
34
+ * Extensions to the class String to print text in various degrees of fancy and also strip any fanciness
35
+ * A module `Cursor` to give you cursor movements around the terminal
36
+ * A top level function `getchr` to capture a single character input from the user (much better than any Ruby built-ins)
37
+
38
+ # class Pane
39
+ To create a pane do something like this:
40
+ ```
41
+ mypane = Pane.new(80, 30, 30, 10, 19, 229)
42
+ ```
43
+ This will create a pane/box starting at terminal column/x 80 and row/y 30 with the width of 30 characters and a hight of 10 characters and with the foreground color 19 and background color 229 (from the 256 choices available)
44
+
45
+ The format for creating a pane is:
46
+ ```
47
+ Pane.new(startx, starty, width, height, foregroundcolor, backgroundcolor)
48
+ ```
49
+ You can drop the last two 256-color codes to create a pane with the defaults for your terminal. Also, you can add anything as `startx`, `starty`, `width` or `height` as those values will be run through a Ruby eval and stored in readable variables `x`, `y`, `w` and `h` respectively. So, a hight value of "@maxrow/2" is valid to create a pane with the height of half the terminal height (the integer corresponding to half the terminal height will then be accessible as the variable `h`). Use the variables @maxrow for terminal height and @maxcol for terminal width.
50
+
51
+ Avaliable properties/variables:
52
+
53
+ Property | Description
54
+ ---------------|---------------------------------------------------------------
55
+ startx | The `x` value to be "Eval-ed"
56
+ x | The readable x-value for the Pane
57
+ starty | The `y` value to be "Eval-ed"
58
+ y | The readable y-value for the Pane
59
+ width | The Pane width to be "Eval-ed"
60
+ w | The readable w-value for the Pane
61
+ height | The Pane height to be "Eval-ed"
62
+ h | The readable h-value for the Pane
63
+ fg | Foreground color for the Pane
64
+ bg | Background color for the Pane
65
+ border | Draw border around the Pane (=true) or not (=false), default being false
66
+ scroll | Whether to indicate more text to be shown above/below the Pane, default is true
67
+ text | The text/content of the Pane
68
+ ix | "Index" - the line number at the top of the Pane, starts at 0, the first line of text in the Pane
69
+ align | Text alignment in the Pane: "l" = lefts aligned, "c" = center, "r" = right, with the default "l"
70
+ prompt | The prompt to print at the beginning of a one-liner Pane used as an input box
71
+
72
+ The methods for Pane:
73
+
74
+ Method | Description
75
+ ---------------|---------------------------------------------------------------
76
+ new/init | Initializes a Pane with optional arguments startx, starty, width, height, foregroundcolor and backgroundcolor
77
+ move(x,y) | Move the pane by `x`and `y` (`mypane.move(-4,5)` will move the pane left four characters and five characters down)
78
+ refresh | Refreshes/redraws the Pane with content
79
+ edit | An editor for the Pane. When this is invoked, all existing font dressing is stripped and the user gets to edit the raw text. The user can add font effects similar to Markdown; Use an asterisk before and after text to be drawn in bold, text between forward-slashes become italic, and underline before and after text means the text will be underlined, a hash-sign before and after text makes the text reverse colored. You can also combine a whole set of dressings in this format: `<23,245,biurl|Hello World!>` - this will make "Hello World!" print in the color 23 with the background color 245 (regardless of the Pane's fg/bg setting) in bold, italic, underlined, reversed colored and blinking. Hitting `ESC` while in edit mode will disregard the edits, while `Ctrl-S` will save the edits
80
+ editline | Used for one-line Panes. It will print the content of the property `prompt` and then the property `text` that can then be edited by the user. Hitting `ESC` will disregard the edits, while `ENTER` will save the edited text
81
+
82
+ # class String extensions
83
+ Method extensions provided for the class String:
84
+
85
+ Method | Description
86
+ ---------------|---------------------------------------------------------------
87
+ fg(fg) | Set text to be printed with the foreground color `fg` (example: `"TEST".fg(84)`)
88
+ bg(bg) | Set text to be printed with the background color `bg` (example: `"TEST".bg(196)`)
89
+ fb(fg, bg) | Set text to be printed with the foreground color `fg` and background color `bg` (example: `"TEST".fb(84,196)`)
90
+ b | Set text to be printed in bold (example: `"TEST".b`)
91
+ i | Set text to be printed in italic (example: `"TEST".i`)
92
+ u | Set text to be printed underlined (example: `"TEST".u`)
93
+ l | Set text to be printed blinking (example: `"TEST".l`)
94
+ r | Set text to be printed in reverse colors (example: `"TEST".r`)
95
+ c(code) | Use coded format like "TEST".c("204,45,bui") to print "TEST" in bold, underline italic, fg=204 and bg=45 (the format is `.c("fg,bg,biulr"))
96
+ pure | Strip text of any "dressing" (example: with `text = "TEST".b`, you will have bold text in the variable `text`, then with `text.pure` it will show "uncoded" or pure text)
97
+
98
+ # module Cursor
99
+ Create a new cursor object with `mycursor = Cursor`. Then you can apply the following methods to `mycursor`:
100
+
101
+ Method | Description
102
+ ------------------|---------------------------------------------------------------
103
+ save | Save current position
104
+ restore | Restore cursor position
105
+ pos | Query cursor current position (example: `row,col = mycursor.pos`)
106
+ colget | Query cursor current cursor col/x position (example: `row = mycursor.rowget`)
107
+ rowget | Query cursor current cursor row/y position (example: `row = mycursor.rowget`)
108
+ up(n = 1) | Move cursor up by n (default is 1 character up)
109
+ down(n = 1) | Move cursor down by n (default is 1 character down)
110
+ left(n = 1) | Move cursor backward by n (default is one character)
111
+ right(n = 1) | Move cursor forward by n (default is one character)
112
+ col(n = 1) | Cursor moves to the nth position horizontally in the current line (default = first column)
113
+ row(n = 1) | Cursor moves to the nth position vertically in the current column (default = first/top row)
114
+ next_line) | Move cursor down to beginning of next line
115
+ prev_line) | Move cursor up to beginning of previous line
116
+ clear_char(n = 1) | Erase n characters from the current cursor position (default is one character)
117
+ clear_line | Erase the entire current line and return to beginning of the line
118
+ clear_line_before | Erase from the beginning of the line up to and including the current cursor position
119
+ clear_line_after | Erase from the current position (inclusive) to the end of the line
120
+ scroll_up | Scroll display up one line
121
+ scroll_down | Scroll display down one line
122
+ clear_screen_down | Clear screen down from current row
123
+
124
+ # function getchr
125
+ rcurses provides a vital extension to Ruby in reading characters entered by the user. This is especially needed for curses applications where readline inputs are required.
126
+
127
+ Simply use `chr = getchr` in a program to read any character input by the user. The returning code (the content of `chr` in this example could be any of the following:
128
+
129
+ Key pressed | string returned
130
+ ----------------|----------------------------------------------------------
131
+ `esc` | "ESC"
132
+ `up` | "UP"
133
+ `ctrl-up` | "C-UP"
134
+ `down` | "DOWN"
135
+ `ctrl-down` | "C-DOWN"
136
+ `right` | "RIGHT"
137
+ `ctrl-right` | "C-RIGHT"
138
+ `left` | "LEFT"
139
+ `ctrl-left` | "C-LEFT"
140
+ `shift-tab` | "S-TAB"
141
+ `insert` | "INS"
142
+ `ctrl-insert` | "C-INS"
143
+ `del` | "DEL"
144
+ `ctrl-del` | "C-DEL"
145
+ `pageup` | "PgUP"
146
+ `ctrl-pageup` | "C-PgUP"
147
+ `pagedown` | "PgDOWN"
148
+ `ctrl-pagedown` | "C-PgDOWN"
149
+ `home` | "HOME"
150
+ `ctrl-home` | "C-HOME"
151
+ `end` | "END"
152
+ `ctrl-end` | "C-END"
153
+ `backspace` | "BACK"
154
+ `ctrl-h` | "BACK"
155
+ `ctrl-a` | "C-A"
156
+ `ctrl-b` | "C-B"
157
+ `ctrl-c` | "C-C"
158
+ `ctrl-d` | "C-D"
159
+ `ctrl-e` | "C-E"
160
+ `ctrl-f` | "C-F"
161
+ `ctrl-g` | "C-G"
162
+ `ctrl-i` | "C-I"
163
+ `ctrl-j` | "C-J"
164
+ `ctrl-k` | "C-K"
165
+ `ctrl-l` | "C-L"
166
+ `ctrl-m` | "C-M"
167
+ `ctrl-n` | "C-N"
168
+ `ctrl-o` | "C-O"
169
+ `ctrl-p` | "C-P"
170
+ `ctrl-q` | "C-Q"
171
+ `ctrl-r` | "C-R"
172
+ `ctrl-s` | "C-S"
173
+ `ctrl-t` | "C-T"
174
+ `ctrl-u` | "C-U"
175
+ `ctrl-v` | "C-V"
176
+ `ctrl-a` | "WBACK"
177
+ `ctrl-x` | "C-X"
178
+ `ctrl-y` | "C-Y"
179
+ `ctrl-z` | "C-Z"
180
+ `enter` | "ENTER"
181
+ `tab` | "TAB"
182
+
183
+ Any other character enter will be returned (to `chr` in the example above).
184
+
185
+ In order to handle several character pased into STDIN by the user (and not only returned the first character only, your program should empty the STDIN like this:
186
+
187
+ ```
188
+ while $stdin.ready?
189
+ chr += $stdin.getc
190
+ end
191
+ ```
192
+
193
+ # Not yet implemented
194
+ Let me know what other features you like to see.
195
+
196
+ # License and copyright
197
+ Just steal or borrow anything you like. This is now Public Domain.
data/extconf.rb ADDED
@@ -0,0 +1,33 @@
1
+ require 'fileutils'
2
+
3
+ # Define the source file and the target directory
4
+ source_file = File.join(File.dirname(__FILE__), 'lib/rcurses.rb')
5
+ target_dir = '/usr/lib/ruby/vendor_ruby'
6
+
7
+ # Check if we have write permission to the target directory
8
+ unless File.writable?(target_dir)
9
+ abort("You need root permissions to install to #{target_dir}. Please run as root or use sudo.")
10
+ end
11
+
12
+ # Create the target directory if it doesn't exist
13
+ FileUtils.mkdir_p(target_dir)
14
+
15
+ # Copy the file
16
+ FileUtils.cp(source_file, target_dir)
17
+
18
+ puts "Installed #{source_file} to #{target_dir}"
19
+
20
+ # Create a dummy Makefile to satisfy Ruby's gem installation process
21
+ File.open('Makefile', 'w') do |f|
22
+ f.puts <<-MAKEFILE
23
+ all:
24
+ \t@echo "Nothing to build"
25
+
26
+ clean:
27
+ \t@echo "Nothing to clean"
28
+
29
+ install:
30
+ \t@echo "Installation handled by extconf.rb"
31
+ MAKEFILE
32
+ end
33
+
data/lib/rcurses.rb ADDED
@@ -0,0 +1,528 @@
1
+ # INFORMATION
2
+ # Name: rcurses - Ruby CURSES
3
+ # Language: Pure Ruby, best viewed in VIM
4
+ # Author: Geir Isene <g@isene.com>
5
+ # Web_site: http://isene.com/
6
+ # Github: https://github.com/isene/rcurses
7
+ # License: Public domain
8
+ # Version = 0.1 : Initial upload to GitHub
9
+
10
+ class Pane
11
+ attr_accessor :startx, :starty, :width, :height, :fg, :bg
12
+ attr_reader :x, :y, :w, :h
13
+ attr_accessor :border, :scroll, :text, :ix, :align, :prompt
14
+ def initialize(startx=1, starty=1, width=1, height=1, fg=nil, bg=nil)
15
+ @startx, @starty, @width, @height, @fg, @bg = startx, starty, width, height, fg, bg
16
+ @text = "" # Initialize text variable
17
+ @align = "l" # Default alignment
18
+ @scroll = true # Initialize scroll indicators to true
19
+ @prompt = "" # Initialize prompt for editline
20
+ @c = Cursor # Local cursor object
21
+ @ix = 0 # Text index (starting text line in pane)
22
+ self.refresh # Draw the pane upon creation
23
+ end
24
+ def move (x,y)
25
+ self.startx = self.x + x
26
+ self.starty = self.y + y
27
+ self.refresh
28
+ end
29
+ def ansicheck(string, ansi)
30
+ ansi_beg = /\u0001\e\[#{ansi[0].to_s}m\u0002/
31
+ if ansi[0] == 38 or ansi[0] == 48
32
+ ansi_beg = /\u0001\e\[#{ansi[0].to_s}[\d;m]+\u0002/
33
+ end
34
+ ansi_end = /\u0001\e\[#{ansi[1].to_s}m\u0002/
35
+ begix = string.rindex(ansi_beg)
36
+ begix = -1 if begix == nil
37
+ endix = string.rindex(ansi_end)
38
+ endix = -1 if endix == nil
39
+ dirty = string[begix..-1].match(ansi_beg)[0] if begix > endix
40
+ dirty.to_s
41
+ end
42
+ def textformat(cont=self.text)
43
+ @txt = cont.split("\n") # Split text into array
44
+ if @txt.any? { |line| line.length >= self.w } # Splitx for lines breaking width
45
+ @txt = cont.splitx(self.w)
46
+ if cont != cont.pure # Treat lines that break ANSI escape codes
47
+ @dirty = "" # Initiate dirty flag (for unclosed ANSI escape sequence)
48
+ @txt.each_index do |i|
49
+ if @dirty != "" # If dirty flag is set from previous line then add the ANSI sequence to beginning of this line
50
+ @txt[i] = @dirty + @txt[i]; @dirty = ""
51
+ end
52
+ @dirty += ansicheck(@txt[i], [1,22]) # Bold
53
+ @dirty += ansicheck(@txt[i], [3,23]) # Italic
54
+ @dirty += ansicheck(@txt[i], [4,24]) # Underline
55
+ @dirty += ansicheck(@txt[i], [5,25]) # Blink
56
+ @dirty += ansicheck(@txt[i], [7,27]) # Reverse
57
+ @dirty += ansicheck(@txt[i], [38,0]) # Foreground color
58
+ @dirty += ansicheck(@txt[i], [48,0]) # Background color
59
+ @txt[i].sub!(/[\u0001\u0002\e\[\dm]*$/, '')
60
+ end
61
+ end
62
+ end
63
+ @txt
64
+ end
65
+ def refresh(cont=self.text)
66
+ o_row, o_col = @c.pos
67
+ @c.row(8000); @c.col(8000) # Set for maxrow/maxcol
68
+ @maxrow, @maxcol = @c.pos
69
+ self.x = eval(self.startx.to_s)
70
+ self.y = eval(self.starty.to_s)
71
+ self.w = eval(self.width.to_s)
72
+ self.h = eval(self.height.to_s)
73
+ if self.border # Keep panes inside screen
74
+ self.x = 2 if self.x < 2; self.x = @maxcol - self.w if self.x + self.w > @maxcol
75
+ self.y = 2 if self.y < 2; self.y = @maxrow - self.h if self.y + self.h > @maxrow
76
+ else
77
+ self.x = 1 if self.x < 1; self.x = @maxcol - self.w + 1 if self.x + self.w > @maxcol + 1
78
+ self.y = 1 if self.y < 1; self.y = @maxrow - self.h + 1 if self.y + self.h > @maxrow + 1
79
+ end
80
+ @c.col(self.x); @c.row(self.y) # Cursor to start of pane
81
+ fmt = self.fg.to_s + "," + self.bg.to_s # Format for printing in pane (fg,bg)
82
+ @txt = textformat(cont) # Call function to create an array out of the pane text
83
+ @ix = @txt.length - 1 if @ix > @txt.length - 1; @ix = 0 if @ix < 0 # Ensuring no out-of-bounds
84
+ self.h.times do |i| # Print pane content
85
+ l = @ix + i # The current line to be printed
86
+ if @txt[l].to_s != "" # Print the text line for line
87
+ pl = self.w - @txt[l].pure.length; hl = pl/2 # Get padding width and half width
88
+ print @txt[l].c(fmt) + " ".c(fmt) * pl if self.align == "l"
89
+ print " ".c(fmt) * pl + @txt[l].c(fmt) if self.align == "r"
90
+ print " ".c(fmt) * hl + @txt[l].c(fmt) + " ".c(fmt) * (pl - hl) if self.align == "c"
91
+ else
92
+ print "".rjust(self.w).bg(self.bg)
93
+ end
94
+ r,c = @c.pos
95
+ @c.row(r + 1) # Cursor down one line
96
+ @c.col(self.x) # Cursor to start of pane
97
+ end
98
+ if @ix > 0 and self.scroll # Print "more" marker at top
99
+ @c.col(self.x + self.w - 1); @c.row(self.y)
100
+ print "▲".c(fmt)
101
+ end
102
+ if @txt.length - @ix > self.h and self.scroll # Print bottom "more" marker
103
+ @c.col(self.x + self.w - 1); @c.row(self.y + self.h - 1)
104
+ print "▼".c(fmt)
105
+ end
106
+ if self.border # Print border if self.border is set to "true"
107
+ @c.row(self.y - 1); @c.col(self.x - 1)
108
+ print ("┌" + "─" * self.w + "┐").c(fmt)
109
+ self.h.times do |i|
110
+ @c.row(self.y + i); @c.col(self.x - 1)
111
+ print "│".c(fmt)
112
+ @c.col(self.x + self.w)
113
+ print "│".c(fmt)
114
+ end
115
+ @c.row(self.y + self.h); @c.col(self.x - 1)
116
+ print ("└" + "─" * self.w + "┘").c(fmt)
117
+ end
118
+ @c.row(o_row)
119
+ @c.col(o_col)
120
+ @txt
121
+ end
122
+ def right
123
+ if @pos < @txt[self.ix + @line].length
124
+ @pos += 1
125
+ else
126
+ if @line == self.h
127
+ self.ix += 1 unless self.ix >= @txt.length
128
+ @pos = 0
129
+ elsif @line + self.ix + 2 <= @txt.length
130
+ @line += 1
131
+ @pos = 0
132
+ end
133
+ end
134
+ end
135
+ def left
136
+ if @pos == 0
137
+ if @line == 0
138
+ unless self.ix == 0
139
+ self.ix -= 1
140
+ @pos = @txt[self.ix + @line].length - 1
141
+ end
142
+ else
143
+ @line -= 1
144
+ @pos = @txt[self.ix + @line].length - 1
145
+ end
146
+ else
147
+ @pos -= 1
148
+ end
149
+ end
150
+ def up
151
+ if @line == 0
152
+ self.ix -= 1 unless self.ix == 0
153
+ else
154
+ @line -= 1
155
+ end
156
+ begin
157
+ @pos = @txt[self.ix + @line].length - 1 if @pos > @txt[self.ix + @line].length - 1
158
+ rescue
159
+ end
160
+ end
161
+ def down
162
+ if @line == self.h - 1
163
+ self.ix += 1 unless self.ix + @line >= @txt.length - 1
164
+ elsif @line + self.ix + 2 <= @txt.length
165
+ @line += 1
166
+ end
167
+ begin
168
+ @pos = @txt[self.ix + @line].length - 1 if @pos > @txt[self.ix + @line].length - 1
169
+ rescue
170
+ end
171
+ end
172
+ def parse(cont)
173
+ cont.gsub!(/\*(.+?)\*/, '\1'.b)
174
+ cont.gsub!(/\/(.+?)\//, '\1'.i)
175
+ cont.gsub!(/_(.+?)_/, '\1'.u)
176
+ cont.gsub!(/#(.+?)#/, '\1'.r)
177
+ cont.gsub!(/<([^|]+)\|([^>]+)>/) do |m|
178
+ text = $2; codes = $1
179
+ text.c(codes)
180
+ end
181
+ cont
182
+ end
183
+ def edit
184
+ cont = self.text.pure.gsub(/\n/, "¬\n")
185
+ @line = self.ix
186
+ @pos = 0
187
+ chr = ""
188
+ while chr != "ESC" # Keep going with readline until user presses ESC
189
+ @txt = self.refresh(cont)
190
+ @c.row(self.y + @line)
191
+ @c.col(self.x + @pos)
192
+ chr = getchr
193
+ case chr
194
+ when 'C-L' # Left justify
195
+ self.align = "l"
196
+ when 'C-R' # Right Justify
197
+ self.align = "r"
198
+ when 'C-C' # Center justify
199
+ self.align = "c"
200
+ when 'C-Y' # Copy pane content to clipboard
201
+ system("echo '#{self.text.pure}' | xclip")
202
+ when 'C-S' # Save edited text back to self.text
203
+ cont = cont.gsub("¬", "\n")
204
+ cont = parse(cont)
205
+ self.text = cont
206
+ chr = "ESC"
207
+ when 'DEL' # Delete character
208
+ posx = 0; (self.ix + @line).times {|i| posx += @txt[i].length + 1}; posx += @pos
209
+ cont[posx] = ""
210
+ when 'BACK' # Backspace
211
+ left
212
+ posx = 0; (self.ix + @line).times {|i| posx += @txt[i].length + 1}; posx += @pos
213
+ cont[posx] = ""
214
+ when 'WBACK' # Word backspace
215
+ posx = 0; (self.ix + @line).times {|i| posx += @txt[i].length + 1}; posx += @pos
216
+ until cont[posx - 1] == " " or @pos == 0
217
+ left
218
+ posx = 0; (self.ix + @line).times {|i| posx += @txt[i].length + 1}; posx += @pos
219
+ cont[posx] = ""
220
+ end
221
+ when 'C-K' # Kill line
222
+ begin
223
+ prev_l = 0
224
+ line_c = self.ix + @line
225
+ line_c.times {|i| prev_l += @txt[i].length + 1}
226
+ cur_l = @txt[line_c].length
227
+ cont.slice!(prev_l,cur_l)
228
+ rescue
229
+ end
230
+ when 'UP' # Up one line
231
+ up
232
+ when 'DOWN' # Down one line
233
+ down
234
+ when 'RIGHT' # Right one character
235
+ right
236
+ when 'LEFT' # Left one character
237
+ left
238
+ when 'HOME' # Start of line
239
+ @pos = 0
240
+ when 'END' # End of line
241
+ @pos = @txt[self.ix + @line].length - 1
242
+ when 'C-HOME' # Start of pane
243
+ @line = 0
244
+ @pos = 0
245
+ when 'C-END' # End of pane
246
+ when 'ENTER'
247
+ posx = 0; (self.ix + @line).times {|i| posx += @txt[i].length + 1}; posx += @pos
248
+ cont.insert(posx,"¬\n")
249
+ right
250
+ when /^.$/
251
+ posx = 0; (self.ix + @line).times {|i| posx += @txt[i].length + 1}; posx += @pos
252
+ cont.insert(posx,chr)
253
+ end
254
+ charx = 1
255
+ posx = 0; (self.ix + @line).times {|i| posx += @txt[i].length + 1}; posx += @pos
256
+ while $stdin.ready?
257
+ chr = $stdin.getc
258
+ charx += 1
259
+ posx += 1
260
+ cont.insert(posx,chr)
261
+ end
262
+ if chr =~ /^.$/
263
+ self.refresh(cont)
264
+ charx.times {right}
265
+ end
266
+
267
+ end
268
+ # Keep?
269
+ @c.row(nil)
270
+ @c.col(nil)
271
+ end
272
+ def editline
273
+ self.x = eval(self.startx.to_s)
274
+ self.y = eval(self.starty.to_s)
275
+ self.w = eval(self.width.to_s)
276
+ self.h = eval(self.height.to_s)
277
+ self.x = 1 if self.x < 1; self.x = @maxcol - self.w + 1 if self.x + self.w > @maxcol + 1
278
+ self.y = 1 if self.y < 1; self.y = @maxrow - self.h + 1 if self.y + self.h > @maxrow + 1
279
+ self.scroll = false
280
+ @c.row(self.y)
281
+ fmt = self.fg.to_s + "," + self.bg.to_s # Format for printing in pane (fg,bg)
282
+ @c.col(self.x); print self.prompt.c(fmt) # Print prompt from start of pane
283
+ pl = self.prompt.pure.length # Prompt length
284
+ cl = self.w - pl # Available content length
285
+ cont = self.text.pure # Actual content
286
+ @pos = cont.length # Initial position set at end of content
287
+ chr = "" # Initialize chr
288
+ while chr != "ESC" # Keep going with readline until user presses ESC
289
+ @c.col(self.x + pl) # Set cursor at start of content
290
+ cont = cont.slice(0,cl) # Trim content to max length
291
+ print cont.ljust(cl).c(fmt) # Print content left justified to max length
292
+ @c.col(self.x + pl + @pos) # Set cursor to current position (@pos)
293
+ chr = getchr # Read character from user input
294
+ case chr
295
+ when 'LEFT' # One character left
296
+ @pos -= 1 unless @pos == 0
297
+ when 'RIGHT' # One character right
298
+ @pos += 1 unless @pos >= cont.length
299
+ when 'HOME' # To start of content
300
+ @pos = 0
301
+ when 'END' # To end of content
302
+ @pos = cont.length - 1
303
+ when 'DEL' # Delete character
304
+ cont[@pos] = ""
305
+ when 'BACK' # Backspace
306
+ @pos -= 1 unless @pos == 0
307
+ cont[@pos] = ""
308
+ when 'WBACK' # Word backspace
309
+ until cont[@pos - 1] == " " or @pos == 0
310
+ @pos -= 1
311
+ cont[@pos] = ""
312
+ end
313
+ when 'C-K' # Kill line (set content to nothing)
314
+ cont = ""
315
+ @pos = 0
316
+ when 'ENTER' # Save content to self.text and end
317
+ self.text = parse(cont)
318
+ chr = 'ESC'
319
+ when /^.$/ # Add character to content
320
+ unless @pos >= cl - 1
321
+ cont.insert(@pos,chr)
322
+ @pos += 1
323
+ end
324
+ end
325
+ while $stdin.ready? # Add characters from pasted input
326
+ chr = $stdin.getc
327
+ unless @pos >= cl - 1
328
+ cont.insert(@pos,chr)
329
+ @pos += 1
330
+ end
331
+ end
332
+ end
333
+ # Keep? Set cursor away from pane
334
+ @c.row(nil)
335
+ @c.col(nil)
336
+ end
337
+ end
338
+
339
+ class String # Add coloring to strings (with escaping for Readline)
340
+ def fg(fg); color(self, "\001\e[38;5;#{fg}m\002", "\001\e[0m\002"); end # Foreground color code
341
+ def bg(bg); color(self, "\001\e[48;5;#{bg}m\002", "\001\e[0m\002"); end # Background color code
342
+ def fb(fg, bg); color(self, "\001\e[38;5;#{fg};48;5;#{bg}m\002"); end # Fore/Background color code
343
+ def b; color(self, "\001\e[1m\002", "\001\e[22m\002"); end # Bold
344
+ def i; color(self, "\001\e[3m\002", "\001\e[23m\002"); end # Italic
345
+ def u; color(self, "\001\e[4m\002", "\001\e[24m\002"); end # Underline
346
+ def l; color(self, "\001\e[5m\002", "\001\e[25m\002"); end # Blink
347
+ def r; color(self, "\001\e[7m\002", "\001\e[27m\002"); end # Reverse
348
+ def color(text, sp, ep) "#{sp}#{text}#{ep}" end # Internal function
349
+ def c(code) # Use format "TEST".c("204,45,bui") to print "TEST" in bold, underline italic, fg=204 and bg=45
350
+ prop = "\001\e["
351
+ prop += "38;5;#{code.match(/^\d+/).to_s};" if code.match(/^\d+/)
352
+ prop += "48;5;#{code.match(/(?<=,)\d+/).to_s};" if code.match(/(?<=,)\d+/)
353
+ prop += "1;" if code.match(/b/)
354
+ prop += "3;" if code.match(/i/)
355
+ prop += "4;" if code.match(/u/)
356
+ prop += "5;" if code.match(/l/)
357
+ prop += "7;" if code.match(/r/)
358
+ prop.chop! if prop[-1] == ";"
359
+ prop += "m\002"
360
+ prop += self
361
+ prop += "\001\e[0m\002"
362
+ #puts "\n XX\n" + code
363
+ prop
364
+ end
365
+ def pure
366
+ self.gsub(/\u0001.*?\u0002/, '')
367
+ end
368
+ def splitx(x)
369
+ lines = self.split("\n")
370
+ until lines.all? { |line| line.pure.length <= x } do
371
+ lines.map! do |l|
372
+ if l.pure.length > x and l[0..x].match(/ /)
373
+ @ix = l[0..x].rindex(" ")
374
+ [ l[0...@ix], l[(@ix + 1)..-1] ]
375
+ elsif l.pure.length > x
376
+ [l[0...x], l[x..-1]]
377
+ else
378
+ l
379
+ end
380
+ end
381
+ lines.flatten!
382
+ end
383
+ lines.reject { |e| e.to_s.empty? }
384
+ end
385
+ end
386
+
387
+ module Cursor # Terminal cursor movement ANSI codes (thanks to https://github.com/piotrmurach/tty-cursor)
388
+ module_function
389
+ ESC = "\e".freeze
390
+ CSI = "\e[".freeze
391
+ def save # Save current position
392
+ print(Gem.win_platform? ? CSI + 's' : ESC + '7')
393
+ end
394
+ def restore # Restore cursor position
395
+ print(Gem.win_platform? ? CSI + 'u' : ESC + '8')
396
+ end
397
+ def pos # Query cursor current position
398
+ res = ''
399
+ $stdin.raw do |stdin|
400
+ $stdout << CSI + '6n' # Tha actual ANSI get-position
401
+ $stdout.flush
402
+ while (c = stdin.getc) != 'R'
403
+ res << c if c
404
+ end
405
+ end
406
+ m = res.match /(?<row>\d+);(?<col>\d+)/
407
+ return m[:row].to_i, m[:col].to_i
408
+ end
409
+ def rowget
410
+ row, col = self.pos
411
+ return row
412
+ end
413
+ def colget
414
+ row, col = self.pos
415
+ return col
416
+ end
417
+ def up(n = 1) # Move cursor up by n
418
+ print(CSI + "#{(n || 1)}A")
419
+ end
420
+ def down(n = 1) # Move the cursor down by n
421
+ print(CSI + "#{(n || 1)}B")
422
+ end
423
+ def left(n = 1) # Move the cursor backward by n
424
+ print(CSI + "#{n || 1}D")
425
+ end
426
+ def right(n = 1) # Move the cursor forward by n
427
+ print(CSI + "#{n || 1}C")
428
+ end
429
+ def col(n = 1) # Cursor moves to nth position horizontally in the current line
430
+ print(CSI + "#{n || 1}G")
431
+ end
432
+ def row(n = 1) # Cursor moves to the nth position vertically in the current column
433
+ print(CSI + "#{n || 1}d")
434
+ end
435
+ def next_line # Move cursor down to beginning of next line
436
+ print(CSI + 'E' + CSI + "1G")
437
+ end
438
+ def prev_line # Move cursor up to beginning of previous line
439
+ print(CSI + 'A' + CSI + "1G")
440
+ end
441
+ def clear_char(n = 1) # Erase n characters from the current cursor position
442
+ print(CSI + "#{n}X")
443
+ end
444
+ def clear_line # Erase the entire current line and return to beginning of the line
445
+ print(CSI + '2K' + CSI + "1G")
446
+ end
447
+ def clear_line_before # Erase from the beginning of the line up to and including the current cursor position.
448
+ print(CSI + '1K')
449
+ end
450
+ def clear_line_after # Erase from the current position (inclusive) to the end of the line
451
+ print(CSI + '0K')
452
+ end
453
+ def scroll_up # Scroll display up one line
454
+ print(ESC + 'M')
455
+ end
456
+ def scroll_down # Scroll display down one line
457
+ print(ESC + 'D')
458
+ end
459
+ def clear_screen_down # Clear screen down from current row
460
+ print(CSI + 'J')
461
+ end
462
+ end
463
+
464
+ def getchr # Function to process key presses
465
+ c = $stdin.getch
466
+ case c
467
+ when "\e" # ANSI escape sequences (with only ESC, it should stop right here)
468
+ return "ESC" if $stdin.ready? == nil
469
+ case $stdin.getc
470
+ when '[' # CSI
471
+ case $stdin.getc # Will get (or ASK) for more (remaining part of special character)
472
+ when 'A' then chr = "UP"
473
+ when 'B' then chr = "DOWN"
474
+ when 'C' then chr = "RIGHT"
475
+ when 'D' then chr = "LEFT"
476
+ when 'Z' then chr = "S-TAB"
477
+ when '2' then chr = "INS" ; chr = "C-INS" if $stdin.getc == "^"
478
+ when '3' then chr = "DEL" ; chr = "C-DEL" if $stdin.getc == "^"
479
+ when '5' then chr = "PgUP" ; chr = "C-PgUP" if $stdin.getc == "^"
480
+ when '6' then chr = "PgDOWN" ; chr = "C-PgDOWN" if $stdin.getc == "^"
481
+ when '7' then chr = "HOME" ; chr = "C-HOME" if $stdin.getc == "^"
482
+ when '8' then chr = "END" ; chr = "C-END" if $stdin.getc == "^"
483
+ else chr = ""
484
+ end
485
+ when 'O' # Set Ctrl+ArrowKey equal to ArrowKey; May be used for other purposes in the future
486
+ case $stdin.getc
487
+ when 'a' then chr = "C-UP"
488
+ when 'b' then chr = "C-DOWN"
489
+ when 'c' then chr = "C-RIGHT"
490
+ when 'd' then chr = "C-LEFT"
491
+ else chr = ""
492
+ end
493
+ end
494
+ when "", "" then chr = "BACK"
495
+ when "" then chr = "C-A"
496
+ when "" then chr = "C-B"
497
+ when "" then chr = "C-C"
498
+ when "^D" then chr = "C-D"
499
+ when "" then chr = "C-E"
500
+ when "" then chr = "C-F"
501
+ when "^G" then chr = "C-G"
502
+ when " " then chr = "C-I"
503
+ when "" then chr = "C-J"
504
+ when " " then chr = "C-K"
505
+ when " " then chr = "C-L"
506
+ when "
507
+ when "^N" then chr = "C-N"
508
+ when "^O" then chr = "C-O"
509
+ when "^P" then chr = "C-P"
510
+ when "" then chr = "C-Q"
511
+ when "" then chr = "C-R"
512
+ when "^T" then chr = "C-T"
513
+ when "" then chr = "C-U"
514
+ when "" then chr = "C-V"
515
+ when "" then chr = "C-X"
516
+ when "" then chr = "C-Y"
517
+ when "" then chr = "C-Z"
518
+ when "" then chr = "WBACK"
519
+ when "\r" then chr = "ENTER"
520
+ when "\t" then chr = "TAB"
521
+ when "" then chr = "C-S"
522
+ when /[[:print:]]/ then chr = c
523
+ else chr = ""
524
+ end
525
+ return chr
526
+ end
527
+
528
+ # vim: set sw=2 sts=2 et filetype=ruby fdm=syntax fdn=2 fcs=fold\:\ :
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rcurses
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Geir Isene
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-11-16 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Create panes (with the colors and(or border), manipulate the panes and
14
+ add content. Dress up text (in panes or anywhere in the terminal) in bold, italic,
15
+ underline, reverse color, blink and in any 256 terminal colors for foreground and
16
+ background. Use a simple editor to let users edit text in panes. Left, right or
17
+ center align text in panes. Cursor movement around the terminal.
18
+ email: g@isene.com
19
+ executables: []
20
+ extensions:
21
+ - extconf.rb
22
+ extra_rdoc_files: []
23
+ files:
24
+ - README.md
25
+ - extconf.rb
26
+ - lib/rcurses.rb
27
+ homepage: https://isene.com/
28
+ licenses:
29
+ - Unlicense
30
+ metadata:
31
+ source_code_uri: https://github.com/isene/rcurses
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubygems_version: 3.4.20
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: rcurses - An alternative curses library written in pure Ruby
51
+ test_files: []