jota 0.8.1 → 0.9.0dev2

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.
@@ -0,0 +1,603 @@
1
+
2
+ # $Id: jota_curses.rb 257 2010-05-02 19:47:26Z dz $
3
+
4
+ require "curses"
5
+ require 'tempfile'
6
+
7
+ require "clip_array"
8
+ require "version"
9
+
10
+ class JotaCurses
11
+
12
+ # instance variables
13
+ # @lines size of terminal window
14
+ # @columns
15
+
16
+ # @mode show what kind of data in the bottom window
17
+
18
+ # @top_win_height height of top window in lines
19
+ # (the corresponding bot_win_height is a function out of
20
+ # @lines and @top_win_height)
21
+
22
+ # @top_win_first first line in top window
23
+ # @bot_win_first first line in bottom window
24
+
25
+ # @top_win_lines drawn lines in top window
26
+ # @bot_win_lines drawn lines in bottom window
27
+
28
+ # @top_bar the windows
29
+ # @top_win
30
+ # @mid_bar
31
+ # @bot_win
32
+ # @bot_bar
33
+
34
+ # @editor_running
35
+
36
+ def set_terminal
37
+ Curses.close_screen
38
+ Curses.init_screen
39
+ Curses.stdscr.refresh
40
+ @columns = Curses.stdscr.maxx
41
+ @lines = Curses.stdscr.maxy
42
+ print_debug "setting terminal: lines=#{@lines}, columns=#{@columns}"
43
+ end
44
+
45
+ def initialize
46
+ @data = nil
47
+
48
+ @top_win_first = 0 # number of top clip in top window
49
+ @bot_win_first = 0 # number of top line in bottom window
50
+
51
+ @top_bar = nil
52
+ @top_win = nil
53
+ @mid_bar = nil
54
+ @bot_win = nil
55
+ @bot_bar = nil
56
+
57
+ @mode = :normal
58
+
59
+ @editor_running = false
60
+
61
+ Curses.init_screen
62
+ Curses.cbreak
63
+ Curses.noecho
64
+ Curses.nonl
65
+
66
+ set_terminal
67
+ @top_win_height = @lines/2
68
+ set_windows
69
+
70
+ begin
71
+ # set SIGgnal handler if terminal WINdow CHanged
72
+ Signal.trap("SIGWINCH") do
73
+ print_debug "terminal size changed"
74
+ set_terminal
75
+ # ruby 1.9 can't cope with curses initializing and closing in fast succession
76
+ # and the "sleep" makes fast resizing smoother
77
+ sleep 0.1
78
+ if not @editor_running then
79
+ set_windows
80
+ end
81
+ end
82
+ rescue ArgumentError
83
+ # for example with Windows
84
+ print_verbose "Cannot set trap for 'SIGWINCH'"
85
+ end
86
+ end
87
+
88
+ def open(filename)
89
+ begin
90
+ new_data = ClipArray.open(filename)
91
+ rescue AppError, SystemCallError => msg
92
+ error(msg)
93
+ return nil
94
+ end
95
+
96
+ if @data then
97
+ @data.close
98
+ end
99
+ @data = new_data
100
+ @data.current_index = 0
101
+ @top_win_first = 0
102
+ draw_bars
103
+ return @data
104
+ end
105
+
106
+ def clip_array
107
+ return @data
108
+ end
109
+
110
+ def create(filename)
111
+ begin
112
+ new_data = ClipArray.create(filename)
113
+ rescue AppError, SystemCallError => msg
114
+ error(msg)
115
+ return
116
+ end
117
+
118
+ if @data then
119
+ @data.close
120
+ end
121
+ @data = new_data
122
+ @data.current_index = 0
123
+ @top_win_first = 0
124
+ draw_bars
125
+ end
126
+
127
+
128
+ def delete
129
+ return if not @data
130
+
131
+ return if not really?("confirm_delete","Really delete?")
132
+
133
+ if @data.pref["deletesave_enable"] and not @data.current.empty? then
134
+ deletefile = expand_filename(@data.pref["deletesave_file"],
135
+ @data.dirname, @data.filename)
136
+ begin
137
+ @data.append(deletefile)
138
+ rescue SystemCallError => msg
139
+ error(msg)
140
+ return
141
+ end
142
+ end
143
+
144
+ @data.delete
145
+ end
146
+
147
+ def run
148
+ draw_bars
149
+ adjust_top_win_first
150
+ adjust_bot_win_first
151
+ loop do
152
+ draw_top_window
153
+ @bot_win_lines = draw_bottom_window
154
+ handle_key
155
+ end
156
+ end
157
+
158
+
159
+
160
+ #
161
+ # Helper
162
+ #
163
+
164
+ def get_bot_win_height
165
+ rows = @lines - @top_win_height - 3
166
+
167
+ # some error checking if the terminal window is made too small
168
+ if rows < 3 then
169
+ # first try to adjust the windows
170
+ @top_win_height = @lines - 3 - 4
171
+ if @top_win_height < 2 then
172
+ # if it doesn't help, display an error
173
+ @top_win_height = 2
174
+ @top_bar.clear
175
+ @top_bar.addstr("TOO SMALL")
176
+ @top_bar.refresh
177
+
178
+ @top_win.clear; @top_win.refresh
179
+ @mid_bar.clear; @mid_bar.refresh
180
+ @bot_win.clear; @bot_win.refresh
181
+ @bot_bar.clear; @bot_bar.refresh
182
+ end
183
+ return 4
184
+ else
185
+ return rows
186
+ end
187
+ end
188
+
189
+ private
190
+
191
+ #
192
+ # Draw the window content
193
+ #
194
+
195
+ def draw_bars
196
+ @top_bar.clear
197
+ @mid_bar.clear
198
+ @bot_bar.clear
199
+
200
+ @top_bar.addstr("Jota #{Version::STRING}")
201
+ if @data then
202
+ @top_bar.addstr(" - ")
203
+ @top_bar.addstr(@data.filename)
204
+ end
205
+ @top_bar.refresh
206
+
207
+ @mid_bar.clear
208
+ @mid_bar.addstr("="*@columns)
209
+ @mid_bar.refresh
210
+
211
+ @bot_bar.clear
212
+ case @mode
213
+ when :normal
214
+ @bot_bar.addstr("Crsr=le/ri/up/dn, e=edit, n=new, d=del, o=open, +/-=resize win, h=help, q=quit")
215
+ when :help
216
+ @bot_bar.addstr("Crsr=up/dn, +/-=resize win, h/q=quit help")
217
+
218
+ when :pref
219
+ @bot_bar.addstr("Crsr=up/dn, e=edit pref, +/-=resize win, p/q=quit pref")
220
+ else
221
+ @bot_bar.addstr("h=help, q=quit")
222
+ end
223
+ @bot_bar.refresh
224
+ end
225
+
226
+ def draw_top_window
227
+ @top_win.clear
228
+ if @data then
229
+ @top_win_first.upto @top_win_first+@top_win_height-1 do | i |
230
+ if i == @data.current_index then
231
+ marker = ">"
232
+ else
233
+ marker = " "
234
+ end
235
+ if i < @data.length then
236
+ @top_win.addstr("%s %s\n" %
237
+ [marker,@data[i].title![0..@columns-4]])
238
+ end
239
+ end
240
+ end
241
+ @top_win.refresh
242
+ end
243
+
244
+ def draw_bottom_window
245
+ print_debug "drawing text in #{@mode} mode"
246
+ case @mode
247
+ when :normal
248
+ if @data then
249
+ text = @data.current.text
250
+ else
251
+ text = "\nHit 'o' to open or 'c' to create a file\n"
252
+ end
253
+ when :help
254
+ text = help_text
255
+ when :pref
256
+ if @data then
257
+ text = @data.pref.write
258
+ else
259
+ text = "\nNo file loaded, so no preferences are available\n"
260
+ end
261
+ else
262
+ text = "unknown mode #{@mode}"
263
+ end
264
+
265
+ @bot_win.clear
266
+ arr = text.split(/\n/)
267
+ @bot_win_first.upto @bot_win_first+self.get_bot_win_height-1 do | i |
268
+ str = arr[i] || ""
269
+ # expand tabs to spaces
270
+ str.gsub!(/([^\t]{8})|([^\t]*)\t/n){[$+].pack("A8")}
271
+ @bot_win.addstr(str[0..@columns-2])
272
+ @bot_win.addstr("\n")
273
+ end
274
+ result = arr.length
275
+ @bot_win.refresh
276
+
277
+ return result
278
+ end
279
+
280
+ def help_text
281
+ return <<-EOT
282
+ Jota #{Version::STRING}, (C) #{Version::YEARS} #{Version::AUTHOR} <#{Version::AUTHOR_EMAIL}>
283
+
284
+ Keys:
285
+ Cursor up/down Scroll up/down
286
+ Cursor left/right Show previous/next clip
287
+ e, ENTER Edit current clip (with $EDITOR)
288
+ n Append new clip
289
+ d Delete current clip
290
+
291
+ o Open file
292
+ c Create new file
293
+ C Close file
294
+
295
+ + Grow top window
296
+ - Shrink top window
297
+ p Preferences
298
+ ! Execute command (default $SHELL)
299
+ q Quit
300
+
301
+ Environment:
302
+ EDITOR #{ENV["EDITOR"]}
303
+ SHELL #{ENV["SHELL"]}
304
+ EOT
305
+ end
306
+
307
+ def redraw_all
308
+ print_debug "redrawing"
309
+ draw_bars
310
+ draw_top_window
311
+ @bot_win_lines = draw_bottom_window
312
+ end
313
+
314
+ def set_windows
315
+ bot_win_height = get_bot_win_height
316
+
317
+ print_debug "setting windows: top_height=#{@top_win_height}, "+
318
+ "bottom_height=#{bot_win_height}, width=#{@columns}"
319
+
320
+
321
+ h = 0
322
+ @top_bar.close if @top_bar
323
+ @top_bar = Curses::Window.new(1,@columns,h,0)
324
+
325
+ h += 1
326
+ @top_win.close if @top_win
327
+ @top_win = Curses::Window.new(@top_win_height,@columns,h,0)
328
+
329
+ h += @top_win_height
330
+ @mid_bar.close if @mid_bar
331
+ @mid_bar = Curses::Window.new(1,@columns,h,0)
332
+
333
+ h += 1
334
+ @bot_win.close if @bot_win
335
+ @bot_win = Curses::Window.new(bot_win_height,@columns,h,0)
336
+
337
+ h += bot_win_height
338
+ @bot_bar.close if @bot_bar
339
+ @bot_bar = Curses::Window.new(1,@columns,h,0)
340
+
341
+ @bot_bar.keypad = true
342
+
343
+ redraw_all
344
+ end
345
+
346
+ def app_quit
347
+ if @data then
348
+ if really?("confirm_quit","Really quit?") then
349
+ @data.close
350
+ exit
351
+ end
352
+ else
353
+ exit
354
+ end
355
+ end
356
+
357
+ def call_editor(text)
358
+ Curses.close_screen
359
+ @editor_running = true
360
+ text = editor(text) # edit from helper.rb
361
+ @editor_running = false
362
+ Curses.init_screen
363
+ set_windows
364
+ return text
365
+ end
366
+
367
+ def handle_key
368
+ @bot_bar.move(0,79)
369
+ @bot_bar.refresh
370
+ # Ruby 1.8: getch return alway a Fixnum
371
+ # Ruby 1.9: getch return a String if char is printable (between " " and "~"), Fixnum otherwise
372
+ begin
373
+ ch = @bot_bar.getch
374
+ ch = 0 if ch.nil?
375
+ ch = ch.ord if ch.class == String
376
+ rescue Interrupt # ignore Ctrl-C
377
+ ch = 0
378
+ end
379
+ draw_bars # remove error message in bottom bar
380
+ if ch < 256 then
381
+ case ch.chr
382
+ when "\f" # Ctrl-L
383
+ redraw_all
384
+ when "c"
385
+ filename = prompt_filename("Create File (?=list): ")
386
+ if filename and filename != "" then
387
+ create(filename)
388
+ @mode = :normal
389
+ end
390
+ when "C"
391
+ @data.close if @data
392
+ @data = nil
393
+ @mode = :normal
394
+ draw_bars
395
+ when "d"
396
+ if @mode == :normal then
397
+ delete
398
+ else
399
+ @mode = :normal
400
+ end
401
+ when "e","\r","\n" # "e" or Enter
402
+ case @mode
403
+ when :normal
404
+ if @data then
405
+ @data.current.text = call_editor(@data.current.text)
406
+ @data.save
407
+ end
408
+ when :pref
409
+ if @data
410
+ newpref = call_editor(@data.pref.write)
411
+ @data.pref = Preferences.read(newpref)
412
+ end
413
+ end
414
+ when "h"
415
+ if @mode == :help then
416
+ @mode = :normal
417
+ else
418
+ @mode = :help
419
+ end
420
+ redraw_all
421
+ @bot_win_first = 0
422
+ when "n"
423
+ if @data then
424
+ @data.new
425
+ @mode = :normal
426
+ @data.current.text = call_editor(@data.current.text)
427
+ @data.save
428
+ end
429
+ when "o"
430
+ filename = prompt_filename("Open File (?=list): ")
431
+ if filename and filename != "" then
432
+ open(filename)
433
+ @mode = :normal
434
+ end
435
+ when "p"
436
+ if @mode == :pref then
437
+ @mode = :normal
438
+ else
439
+ @mode = :pref
440
+ end
441
+ @bot_win_first = 0
442
+ redraw_all
443
+ when "q"
444
+ if @mode == :normal then
445
+ app_quit
446
+ else
447
+ @mode = :normal
448
+ end
449
+ when "+" # grow top window
450
+ if @top_win_height + 5 <= @lines then
451
+ @top_win_height += 1
452
+ set_windows
453
+ end
454
+ when "-" # shrink top window
455
+ if @top_win_height > 2 then
456
+ @top_win_height -= 1
457
+ set_windows
458
+ end
459
+ when "!"
460
+ command = prompt("Command: ")
461
+ if command == "" then
462
+ command = ENV["SHELL"]
463
+ end
464
+
465
+ Curses.stdscr.clear
466
+ Curses.close_screen
467
+ puts "\nJota executing '#{command}'"
468
+ $stdout.flush
469
+ $stderr.flush
470
+ system(command)
471
+
472
+ puts "\nPress Enter to continue"
473
+ $stdout.flush
474
+ $stdin.gets
475
+
476
+ Curses.init_screen
477
+ redraw_all
478
+ @mode = :normal
479
+ end
480
+ else
481
+ case ch
482
+ when Curses::Key::LEFT, Curses::Key::RIGHT
483
+ if @data then
484
+ @data.current.pos = @bot_win_first
485
+ if ch == Curses::Key::LEFT then
486
+ @data.prev
487
+ else
488
+ @data.next
489
+ end
490
+ if @data.current.pos then
491
+ @bot_win_first = @data.current.pos
492
+ else
493
+ @bot_win_first = 0
494
+ end
495
+ @mode = :normal
496
+ end
497
+ when Curses::Key::UP, Curses::Key::DOWN
498
+ if @data then
499
+ if ch == Curses::Key::UP then
500
+ @bot_win_first -= 1
501
+ else
502
+ @bot_win_first += 1
503
+ end
504
+ end
505
+ when Curses::Key::HOME
506
+ if @data then
507
+ @bot_win_first = 0
508
+ end
509
+ end
510
+ end
511
+
512
+ adjust_top_win_first
513
+ adjust_bot_win_first
514
+ end
515
+
516
+ def adjust_top_win_first
517
+ return if not @data
518
+ if @data.current_index < @top_win_first then
519
+ @top_win_first = @data.current_index
520
+ elsif @data.current_index > @top_win_first+@top_win_height-1 then
521
+ @top_win_first = @data.current_index-@top_win_height+1
522
+ end
523
+ end
524
+
525
+ def adjust_bot_win_first
526
+ return if not @data
527
+ if @bot_win_first < 0 then
528
+ @bot_win_first = 0
529
+ elsif @bot_win_lines and @bot_win_first > @bot_win_lines-1 then
530
+ @bot_win_first = @bot_win_lines-1
531
+ end
532
+ end
533
+
534
+ def message(str)
535
+ @bot_bar.clear
536
+ @bot_bar.addstr(str)
537
+ @bot_bar.refresh
538
+ end
539
+
540
+ def prompt(str,type=:string)
541
+ @bot_bar.clear
542
+ @bot_bar.addstr(str)
543
+ @bot_bar.refresh
544
+ Curses.echo
545
+ begin
546
+ case type
547
+ when :string
548
+ result = @bot_bar.getstr
549
+ when :char
550
+ result = @bot_bar.getch.chr
551
+ else
552
+ raise "Fallthru in JotaCurses#prompt"
553
+ end
554
+ rescue Interrupt # ignore Ctrl-C
555
+ return ""
556
+ end
557
+
558
+ Curses.noecho
559
+ draw_bars
560
+ return result
561
+ end
562
+
563
+ def prompt_filename(str)
564
+ loop do
565
+ filename = prompt(str)
566
+ if filename == "?" then
567
+ @bot_win.clear
568
+ Dir.foreach(".") do | f |
569
+ @bot_win.addstr(f+"\t") if f !~ /^\./
570
+ end
571
+ @bot_win.refresh
572
+ else
573
+ return filename
574
+ end
575
+ end
576
+ end
577
+
578
+ def error(msg)
579
+ @bot_bar.clear
580
+ @bot_bar.addstr("ERROR: ")
581
+ @bot_bar.addstr(msg.to_s)
582
+ @bot_bar.refresh
583
+ end
584
+
585
+ def really?(prefstr,promptstr)
586
+ if @data.nil? then
587
+ return true
588
+ end
589
+
590
+ ask = @data.pref[prefstr]
591
+ if not ask then
592
+ return true
593
+ end
594
+
595
+ ch = prompt("#{promptstr} [y,N] ",:char)
596
+ if ch.downcase == "y" then
597
+ return true
598
+ else
599
+ return false
600
+ end
601
+ end
602
+
603
+ end # class
data/lib/preferences.rb CHANGED
@@ -1,7 +1,5 @@
1
1
 
