rcurses 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +197 -0
- data/extconf.rb +33 -0
- data/lib/rcurses.rb +528 -0
- 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: []
|