mancurses 0.0.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.
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +43 -0
- data/Rakefile +1 -0
- data/bin/mancurses +724 -0
- data/mancurses.gemspec +21 -0
- metadata +109 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Rahul Kumar
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# Mancurses
|
2
|
+
|
3
|
+
Browse manpages inside ncurses. Jump around to other man pages
|
4
|
+
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
$ gem install mancurses
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
Use Alt-c to enter program name to search such as "grep", "strcmp" etc.
|
13
|
+
Use "/" to search for strings within the page displayed.
|
14
|
+
|
15
|
+
Use '?' to see bindings.
|
16
|
+
|
17
|
+
When pressing F1 for help or "?" for bindings, and returning, the background is black. You need to
|
18
|
+
press a key for it to refresh. I am figuring this out.
|
19
|
+
|
20
|
+
## General
|
21
|
+
|
22
|
+
This gem is not really gonna make much difference to you. It's faster to just type `man grep` on the
|
23
|
+
command line and 'q' to get out. It's not like you keep manning pages one after another.
|
24
|
+
|
25
|
+
I am writing this to test out a new text widget which uses a pad. I hope to replace the current
|
26
|
+
text widgets such as textview and list and maybe tabular and tree in rbcurse-core with this.
|
27
|
+
|
28
|
+
Currently, using a window requires a lot of work each time one scrolls around. Too much string creation , truncation, sanitizing and gc going on repeatedly. Using a pad simplifies all this.
|
29
|
+
|
30
|
+
However, pad is not without its issues. If I have two pads on the screen, and a popup is displayed,
|
31
|
+
then a black rectangle is left on the other pad. I would have to tab there and scroll for a `prefresh` to happen. Otherwise, the app needs to do some book-keeping of underlying pads created and refresh them when a messagebox or window closes.
|
32
|
+
|
33
|
+
If i cannot manage that reliably, then i cannot include this in the main rbcurse-core.
|
34
|
+
|
35
|
+
## Contributing
|
36
|
+
|
37
|
+
1. Fork it
|
38
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
39
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
40
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
41
|
+
5. Create new Pull Request
|
42
|
+
|
43
|
+
https://rubygems.org/profiles/rkumar
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/mancurses
ADDED
@@ -0,0 +1,724 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# ----------------------------------------------------------------------------- #
|
3
|
+
# File: mancurses
|
4
|
+
# Description: A class that displays text using a pad.
|
5
|
+
# The motivation for this is to put formatted text and not care about truncating and
|
6
|
+
# stuff. Also, there will be only one write, not each time scrolling happens.
|
7
|
+
# I found textview code for repaint being more complex than required.
|
8
|
+
# Author: rkumar http://github.com/rkumar/mancurses/
|
9
|
+
# Date: 2011-11-09 - 16:59
|
10
|
+
# License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
11
|
+
# Last update: 2013-03-08 01:14
|
12
|
+
#
|
13
|
+
# == CHANGES
|
14
|
+
# == TODO
|
15
|
+
# when moving right, also don't pan straight away
|
16
|
+
# x add mappings and process key in handle_keys and other widget things
|
17
|
+
# x user can put text or list
|
18
|
+
# . handle putting data again and overwriting existing
|
19
|
+
# . formatted text
|
20
|
+
# x search and other features
|
21
|
+
# - can pad movement and other ops be abstracted into module for reuse
|
22
|
+
# / get scrolling like in vim (C-f e y b d)
|
23
|
+
# - alert issue of leaving a blank is poss due to using prefresh i/o copywin
|
24
|
+
#
|
25
|
+
# == TODO 2013-03-07 - 20:34
|
26
|
+
# _ key bindings not showing up -- bind properly
|
27
|
+
# _ F1 screen leaves everything blank, so does bindings
|
28
|
+
# ----------------------------------------------------------------------------- #
|
29
|
+
#
|
30
|
+
require 'rbcurse'
|
31
|
+
require 'rbcurse/core/include/bordertitle'
|
32
|
+
|
33
|
+
include RubyCurses
|
34
|
+
module RubyCurses
|
35
|
+
extend self
|
36
|
+
class TextPad < Widget
|
37
|
+
include BorderTitle
|
38
|
+
|
39
|
+
dsl_accessor :suppress_border
|
40
|
+
# You may pass height, width, row and col for creating a window otherwise a fullscreen window
|
41
|
+
# will be created. If you pass a window from caller then that window will be used.
|
42
|
+
# Some keys are trapped, jkhl space, pgup, pgdown, end, home, t b
|
43
|
+
# This is currently very minimal and was created to get me started to integrating
|
44
|
+
# pads into other classes such as textview.
|
45
|
+
def initialize form=nil, config={}, &block
|
46
|
+
|
47
|
+
@editable = false
|
48
|
+
@focusable = true
|
49
|
+
@config = config
|
50
|
+
@prow = @pcol = 0
|
51
|
+
@startrow = 0
|
52
|
+
@startcol = 0
|
53
|
+
@list = []
|
54
|
+
super
|
55
|
+
|
56
|
+
# FIXME 0 as height craps out. need to make it LINES
|
57
|
+
|
58
|
+
#@height = @height.ifzero(FFI::NCurses.LINES)
|
59
|
+
#@width = @width.ifzero(FFI::NCurses.COLS)
|
60
|
+
@rows = @height
|
61
|
+
@cols = @width
|
62
|
+
@startrow = @row
|
63
|
+
@startcol = @col
|
64
|
+
#@suppress_border = config[:suppress_border]
|
65
|
+
@row_offset = @col_offset = 1
|
66
|
+
unless @suppress_border
|
67
|
+
@startrow += 1
|
68
|
+
@startcol += 1
|
69
|
+
@rows -=3 # 3 is since print_border_only reduces one from width, to check whether this is correct
|
70
|
+
@cols -=3
|
71
|
+
end
|
72
|
+
@row_offset = @col_offset = 0 if @suppress_borders
|
73
|
+
@top = @row
|
74
|
+
@left = @col
|
75
|
+
@lastrow = @row + 1
|
76
|
+
@lastcol = @col + 1
|
77
|
+
init_vars
|
78
|
+
end
|
79
|
+
def init_vars
|
80
|
+
@scrollatrows = @height - 3
|
81
|
+
@oldindex = @current_index = 0
|
82
|
+
# column cursor
|
83
|
+
@ccol = 0
|
84
|
+
@repaint_required = true
|
85
|
+
end
|
86
|
+
def rowcol #:nodoc:
|
87
|
+
return @row+@row_offset, @col+@col_offset
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
def create_pad
|
92
|
+
destroy if @pad
|
93
|
+
#@pad = FFI::NCurses.newpad(@content_rows, @content_cols)
|
94
|
+
@pad = @window.get_pad(@content_rows, @content_cols )
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
# create and populate pad
|
99
|
+
def populate_pad
|
100
|
+
@_populate_needed = false
|
101
|
+
# how can we make this more sensible ? FIXME
|
102
|
+
@renderer ||= DefaultRubyRenderer.new if ".rb" == @filetype
|
103
|
+
@content_rows = @content.count
|
104
|
+
@content_cols = content_cols()
|
105
|
+
@content_rows = @rows if @content_rows < @rows
|
106
|
+
@content_cols = @cols if @content_cols < @cols
|
107
|
+
$log.debug "XXXX content_cols = #{@content_cols}"
|
108
|
+
|
109
|
+
create_pad
|
110
|
+
|
111
|
+
Ncurses::Panel.update_panels
|
112
|
+
@content.each_index { |ix|
|
113
|
+
#FFI::NCurses.mvwaddstr(@pad,ix, 0, @content[ix])
|
114
|
+
render @pad, ix, @content[ix]
|
115
|
+
}
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
public
|
120
|
+
# supply a custom renderer that implements +render()+
|
121
|
+
# @see render
|
122
|
+
def renderer r
|
123
|
+
@renderer = r
|
124
|
+
end
|
125
|
+
#
|
126
|
+
# default method for rendering a line
|
127
|
+
#
|
128
|
+
def render pad, lineno, text
|
129
|
+
if text.is_a? Chunks::ChunkLine
|
130
|
+
FFI::NCurses.wmove @pad, lineno, 0
|
131
|
+
a = get_attrib @attrib
|
132
|
+
|
133
|
+
show_colored_chunks text, nil, a
|
134
|
+
return
|
135
|
+
end
|
136
|
+
if @renderer
|
137
|
+
@renderer.render @pad, lineno, text
|
138
|
+
else
|
139
|
+
FFI::NCurses.mvwaddstr(@pad,lineno, 0, @content[lineno])
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# supply a filename as source for textpad
|
144
|
+
# Reads up file into @content
|
145
|
+
|
146
|
+
def filename(filename)
|
147
|
+
@file = filename
|
148
|
+
@filetype = File.extname filename
|
149
|
+
@content = File.open(filename,"r").readlines
|
150
|
+
if @filetype == ""
|
151
|
+
if @content.first.index("ruby")
|
152
|
+
@filetype = ".rb"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
@_populate_needed = true
|
156
|
+
end
|
157
|
+
|
158
|
+
# Supply an array of string to be displayed
|
159
|
+
# This will replace existing text
|
160
|
+
|
161
|
+
def text lines
|
162
|
+
raise "text() receiving null content" unless lines
|
163
|
+
@content = lines
|
164
|
+
@_populate_needed = true
|
165
|
+
end
|
166
|
+
|
167
|
+
## ---- the next 2 methods deal with printing chunks
|
168
|
+
# we should put it int a common module and include it
|
169
|
+
# in Window and Pad stuff and perhaps include it conditionally.
|
170
|
+
|
171
|
+
## 2013-03-07 - 19:57 changed width to @content_cols since data not printing
|
172
|
+
# in some cases fully when ansi sequences were present int some line but not in others
|
173
|
+
# lines without ansi were printing less by a few chars.
|
174
|
+
def print(string, _width = @content_cols)
|
175
|
+
#return unless visible?
|
176
|
+
w = _width == 0? Ncurses.COLS : _width
|
177
|
+
FFI::NCurses.waddnstr(@pad,string.to_s, w) # changed 2011 dts
|
178
|
+
end
|
179
|
+
|
180
|
+
def show_colored_chunks(chunks, defcolor = nil, defattr = nil)
|
181
|
+
#return unless visible?
|
182
|
+
chunks.each do |chunk| #|color, chunk, attrib|
|
183
|
+
case chunk
|
184
|
+
when Chunks::Chunk
|
185
|
+
color = chunk.color
|
186
|
+
attrib = chunk.attrib
|
187
|
+
text = chunk.text
|
188
|
+
when Array
|
189
|
+
# for earlier demos that used an array
|
190
|
+
color = chunk[0]
|
191
|
+
attrib = chunk[2]
|
192
|
+
text = chunk[1]
|
193
|
+
end
|
194
|
+
|
195
|
+
color ||= defcolor
|
196
|
+
attrib ||= defattr || NORMAL
|
197
|
+
|
198
|
+
#cc, bg = ColorMap.get_colors_for_pair color
|
199
|
+
#$log.debug "XXX: CHUNK textpad #{text}, cp #{color} , attrib #{attrib}. #{cc}, #{bg} "
|
200
|
+
FFI::NCurses.wcolor_set(@pad, color,nil) if color
|
201
|
+
FFI::NCurses.wattron(@pad, attrib) if attrib
|
202
|
+
print(text)
|
203
|
+
FFI::NCurses.wattroff(@pad, attrib) if attrib
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def formatted_text text, fmt
|
208
|
+
require 'rbcurse/core/include/chunk'
|
209
|
+
@formatted_text = text
|
210
|
+
@color_parser = fmt
|
211
|
+
@repaint_required = true
|
212
|
+
# don't know if start is always required. so putting in caller
|
213
|
+
#goto_start
|
214
|
+
#remove_all
|
215
|
+
end
|
216
|
+
|
217
|
+
# write pad onto window
|
218
|
+
#private
|
219
|
+
def padrefresh
|
220
|
+
FFI::NCurses.prefresh(@pad,@prow,@pcol, @startrow, @startcol, @rows + @startrow, @cols+@startcol);
|
221
|
+
end
|
222
|
+
|
223
|
+
# convenience method to return byte
|
224
|
+
private
|
225
|
+
def key x
|
226
|
+
x.getbyte(0)
|
227
|
+
end
|
228
|
+
|
229
|
+
# length of longest string in array
|
230
|
+
# This will give a 'wrong' max length if the array has ansi color escape sequences in it
|
231
|
+
# which inc the length but won't be printed. Such lines actually have less length when printed
|
232
|
+
# So in such cases, give more space to the pad.
|
233
|
+
def content_cols
|
234
|
+
longest = @content.max_by(&:length)
|
235
|
+
## 2013-03-06 - 20:41 crashes here for some reason when man gives error message no man entry
|
236
|
+
return 0 unless longest
|
237
|
+
longest.length
|
238
|
+
end
|
239
|
+
|
240
|
+
public
|
241
|
+
def repaint
|
242
|
+
return unless @repaint_required
|
243
|
+
if @formatted_text
|
244
|
+
$log.debug "XXX: INSIDE FORMATTED TEXT "
|
245
|
+
|
246
|
+
l = RubyCurses::Utils.parse_formatted_text(@color_parser,
|
247
|
+
@formatted_text)
|
248
|
+
|
249
|
+
text(l)
|
250
|
+
@formatted_text = nil
|
251
|
+
end
|
252
|
+
|
253
|
+
## moved this line up or else create_p was crashing
|
254
|
+
@window ||= @graphic
|
255
|
+
populate_pad if @_populate_needed
|
256
|
+
#HERE we need to populate once so user can pass a renderer
|
257
|
+
unless @suppress_border
|
258
|
+
if @repaint_all
|
259
|
+
@window.print_border_only @top, @left, @height-1, @width, $datacolor
|
260
|
+
print_title
|
261
|
+
@window.wrefresh
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
padrefresh
|
266
|
+
Ncurses::Panel.update_panels
|
267
|
+
@repaint_required = false
|
268
|
+
@repaint_all = false
|
269
|
+
end
|
270
|
+
|
271
|
+
#
|
272
|
+
# key mappings
|
273
|
+
#
|
274
|
+
def map_keys
|
275
|
+
@mapped_keys = true
|
276
|
+
bind_key([?g,?g], 'goto_start'){ goto_start } # mapping double keys like vim
|
277
|
+
bind_key(279, 'goto_start'){ goto_start }
|
278
|
+
bind_keys([?G,277], 'goto end'){ goto_end }
|
279
|
+
bind_keys([?k,KEY_UP], "Up"){ up }
|
280
|
+
bind_keys([?j,KEY_DOWN], "Down"){ down }
|
281
|
+
bind_key(?\C-e, "Scroll Window Down"){ scroll_window_down }
|
282
|
+
bind_key(?\C-y, "Scroll Window Up"){ scroll_window_up }
|
283
|
+
bind_keys([32,338, ?\C-d], "Scroll Forward"){ scroll_forward }
|
284
|
+
bind_keys([?\C-b,339]){ scroll_backward }
|
285
|
+
bind_key([?',?']){ goto_last_position } # vim , goto last row position (not column)
|
286
|
+
bind_key(?/, :ask_search)
|
287
|
+
bind_key(?n, :find_more)
|
288
|
+
bind_key([?\C-x, ?>], :scroll_right)
|
289
|
+
bind_key([?\C-x, ?<], :scroll_left)
|
290
|
+
bind_key(?\M-l, :scroll_right)
|
291
|
+
bind_key(?\M-h, :scroll_left)
|
292
|
+
#bind_key([?\C-x, ?\C-s], :saveas)
|
293
|
+
#bind_key(?r) { getstr("Enter a word: ") }
|
294
|
+
#bind_key(?m, :disp_menu)
|
295
|
+
end
|
296
|
+
|
297
|
+
# goto first line of file
|
298
|
+
def goto_start
|
299
|
+
@oldindex = @current_index
|
300
|
+
@current_index = @ccol = 0
|
301
|
+
@pcol = @prow = 0
|
302
|
+
end
|
303
|
+
|
304
|
+
# goto last line of file
|
305
|
+
def goto_end
|
306
|
+
@oldindex = @current_index
|
307
|
+
@current_index = @content_rows-1
|
308
|
+
@prow = @current_index - @scrollatrows
|
309
|
+
end
|
310
|
+
|
311
|
+
# move down a line mimicking vim's j key
|
312
|
+
# @param [int] multiplier entered prior to invoking key
|
313
|
+
def down num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
|
314
|
+
@oldindex = @current_index if num > 10
|
315
|
+
@current_index += num
|
316
|
+
unless is_visible? @current_index
|
317
|
+
if @current_index > @scrollatrows
|
318
|
+
@prow += 1
|
319
|
+
end
|
320
|
+
end
|
321
|
+
$multiplier = 0
|
322
|
+
end
|
323
|
+
|
324
|
+
# move up a line mimicking vim's k key
|
325
|
+
# @param [int] multiplier entered prior to invoking key
|
326
|
+
def up num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
|
327
|
+
@oldindex = @current_index if num > 10
|
328
|
+
@current_index -= num
|
329
|
+
unless is_visible? @current_index
|
330
|
+
if @prow > @current_index
|
331
|
+
$status_message.value = "1 #{@prow} > #{@current_index} "
|
332
|
+
@prow -= 1
|
333
|
+
else
|
334
|
+
end
|
335
|
+
end
|
336
|
+
$multiplier = 0
|
337
|
+
end
|
338
|
+
|
339
|
+
# scrolls window down mimicking vim C-e
|
340
|
+
# @param [int] multiplier entered prior to invoking key
|
341
|
+
def scroll_window_down num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
|
342
|
+
@prow += num
|
343
|
+
if @prow > @current_index
|
344
|
+
@current_index += 1
|
345
|
+
end
|
346
|
+
#check_prow
|
347
|
+
$multiplier = 0
|
348
|
+
end
|
349
|
+
|
350
|
+
# scrolls window up mimicking vim C-y
|
351
|
+
# @param [int] multiplier entered prior to invoking key
|
352
|
+
def scroll_window_up num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
|
353
|
+
@prow -= num
|
354
|
+
unless is_visible? @current_index
|
355
|
+
# one more check may be needed here TODO
|
356
|
+
@current_index -= num
|
357
|
+
end
|
358
|
+
$multiplier = 0
|
359
|
+
end
|
360
|
+
|
361
|
+
# scrolls lines a window full at a time, on pressing ENTER or C-d or pagedown
|
362
|
+
def scroll_forward
|
363
|
+
@oldindex = @current_index
|
364
|
+
@current_index += @scrollatrows
|
365
|
+
@prow = @current_index - @scrollatrows
|
366
|
+
end
|
367
|
+
|
368
|
+
# scrolls lines backward a window full at a time, on pressing pageup
|
369
|
+
# C-u may not work since it is trapped by form earlier. Need to fix
|
370
|
+
def scroll_backward
|
371
|
+
@oldindex = @current_index
|
372
|
+
@current_index -= @scrollatrows
|
373
|
+
@prow = @current_index - @scrollatrows
|
374
|
+
end
|
375
|
+
def goto_last_position
|
376
|
+
return unless @oldindex
|
377
|
+
tmp = @current_index
|
378
|
+
@current_index = @oldindex
|
379
|
+
@oldindex = tmp
|
380
|
+
bounds_check
|
381
|
+
end
|
382
|
+
def scroll_right
|
383
|
+
if @content_cols < @cols
|
384
|
+
maxpcol = 0
|
385
|
+
else
|
386
|
+
maxpcol = @content_cols - @cols
|
387
|
+
end
|
388
|
+
@pcol += 1
|
389
|
+
@pcol = maxpcol if @pcol > maxpcol
|
390
|
+
# to prevent right from retaining earlier painted values
|
391
|
+
# padreader does not do a clear, yet works fine.
|
392
|
+
# OK it has an update_panel after padrefresh, that clears it seems.
|
393
|
+
#this clears entire window not just the pad
|
394
|
+
#FFI::NCurses.wclear(@window.get_window)
|
395
|
+
# so border and title is repainted after window clearing
|
396
|
+
#
|
397
|
+
# Next line was causing all sorts of problems when scrolling with ansi formatted text
|
398
|
+
#@repaint_all = true
|
399
|
+
end
|
400
|
+
def scroll_left
|
401
|
+
@pcol -= 1
|
402
|
+
end
|
403
|
+
#
|
404
|
+
#
|
405
|
+
#
|
406
|
+
# NOTE : if advancing pcol one has to clear the pad or something or else
|
407
|
+
# there'll be older content on the right side.
|
408
|
+
#
|
409
|
+
def handle_key ch
|
410
|
+
return :UNHANDLED unless @content
|
411
|
+
map_keys unless @mapped_keys
|
412
|
+
|
413
|
+
@maxrow = @content_rows - @rows
|
414
|
+
@maxcol = @content_cols - @cols
|
415
|
+
|
416
|
+
# need to understand the above line, can go below zero.
|
417
|
+
# all this seems to work fine in padreader.rb in util.
|
418
|
+
# somehow maxcol going to -33
|
419
|
+
@oldrow = @prow
|
420
|
+
@oldcol = @pcol
|
421
|
+
#$log.debug "XXX: PAD got #{ch} maxcol = #{@maxcol} cols=#{@cols}, maxpcol = #{maxpcol}"
|
422
|
+
begin
|
423
|
+
case ch
|
424
|
+
when key(?H)
|
425
|
+
when key(?l)
|
426
|
+
# TODO take multipler
|
427
|
+
#@pcol += 1
|
428
|
+
if @ccol < @cols
|
429
|
+
@ccol += 1
|
430
|
+
end
|
431
|
+
when key(?$)
|
432
|
+
#@pcol = @maxcol - 1
|
433
|
+
@ccol = [@content[@current_index].size, @cols].min
|
434
|
+
when key(?h)
|
435
|
+
# TODO take multipler
|
436
|
+
if @ccol > 0
|
437
|
+
@ccol -= 1
|
438
|
+
end
|
439
|
+
when key(?0)
|
440
|
+
@ccol = 0
|
441
|
+
when ?0.getbyte(0)..?9.getbyte(0)
|
442
|
+
if ch == ?0.getbyte(0) && $multiplier == 0
|
443
|
+
# copy of C-a - start of line
|
444
|
+
@repaint_required = true if @pcol > 0 # tried other things but did not work
|
445
|
+
@pcol = 0
|
446
|
+
return 0
|
447
|
+
end
|
448
|
+
# storing digits entered so we can multiply motion actions
|
449
|
+
$multiplier *= 10 ; $multiplier += (ch-48)
|
450
|
+
return 0
|
451
|
+
when ?\C-c.getbyte(0)
|
452
|
+
$multiplier = 0
|
453
|
+
return 0
|
454
|
+
else
|
455
|
+
# check for bindings, these cannot override above keys since placed at end
|
456
|
+
begin
|
457
|
+
ret = process_key ch, self
|
458
|
+
## If i press C-x > i get an alert from rwidgets which blacks the screen
|
459
|
+
# if i put a padrefresh here it becomes okay but only for one pad,
|
460
|
+
# i still need to do it for all pads.
|
461
|
+
rescue => err
|
462
|
+
$log.error " TEXTPAD ERROR INS #{err} "
|
463
|
+
$log.debug(err.backtrace.join("\n"))
|
464
|
+
textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception"
|
465
|
+
# FIXME why does this result in a blank spot on screen till its refreshed again
|
466
|
+
# should not happen if its deleting its panel and doing an update panel
|
467
|
+
end
|
468
|
+
return :UNHANDLED if ret == :UNHANDLED
|
469
|
+
end
|
470
|
+
bounds_check
|
471
|
+
rescue => err
|
472
|
+
$log.error " TEXTPAD ERROR 111 #{err} "
|
473
|
+
$log.debug( err) if err
|
474
|
+
$log.debug(err.backtrace.join("\n")) if err
|
475
|
+
textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception"
|
476
|
+
$error_message.value = ""
|
477
|
+
ensure
|
478
|
+
padrefresh
|
479
|
+
Ncurses::Panel.update_panels
|
480
|
+
end
|
481
|
+
return 0
|
482
|
+
end # while loop
|
483
|
+
|
484
|
+
# destroy the pad, this needs to be called from somewhere, like when the app
|
485
|
+
# closes or the current window closes , or else we could have a seg fault
|
486
|
+
# or some ugliness on the screen below this one (if nested).
|
487
|
+
|
488
|
+
# Now since we use get_pad from window, upon the window being destroyed,
|
489
|
+
# it will call this. Else it will destroy pad
|
490
|
+
def destroy
|
491
|
+
FFI::NCurses.delwin(@pad) if @pad # when do i do this ? FIXME
|
492
|
+
@pad = nil
|
493
|
+
end
|
494
|
+
def is_visible? index
|
495
|
+
j = index - @prow #@toprow
|
496
|
+
j >= 0 && j <= @scrollatrows
|
497
|
+
end
|
498
|
+
def on_enter
|
499
|
+
set_form_row
|
500
|
+
end
|
501
|
+
def set_form_row
|
502
|
+
setrowcol @lastrow, @lastcol
|
503
|
+
end
|
504
|
+
def set_form_col
|
505
|
+
end
|
506
|
+
|
507
|
+
private
|
508
|
+
|
509
|
+
# check that current_index and prow are within correct ranges
|
510
|
+
# sets row (and someday col too)
|
511
|
+
# sets repaint_required
|
512
|
+
|
513
|
+
def bounds_check
|
514
|
+
r,c = rowcol
|
515
|
+
@current_index = 0 if @current_index < 0
|
516
|
+
@current_index = @content_rows-1 if @current_index > @content_rows-1
|
517
|
+
$status_message.value = "visible #{@prow} , #{@current_index} "
|
518
|
+
unless is_visible? @current_index
|
519
|
+
if @prow > @current_index
|
520
|
+
$status_message.value = "1 #{@prow} > #{@current_index} "
|
521
|
+
@prow -= 1
|
522
|
+
else
|
523
|
+
end
|
524
|
+
end
|
525
|
+
#end
|
526
|
+
check_prow
|
527
|
+
#$log.debug "XXX: PAD BOUNDS ci:#{@current_index} , old #{@oldrow},pr #{@prow}, max #{@maxrow} pcol #{@pcol} maxcol #{@maxcol}"
|
528
|
+
@crow = @current_index + r - @prow
|
529
|
+
@crow = r if @crow < r
|
530
|
+
# 2 depends on whetehr suppressborders
|
531
|
+
@crow = @row + @height -2 if @crow >= r + @height -2
|
532
|
+
setrowcol @crow, @ccol+c
|
533
|
+
lastcurpos @crow, @ccol+c
|
534
|
+
if @oldrow != @prow || @oldcol != @pcol
|
535
|
+
@repaint_required = true
|
536
|
+
end
|
537
|
+
end
|
538
|
+
def lastcurpos r,c
|
539
|
+
@lastrow = r
|
540
|
+
@lastcol = c
|
541
|
+
end
|
542
|
+
|
543
|
+
|
544
|
+
# check that prow and pcol are within bounds
|
545
|
+
|
546
|
+
def check_prow
|
547
|
+
@prow = 0 if @prow < 0
|
548
|
+
if @prow > @maxrow-1
|
549
|
+
@prow = @maxrow-1
|
550
|
+
end
|
551
|
+
if @pcol > @maxcol-1
|
552
|
+
@pcol = @maxcol-1
|
553
|
+
end
|
554
|
+
@pcol = 0 if @pcol < 0
|
555
|
+
end
|
556
|
+
public
|
557
|
+
##
|
558
|
+
# Ask user for string to search for
|
559
|
+
def ask_search
|
560
|
+
str = get_string("Enter pattern: ")
|
561
|
+
return if str.nil?
|
562
|
+
str = @last_regex if str == ""
|
563
|
+
return if str == ""
|
564
|
+
ix = next_match str
|
565
|
+
return unless ix
|
566
|
+
@last_regex = str
|
567
|
+
|
568
|
+
@oldindex = @current_index
|
569
|
+
@current_index = ix[0]
|
570
|
+
@ccol = ix[1]
|
571
|
+
ensure_visible
|
572
|
+
end
|
573
|
+
##
|
574
|
+
# Find next matching row for string accepted in ask_search
|
575
|
+
#
|
576
|
+
def find_more
|
577
|
+
return unless @last_regex
|
578
|
+
ix = next_match @last_regex
|
579
|
+
return unless ix
|
580
|
+
@oldindex = @current_index
|
581
|
+
@current_index = ix[0]
|
582
|
+
@ccol = ix[1]
|
583
|
+
ensure_visible
|
584
|
+
end
|
585
|
+
|
586
|
+
##
|
587
|
+
# Find the next row that contains given string
|
588
|
+
# @return row and col offset of match, or nil
|
589
|
+
# @param String to find
|
590
|
+
def next_match str
|
591
|
+
first = nil
|
592
|
+
## content can be string or Chunkline, so we had to write <tt>index</tt> for this.
|
593
|
+
## =~ does not give an error, but it does not work.
|
594
|
+
@content.each_with_index do |line, ix|
|
595
|
+
col = line.index str
|
596
|
+
if col
|
597
|
+
first ||= [ ix, col ]
|
598
|
+
if ix > @current_index
|
599
|
+
return [ix, col]
|
600
|
+
end
|
601
|
+
end
|
602
|
+
end
|
603
|
+
return first
|
604
|
+
end
|
605
|
+
##
|
606
|
+
# Ensure current row is visible, if not make it first row
|
607
|
+
# TODO - need to check if its at end and then reduce scroll at rows,
|
608
|
+
# @param current_index (default if not given)
|
609
|
+
#
|
610
|
+
def ensure_visible row = @current_line
|
611
|
+
unless is_visible? row
|
612
|
+
@prow = @current_index
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
616
|
+
end # class textpad
|
617
|
+
|
618
|
+
# a test renderer to see how things go
|
619
|
+
class DefaultRubyRenderer
|
620
|
+
def render pad, lineno, text
|
621
|
+
bg = :black
|
622
|
+
fg = :white
|
623
|
+
att = NORMAL
|
624
|
+
cp = $datacolor
|
625
|
+
if text =~ /^\s*# /
|
626
|
+
fg = :red
|
627
|
+
cp = get_color($datacolor, fg, bg)
|
628
|
+
elsif text =~ /^\s*#/
|
629
|
+
fg = :blue
|
630
|
+
cp = get_color($datacolor, fg, bg)
|
631
|
+
elsif text =~ /^\s*class /
|
632
|
+
fg = :magenta
|
633
|
+
cp = get_color($datacolor, fg, bg)
|
634
|
+
elsif text =~ /^\s*def /
|
635
|
+
fg = :yellow
|
636
|
+
att = BOLD
|
637
|
+
cp = get_color($datacolor, fg, bg)
|
638
|
+
elsif text =~ /^\s*(begin|rescue|ensure|end)/
|
639
|
+
fg = :magenta
|
640
|
+
att = BOLD
|
641
|
+
cp = get_color($datacolor, fg, bg)
|
642
|
+
end
|
643
|
+
FFI::NCurses.wattron(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
|
644
|
+
FFI::NCurses.mvwaddstr(pad, lineno, 0, text)
|
645
|
+
FFI::NCurses.wattroff(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
|
646
|
+
|
647
|
+
end
|
648
|
+
end
|
649
|
+
end
|
650
|
+
## Since we already have an ansi sequence parser, why not convert to that and use that.
|
651
|
+
#
|
652
|
+
# man pages have some kind of sucky format probably related to sme ancient hardware.
|
653
|
+
# I notice two patterns:
|
654
|
+
# 1. a character is followed by a ^H and then the same character repeated.
|
655
|
+
# Such a char is to be printed in one color. Oh it get it, LOL, ^H is a backspace
|
656
|
+
# so basically the printer is giogn back and printing that char again. So its printed
|
657
|
+
# two times, aka bold.
|
658
|
+
# 2. The second is an underline folowed by BS and then any character, this goes in another
|
659
|
+
# color and is obviously meant to be underlined text. NOW it get it, bird-brained me!
|
660
|
+
#
|
661
|
+
#
|
662
|
+
def convert_man_to_ansi file
|
663
|
+
lines = file.split "\n"
|
664
|
+
l = nil
|
665
|
+
lines.each_with_index do |line, ix|
|
666
|
+
# convert underlined words to yellow or one color, these are usually params
|
667
|
+
line.gsub!(/((_[^ ])+)/,'[4;33m\1[0m')
|
668
|
+
line.gsub!(/_/,'')
|
669
|
+
# convert bold words to red or one color, these are usually headers and other words
|
670
|
+
l= line.gsub(/(([^ ][^ ])+)/,'[1;31m\1[0m').gsub(/[^ ]/,'').gsub(//,'')
|
671
|
+
# ==
|
672
|
+
#line.gsub!(/((_[^ ])+)/,'_\1_')
|
673
|
+
#line.gsub!(/_/,'')
|
674
|
+
### convert bold words to red or one color, these are usually headers and other words
|
675
|
+
#l= line.gsub(/(([^ ][^ ])+)/,'*\1*').gsub(/[^ ]/,'').gsub(//,'')
|
676
|
+
# ==
|
677
|
+
lines[ix] = l
|
678
|
+
end
|
679
|
+
lines
|
680
|
+
end
|
681
|
+
def ask_program
|
682
|
+
p = @form.by_name["textpad"];
|
683
|
+
prog = get_string("Program to man:")
|
684
|
+
p.padrefresh
|
685
|
+
return unless prog
|
686
|
+
return if prog == ""
|
687
|
+
file = `man #{prog} 2>&1`
|
688
|
+
return unless file
|
689
|
+
text = convert_man_to_ansi(file)
|
690
|
+
p.formatted_text(text, :ansi)
|
691
|
+
p.goto_start
|
692
|
+
end
|
693
|
+
if __FILE__ == $PROGRAM_NAME
|
694
|
+
require 'rbcurse/core/util/app'
|
695
|
+
App.new do
|
696
|
+
@form.bind_key(?\M-c, "Ask program name: ") { ask_program }
|
697
|
+
@form.bind_key(?q, "quit: ") { throw :close }
|
698
|
+
single = true
|
699
|
+
w = 50
|
700
|
+
w2 = FFI::NCurses.COLS-w-1
|
701
|
+
if single
|
702
|
+
w = FFI::NCurses.COLS-1
|
703
|
+
## no matter what you do, man's output gets wrapped to 80 cols if not going to a terminal.
|
704
|
+
end
|
705
|
+
## create two side by side pads on for ansi and one for ruby
|
706
|
+
p = RubyCurses::TextPad.new @form, :height => FFI::NCurses.LINES-1, :width => w, :row => 0, :col => 0 , :title => " mancurses ", :name => "textpad"
|
707
|
+
#fn = "m.m"
|
708
|
+
#text = File.open(fn,"r").readlines
|
709
|
+
file = `man man`
|
710
|
+
text = convert_man_to_ansi(file)
|
711
|
+
#File.open("t.t", 'w') { |file| file.write(text.join "\n") }
|
712
|
+
p.formatted_text(text, :ansi)
|
713
|
+
#p.text(text)
|
714
|
+
if !single
|
715
|
+
RubyCurses::TextPad.new @form, :filename => "bin/mancurses", :height => FFI::NCurses.LINES, :width => w2, :row => 0, :col => w+1 , :title => " ruby "
|
716
|
+
end
|
717
|
+
#throw :close
|
718
|
+
@status_line = status_line :row => Ncurses.LINES-1
|
719
|
+
|
720
|
+
@status_line.command {
|
721
|
+
"q Quit | ? Keys | M-c Program | "
|
722
|
+
}
|
723
|
+
end
|
724
|
+
end
|
data/mancurses.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "mancurses"
|
7
|
+
spec.version = "0.0.1"
|
8
|
+
spec.authors = ["Rahul Kumar"]
|
9
|
+
spec.email = ["sentinel1879@gmail.com"]
|
10
|
+
spec.description = %q{view manpages in an ncurses window and navigate with vim bindings}
|
11
|
+
spec.summary = %q{view manpages in an ncurses window and navigate with vim bindings and much much moah}
|
12
|
+
spec.homepage = "https://github.com/rkumar/mancurses"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
|
18
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
19
|
+
spec.add_development_dependency "rake"
|
20
|
+
spec.add_dependency "rbcurse-core"
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mancurses
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rahul Kumar
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rbcurse-core
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: view manpages in an ncurses window and navigate with vim bindings
|
63
|
+
email:
|
64
|
+
- sentinel1879@gmail.com
|
65
|
+
executables:
|
66
|
+
- mancurses
|
67
|
+
extensions: []
|
68
|
+
extra_rdoc_files: []
|
69
|
+
files:
|
70
|
+
- .gitignore
|
71
|
+
- Gemfile
|
72
|
+
- LICENSE.txt
|
73
|
+
- README.md
|
74
|
+
- Rakefile
|
75
|
+
- bin/mancurses
|
76
|
+
- mancurses.gemspec
|
77
|
+
homepage: https://github.com/rkumar/mancurses
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
hash: -1947742288534939193
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
hash: -1947742288534939193
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.8.25
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: view manpages in an ncurses window and navigate with vim bindings and much
|
108
|
+
much moah
|
109
|
+
test_files: []
|