2
- # $Id: preferences.rb 46 2009-05-08 09:27:55Z dz $
3
-
4
- # if you use vim and don't like folds type zR
2
+ # $Id: preferences.rb 249 2010-04-14 15:56:22Z dz $
5
3
 
6
4
  require 'yaml'
7
5
  require 'version'
@@ -10,12 +8,10 @@ class Preferences < Hash
10
8
 
11
9
  # synonym for 'new'
12
10
  def Preferences.defaults
13
- #{{{1
14
11
  return Preferences.new
15
- end #}}}1
12
+ end
16
13
 
17
14
  def Preferences.read(str)
18
- #{{{1
19
15
  p = Preferences.new
20
16
 
21
17
  begin
@@ -29,42 +25,38 @@ def Preferences.read(str)
29
25
  end
30
26
 
31
27
  return p
32
- end #}}}1
28
+ end
33
29
 
34
30
  def write
35
- #{{{1
36
31
  print_debug "writing preferences to string"
37
32
  return YAML.dump(self)
38
- end #}}}1
33
+ end
39
34
 
40
35
  def initialize
41
- #{{{1
42
36
  set_defaults
43
- end #}}}1
37
+ end
44
38
 
45
39
  def [](key)
46
- #{{{1
47
40
  if has_key?(key) then
48
41
  return super(key)
49
42
  else
50
43
  raise "'#{key}' is not a valid preferences key"
51
44
  end
52
- end #}}}1
45
+ end
53
46
 
54
47
  #def []=(key,value)
55
- ##{{{1
48
+ #
56
49
  # if has_key?(key) then
57
50
  # return super(key)
58
51
  # else
59
52
  # raise "'#{key}' is not a valid preferences key"
60
53
  # end
61
- #end #}}}1
54
+ #end
62
55
 
63
56
 
64
57
  private
65
58
 
66
59
  def set_defaults
67
- #{{{1
68
60
  clear
69
61
  self["foreground"] = [65535, 65535, 65535]
70
62
  self["background"] = [20316, 20316, 20316]
@@ -76,8 +68,6 @@ def set_defaults
76
68
  self["confirm_quit"] = false
77
69
  self["autosave_enable"] = true
78
70
  self["autosave_seconds"] = 600
79
- end #}}}1
71
+ end
80
72
 
81
73
  end # class
82
-
83
- # vim: set foldmethod=marker: