diakonos 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ proc = Proc.new do |buffer|
2
+ filename = buffer.name
3
+ $diakonos.cursorBOF
4
+ end
5
+
6
+ $diakonos.registerProc( proc, :after_save )
@@ -0,0 +1,2397 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # == Diakonos
4
+ #
5
+ # A usable console text editor.
6
+ # :title: Diakonos
7
+ #
8
+ # Author:: Pistos (irc.freenode.net)
9
+ # http://purepistos.net/diakonos
10
+ #
11
+ # This software is released under the GNU General Public License.
12
+ # http://www.gnu.org/copyleft/gpl.html
13
+ #
14
+ # This program is free software; you can redistribute it and/or
15
+ # modify it under the terms of the GNU General Public License
16
+ # as published by the Free Software Foundation; either version 2
17
+ # of the License, or (at your option) any later version.
18
+
19
+ # This program is distributed in the hope that it will be useful,
20
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
21
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
+ # GNU General Public License for more details.
23
+
24
+ require 'curses'
25
+ require 'open3'
26
+ require 'thread'
27
+ require 'English'
28
+ require 'set'
29
+
30
+ require 'diakonos/object'
31
+ require 'diakonos/enumerable'
32
+ require 'diakonos/regexp'
33
+ require 'diakonos/sized-array'
34
+ require 'diakonos/hash'
35
+ require 'diakonos/buffer-hash'
36
+ require 'diakonos/array'
37
+ require 'diakonos/string'
38
+ require 'diakonos/keycode'
39
+ require 'diakonos/fixnum'
40
+ require 'diakonos/bignum'
41
+ require 'diakonos/text-mark'
42
+ require 'diakonos/bookmark'
43
+ require 'diakonos/ctag'
44
+ require 'diakonos/finding'
45
+ require 'diakonos/buffer'
46
+ require 'diakonos/window'
47
+ require 'diakonos/clipboard'
48
+ require 'diakonos/readline'
49
+
50
+ #$profiling = true
51
+
52
+ #if $profiling
53
+ #require 'ruby-prof'
54
+ #end
55
+
56
+ module Diakonos
57
+
58
+ VERSION = '0.8.3'
59
+ LAST_MODIFIED = 'February 7, 2007'
60
+
61
+ DONT_ADJUST_ROW = false
62
+ ADJUST_ROW = true
63
+ PROMPT_OVERWRITE = true
64
+ DONT_PROMPT_OVERWRITE = false
65
+ DO_REDRAW = true
66
+ DONT_REDRAW = false
67
+
68
+ TAB = 9
69
+ ENTER = 13
70
+ ESCAPE = 27
71
+ BACKSPACE = 127
72
+ CTRL_C = 3
73
+ CTRL_D = 4
74
+ CTRL_K = 11
75
+ CTRL_Q = 17
76
+ CTRL_H = 263
77
+ RESIZE2 = 4294967295
78
+
79
+ DEFAULT_TAB_SIZE = 8
80
+
81
+ CHOICE_NO = 0
82
+ CHOICE_YES = 1
83
+ CHOICE_ALL = 2
84
+ CHOICE_CANCEL = 3
85
+ CHOICE_YES_TO_ALL = 4
86
+ CHOICE_NO_TO_ALL = 5
87
+ CHOICE_KEYS = [
88
+ [ ?n, ?N ],
89
+ [ ?y, ?Y ],
90
+ [ ?a, ?A ],
91
+ [ ?c, ?C, ESCAPE, CTRL_C, CTRL_D, CTRL_Q ],
92
+ [ ?e ],
93
+ [ ?o ]
94
+ ]
95
+ CHOICE_STRINGS = [ '(n)o', '(y)es', '(a)ll', '(c)ancel', 'y(e)s to all', 'n(o) to all' ]
96
+
97
+ BOL_ZERO = 0
98
+ BOL_FIRST_CHAR = 1
99
+ BOL_ALT_ZERO = 2
100
+ BOL_ALT_FIRST_CHAR = 3
101
+
102
+ FORCE_REVERT = true
103
+ ASK_REVERT = false
104
+
105
+ ASK_REPLACEMENT = true
106
+
107
+ CASE_SENSITIVE = true
108
+ CASE_INSENSITIVE = false
109
+
110
+ FUNCTIONS = [
111
+ 'addNamedBookmark',
112
+ 'anchorSelection',
113
+ 'backspace',
114
+ 'carriageReturn',
115
+ 'changeSessionSetting',
116
+ 'clearMatches',
117
+ 'closeFile',
118
+ 'collapseWhitespace',
119
+ 'copySelection',
120
+ 'cursorBOF',
121
+ 'cursorBOL',
122
+ 'cursorDown',
123
+ 'cursorEOF',
124
+ 'cursorEOL',
125
+ 'cursorBOV',
126
+ 'cursorTOV',
127
+ 'cursorLeft',
128
+ 'cursorReturn',
129
+ 'cursorRight',
130
+ 'cursorUp',
131
+ 'cutSelection',
132
+ 'delete',
133
+ 'deleteAndStoreLine',
134
+ 'deleteLine',
135
+ 'deleteToEOL',
136
+ 'evaluate',
137
+ 'execute',
138
+ 'find',
139
+ 'findAgain',
140
+ 'findAndReplace',
141
+ 'findExact',
142
+ 'goToLineAsk',
143
+ 'goToNamedBookmark',
144
+ 'goToNextBookmark',
145
+ 'goToPreviousBookmark',
146
+ 'goToTag',
147
+ 'goToTagUnderCursor',
148
+ 'help',
149
+ 'indent',
150
+ 'insertSpaces',
151
+ 'insertTab',
152
+ 'loadConfiguration',
153
+ 'loadScript',
154
+ 'newFile',
155
+ 'openFile',
156
+ 'openFileAsk',
157
+ 'operateOnEachLine',
158
+ 'operateOnLines',
159
+ 'operateOnString',
160
+ 'pageDown',
161
+ 'pageUp',
162
+ 'parsedIndent',
163
+ 'paste',
164
+ 'pasteShellResult',
165
+ 'playMacro',
166
+ 'popTag',
167
+ 'printKeychain',
168
+ 'quit',
169
+ 'redraw',
170
+ 'removeNamedBookmark',
171
+ 'removeSelection',
172
+ 'repeatLast',
173
+ 'revert',
174
+ 'saveFile',
175
+ 'saveFileAs',
176
+ 'scrollDown',
177
+ 'scrollUp',
178
+ 'searchAndReplace',
179
+ 'seek',
180
+ 'setBufferType',
181
+ 'setReadOnly',
182
+ 'shell',
183
+ 'showClips',
184
+ 'suspend',
185
+ 'switchToBufferNumber',
186
+ 'switchToNextBuffer',
187
+ 'switchToPreviousBuffer',
188
+ 'toggleBookmark',
189
+ 'toggleMacroRecording',
190
+ 'toggleSelection',
191
+ 'toggleSessionSetting',
192
+ 'undo',
193
+ 'unindent',
194
+ 'unundo'
195
+ ]
196
+ LANG_TEXT = 'text'
197
+
198
+ NUM_LAST_COMMANDS = 2
199
+
200
+ class Diakonos
201
+ attr_reader :win_main, :settings, :token_regexps, :close_token_regexps,
202
+ :token_formats, :diakonos_home, :script_dir, :diakonos_conf, :display_mutex,
203
+ :indenters, :unindenters, :clipboard, :do_display,
204
+ :current_buffer, :list_filename, :hooks, :last_commands, :there_was_non_movement
205
+
206
+
207
+ def initialize( argv = [] )
208
+ @diakonos_home = ( ( ENV[ 'HOME' ] or '' ) + '/.diakonos' ).subHome
209
+ if not FileTest.exists? @diakonos_home
210
+ Dir.mkdir @diakonos_home
211
+ end
212
+ @script_dir = "#{@diakonos_home}/scripts"
213
+ if not FileTest.exists? @script_dir
214
+ Dir.mkdir @script_dir
215
+ end
216
+ @debug = File.new( "#{@diakonos_home}/debug.log", 'w' )
217
+ @list_filename = @diakonos_home + '/listing.txt'
218
+ @diff_filename = @diakonos_home + '/text.diff'
219
+
220
+ @files = Array.new
221
+ @read_only_files = Array.new
222
+ @config_filename = nil
223
+
224
+ parseOptions argv
225
+
226
+ @session_settings = Hash.new
227
+ @win_main = nil
228
+ @win_context = nil
229
+ @win_status = nil
230
+ @win_interaction = nil
231
+ @buffers = BufferHash.new
232
+
233
+ loadConfiguration
234
+
235
+ @quitting = false
236
+ @untitled_id = 0
237
+
238
+ @x = 0
239
+ @y = 0
240
+
241
+ @buffer_stack = Array.new
242
+ @current_buffer = nil
243
+ @bookmarks = Hash.new
244
+ @macro_history = nil
245
+ @macro_input_history = nil
246
+ @macros = Hash.new
247
+ @last_commands = SizedArray.new( NUM_LAST_COMMANDS )
248
+ @playing_macro = false
249
+ @display_mutex = Mutex.new
250
+ @display_queue_mutex = Mutex.new
251
+ @display_queue = nil
252
+ @do_display = true
253
+ @iline_mutex = Mutex.new
254
+ @tag_stack = Array.new
255
+ @last_search_regexps = nil
256
+ @iterated_choice = nil
257
+ @choice_iterations = 0
258
+ @there_was_non_movement = false
259
+
260
+ # Readline histories
261
+ @rlh_general = Array.new
262
+ @rlh_files = Array.new
263
+ @rlh_search = Array.new
264
+ @rlh_shell = Array.new
265
+ end
266
+
267
+ def parseOptions( argv )
268
+ while argv.length > 0
269
+ arg = argv.shift
270
+ case arg
271
+ when '--help'
272
+ printUsage
273
+ exit 1
274
+ when '-ro'
275
+ filename = argv.shift
276
+ if filename == nil
277
+ printUsage
278
+ exit 1
279
+ else
280
+ @read_only_files.push filename
281
+ end
282
+ when '-c', '--config'
283
+ @config_filename = argv.shift
284
+ if @config_filename == nil
285
+ printUsage
286
+ exit 1
287
+ end
288
+ when '-e', '--execute'
289
+ post_load_script = argv.shift
290
+ if post_load_script == nil
291
+ printUsage
292
+ exit 1
293
+ else
294
+ @post_load_script = post_load_script
295
+ end
296
+ else
297
+ # a name of a file to open
298
+ @files.push arg
299
+ end
300
+ end
301
+ end
302
+ protected :parseOptions
303
+
304
+ def printUsage
305
+ puts "Usage: #{$0} [options] [file] [file...]"
306
+ puts "\t--help\tDisplay usage"
307
+ puts "\t-ro <file>\tLoad file as read-only"
308
+ puts "\t-c <config file>\tLoad this config file instead of ~/.diakonos/diakonos.conf"
309
+ puts "\t-e, --execute <Ruby code>\tExecute Ruby code (such as Diakonos commands) after startup"
310
+ end
311
+ protected :printUsage
312
+
313
+ def initializeDisplay
314
+ if @win_main != nil
315
+ @win_main.close
316
+ end
317
+ if @win_status != nil
318
+ @win_status.close
319
+ end
320
+ if @win_interaction != nil
321
+ @win_interaction.close
322
+ end
323
+ if @win_context != nil
324
+ @win_context.close
325
+ end
326
+
327
+ Curses::init_screen
328
+ Curses::nonl
329
+ Curses::raw
330
+ Curses::noecho
331
+
332
+ if Curses::has_colors?
333
+ Curses::start_color
334
+ Curses::init_pair( Curses::COLOR_BLACK, Curses::COLOR_BLACK, Curses::COLOR_BLACK )
335
+ Curses::init_pair( Curses::COLOR_RED, Curses::COLOR_RED, Curses::COLOR_BLACK )
336
+ Curses::init_pair( Curses::COLOR_GREEN, Curses::COLOR_GREEN, Curses::COLOR_BLACK )
337
+ Curses::init_pair( Curses::COLOR_YELLOW, Curses::COLOR_YELLOW, Curses::COLOR_BLACK )
338
+ Curses::init_pair( Curses::COLOR_BLUE, Curses::COLOR_BLUE, Curses::COLOR_BLACK )
339
+ Curses::init_pair( Curses::COLOR_MAGENTA, Curses::COLOR_MAGENTA, Curses::COLOR_BLACK )
340
+ Curses::init_pair( Curses::COLOR_CYAN, Curses::COLOR_CYAN, Curses::COLOR_BLACK )
341
+ Curses::init_pair( Curses::COLOR_WHITE, Curses::COLOR_WHITE, Curses::COLOR_BLACK )
342
+ @colour_pairs.each do |cp|
343
+ Curses::init_pair( cp[ :number ], cp[ :fg ], cp[ :bg ] )
344
+ end
345
+ end
346
+
347
+ @win_main = Curses::Window.new( main_window_height, Curses::cols, 0, 0 )
348
+ @win_main.keypad( true )
349
+ @win_status = Curses::Window.new( 1, Curses::cols, Curses::lines - 2, 0 )
350
+ @win_status.keypad( true )
351
+ @win_status.attrset @settings[ 'status.format' ]
352
+ @win_interaction = Curses::Window.new( 1, Curses::cols, Curses::lines - 1, 0 )
353
+ @win_interaction.keypad( true )
354
+
355
+ if @settings[ 'context.visible' ]
356
+ if @settings[ 'context.combined' ]
357
+ pos = 1
358
+ else
359
+ pos = 3
360
+ end
361
+ @win_context = Curses::Window.new( 1, Curses::cols, Curses::lines - pos, 0 )
362
+ @win_context.keypad( true )
363
+ else
364
+ @win_context = nil
365
+ end
366
+
367
+ @win_interaction.refresh
368
+ @win_main.refresh
369
+
370
+ @buffers.each_value do |buffer|
371
+ buffer.reset_win_main
372
+ end
373
+ end
374
+
375
+ def fetch_conf( location = "tags/v#{VERSION}" )
376
+ require 'open-uri'
377
+ found = false
378
+ puts "Fetching configuration from #{location}..."
379
+
380
+ begin
381
+ open( "http://rome.purepistos.net/issues/diakonos/browser/#{location}/diakonos.conf?format=raw" ) do |http|
382
+ text = http.read
383
+ if Regexp.new( "No node /#{location}/diakonos.conf" ) !~ text
384
+ found = true
385
+ File.open( @diakonos_conf, 'w' ) do |f|
386
+ f.puts text
387
+ end
388
+ end
389
+ end
390
+ rescue OpenURI::HTTPError => e
391
+ $stderr.puts "Failed to fetch from #{location}."
392
+ end
393
+
394
+ return found
395
+ end
396
+
397
+ def loadConfiguration
398
+ # Set defaults first
399
+
400
+ existent = 0
401
+ conf_dirs = [
402
+ '/usr/local/etc/diakonos.conf',
403
+ '/usr/etc/diakonos.conf',
404
+ '/etc/diakonos.conf',
405
+ '/usr/local/share/diakonos/diakonos.conf',
406
+ '/usr/share/diakonos/diakonos.conf'
407
+ ]
408
+
409
+ conf_dirs.each do |conf_dir|
410
+ @global_diakonos_conf = conf_dir
411
+ if FileTest.exists? @global_diakonos_conf
412
+ existent += 1
413
+ break
414
+ end
415
+ end
416
+
417
+ @diakonos_conf = ( @config_filename or ( @diakonos_home + '/diakonos.conf' ) )
418
+ existent += 1 if FileTest.exists? @diakonos_conf
419
+
420
+ if existent < 1
421
+ puts "diakonos.conf not found in any of:"
422
+ conf_dirs.each do |conf_dir|
423
+ puts " #{conf_dir}"
424
+ end
425
+ puts " ~/.diakonos/"
426
+ puts "At least one configuration file must exist."
427
+ $stdout.puts "Would you like to download one right now from the Diakonos repository? (y/n)"; $stdout.flush
428
+ answer = $stdin.gets
429
+ case answer
430
+ when /^y/i
431
+ if not fetch_conf
432
+ fetch_conf 'trunk'
433
+ end
434
+ end
435
+
436
+ if not FileTest.exists?( @diakonos_conf )
437
+ puts "Terminating..."
438
+ exit 1
439
+ end
440
+ end
441
+
442
+ @logfilename = @diakonos_home + "/diakonos.log"
443
+ @keychains = Hash.new
444
+ @token_regexps = Hash.new
445
+ @close_token_regexps = Hash.new
446
+ @token_formats = Hash.new
447
+ @indenters = Hash.new
448
+ @unindenters = Hash.new
449
+ @filemasks = Hash.new
450
+ @bangmasks = Hash.new
451
+
452
+ @settings = Hash.new
453
+ # Setup some defaults
454
+ @settings[ "context.format" ] = Curses::A_REVERSE
455
+
456
+ @keychains[ Curses::KEY_RESIZE ] = [ "redraw", nil ]
457
+ @keychains[ RESIZE2 ] = [ "redraw", nil ]
458
+
459
+ @colour_pairs = Array.new
460
+
461
+ begin
462
+ parseConfigurationFile( @global_diakonos_conf )
463
+ parseConfigurationFile( @diakonos_conf )
464
+
465
+ # Session settings override config file settings.
466
+
467
+ @session_settings.each do |key,value|
468
+ @settings[ key ] = value
469
+ end
470
+
471
+ @clipboard = Clipboard.new @settings[ "max_clips" ]
472
+ @log = File.open( @logfilename, "a" )
473
+
474
+ if @buffers != nil
475
+ @buffers.each_value do |buffer|
476
+ buffer.configure
477
+ end
478
+ end
479
+ rescue Errno::ENOENT
480
+ # No config file found or readable
481
+ end
482
+ end
483
+
484
+ def parseConfigurationFile( filename )
485
+ return if not FileTest.exists? filename
486
+
487
+ lines = IO.readlines( filename ).collect { |l| l.chomp }
488
+ lines.each do |line|
489
+ # Skip comments
490
+ next if line[ 0 ] == ?#
491
+
492
+ command, arg = line.split( /\s+/, 2 )
493
+ next if command == nil
494
+ command = command.downcase
495
+ case command
496
+ when "include"
497
+ parseConfigurationFile arg.subHome
498
+ when "key"
499
+ if arg != nil
500
+ if / / === arg
501
+ keystrings, function_and_args = arg.split( / {2,}/, 2 )
502
+ else
503
+ keystrings, function_and_args = arg.split( /;/, 2 )
504
+ end
505
+ keystrokes = Array.new
506
+ keystrings.split( /\s+/ ).each do |ks_str|
507
+ code = ks_str.keyCode
508
+ if code != nil
509
+ keystrokes.concat code
510
+ else
511
+ puts "unknown keystring: #{ks_str}"
512
+ end
513
+ end
514
+ if function_and_args == nil
515
+ @keychains.deleteKeyPath( keystrokes )
516
+ else
517
+ function, function_args = function_and_args.split( /\s+/, 2 )
518
+ if FUNCTIONS.include? function
519
+ @keychains.setKeyPath(
520
+ keystrokes,
521
+ [ function, function_args ]
522
+ )
523
+ end
524
+ end
525
+ end
526
+ when /^lang\.(.+?)\.tokens\.([^.]+)(\.case_insensitive)?$/
527
+ getTokenRegexp( @token_regexps, arg, Regexp.last_match )
528
+ when /^lang\.(.+?)\.tokens\.([^.]+)\.open(\.case_insensitive)?$/
529
+ getTokenRegexp( @token_regexps, arg, Regexp.last_match )
530
+ when /^lang\.(.+?)\.tokens\.([^.]+)\.close(\.case_insensitive)?$/
531
+ getTokenRegexp( @close_token_regexps, arg, Regexp.last_match )
532
+ when /^lang\.(.+?)\.tokens\.(.+?)\.format$/
533
+ language = $1
534
+ token_class = $2
535
+ @token_formats[ language ] = ( @token_formats[ language ] or Hash.new )
536
+ @token_formats[ language ][ token_class ] = arg.toFormatting
537
+ when /^lang\.(.+?)\.format\..+$/
538
+ @settings[ command ] = arg.toFormatting
539
+ when /^colou?r$/
540
+ number, fg, bg = arg.split( /\s+/ )
541
+ number = number.to_i
542
+ fg = fg.toColourConstant
543
+ bg = bg.toColourConstant
544
+ @colour_pairs << {
545
+ :number => number,
546
+ :fg => fg,
547
+ :bg => bg
548
+ }
549
+ when /^lang\.(.+?)\.indent\.indenters(\.case_insensitive)?$/
550
+ case_insensitive = ( $2 != nil )
551
+ if case_insensitive
552
+ @indenters[ $1 ] = Regexp.new( arg, Regexp::IGNORECASE )
553
+ else
554
+ @indenters[ $1 ] = Regexp.new arg
555
+ end
556
+ when /^lang\.(.+?)\.indent\.unindenters(\.case_insensitive)?$/
557
+ case_insensitive = ( $2 != nil )
558
+ if case_insensitive
559
+ @unindenters[ $1 ] = Regexp.new( arg, Regexp::IGNORECASE )
560
+ else
561
+ @unindenters[ $1 ] = Regexp.new arg
562
+ end
563
+ when /^lang\.(.+?)\.indent\.preventers(\.case_insensitive)?$/,
564
+ /^lang\.(.+?)\.indent\.ignore(\.case_insensitive)?$/,
565
+ /^lang\.(.+?)\.context\.ignore(\.case_insensitive)?$/
566
+ case_insensitive = ( $2 != nil )
567
+ if case_insensitive
568
+ @settings[ command ] = Regexp.new( arg, Regexp::IGNORECASE )
569
+ else
570
+ @settings[ command ] = Regexp.new arg
571
+ end
572
+ when /^lang\.(.+?)\.filemask$/
573
+ @filemasks[ $1 ] = Regexp.new arg
574
+ when /^lang\.(.+?)\.bangmask$/
575
+ @bangmasks[ $1 ] = Regexp.new arg
576
+ when "context.visible", "context.combined", "eof_newline", "view.nonfilelines.visible",
577
+ /^lang\.(.+?)\.indent\.(?:auto|roundup|using_tabs)$/,
578
+ "found_cursor_start", "convert_tabs"
579
+ @settings[ command ] = arg.to_b
580
+ when "context.format", "context.separator.format", "status.format"
581
+ @settings[ command ] = arg.toFormatting
582
+ when "logfile"
583
+ @logfilename = arg.subHome
584
+ when "context.separator", "status.left", "status.right", "status.filler",
585
+ "status.modified_str", "status.unnamed_str", "status.selecting_str",
586
+ "status.read_only_str", /^lang\..+?\.indent\.ignore\.charset$/,
587
+ /^lang\.(.+?)\.tokens\.([^.]+)\.change_to$/, "view.nonfilelines.character",
588
+ 'interaction.blink_string', 'diff_command'
589
+ @settings[ command ] = arg
590
+ when "status.vars"
591
+ @settings[ command ] = arg.split( /\s+/ )
592
+ when /^lang\.(.+?)\.indent\.size$/, /^lang\.(.+?)\.tabsize$/
593
+ @settings[ command ] = arg.to_i
594
+ when "context.max_levels", "context.max_segment_width", "max_clips", "max_undo_lines",
595
+ "view.margin.x", "view.margin.y", "view.scroll_amount", "view.lookback"
596
+ @settings[ command ] = arg.to_i
597
+ when "view.jump.x", "view.jump.y"
598
+ value = arg.to_i
599
+ if value < 1
600
+ value = 1
601
+ end
602
+ @settings[ command ] = value
603
+ when "bol_behaviour", "bol_behavior"
604
+ case arg.downcase
605
+ when "zero"
606
+ @settings[ "bol_behaviour" ] = BOL_ZERO
607
+ when "first-char"
608
+ @settings[ "bol_behaviour" ] = BOL_FIRST_CHAR
609
+ when "alternating-zero"
610
+ @settings[ "bol_behaviour" ] = BOL_ALT_ZERO
611
+ else # default
612
+ @settings[ "bol_behaviour" ] = BOL_ALT_FIRST_CHAR
613
+ end
614
+ when "context.delay", 'interaction.blink_duration', 'interaction.choice_delay'
615
+ @settings[ command ] = arg.to_f
616
+ end
617
+ end
618
+ end
619
+ protected :parseConfigurationFile
620
+
621
+ def getTokenRegexp( hash, arg, match )
622
+ language = match[ 1 ]
623
+ token_class = match[ 2 ]
624
+ case_insensitive = ( match[ 3 ] != nil )
625
+ hash[ language ] = ( hash[ language ] or Hash.new )
626
+ if case_insensitive
627
+ hash[ language ][ token_class ] = Regexp.new( arg, Regexp::IGNORECASE )
628
+ else
629
+ hash[ language ][ token_class ] = Regexp.new arg
630
+ end
631
+ end
632
+
633
+ def redraw
634
+ loadConfiguration
635
+ initializeDisplay
636
+ updateStatusLine
637
+ updateContextLine
638
+ @current_buffer.display
639
+ end
640
+
641
+ def log( string )
642
+ @log.puts string
643
+ @log.flush
644
+ end
645
+
646
+ def debugLog( string )
647
+ @debug.puts( Time.now.strftime( "[%a %H:%M:%S] #{string}" ) )
648
+ @debug.flush
649
+ end
650
+
651
+ def registerProc( proc, hook_name, priority = 0 )
652
+ @hooks[ hook_name ] << { :proc => proc, :priority => priority }
653
+ end
654
+
655
+ def clearNonMovementFlag
656
+ @there_was_non_movement = false
657
+ end
658
+
659
+ # -----------------------------------------------------------------------
660
+
661
+ def main_window_height
662
+ # One line for the status line
663
+ # One line for the input line
664
+ # One line for the context line
665
+ retval = Curses::lines - 2
666
+ if @settings[ "context.visible" ] and not @settings[ "context.combined" ]
667
+ retval = retval - 1
668
+ end
669
+ return retval
670
+ end
671
+
672
+ def main_window_width
673
+ return Curses::cols
674
+ end
675
+
676
+ def start
677
+ initializeDisplay
678
+
679
+ @hooks = {
680
+ :after_save => [],
681
+ :after_startup => [],
682
+ }
683
+ Dir[ "#{@script_dir}/*" ].each do |script|
684
+ begin
685
+ require script
686
+ rescue Exception => e
687
+ showException(
688
+ e,
689
+ [
690
+ "There is a syntax error in the script.",
691
+ "An invalid hook name was used."
692
+ ]
693
+ )
694
+ end
695
+ end
696
+ @hooks.each do |hook_name, hook|
697
+ hook.sort { |a,b| a[ :priority ] <=> b[ :priority ] }
698
+ end
699
+
700
+ setILine "Diakonos #{VERSION} (#{LAST_MODIFIED}) F1 for help F12 to configure"
701
+
702
+ num_opened = 0
703
+ if @files.length == 0 and @read_only_files.length == 0
704
+ num_opened += 1 if openFile
705
+ else
706
+ @files.each do |file|
707
+ num_opened += 1 if openFile file
708
+ end
709
+ @read_only_files.each do |file|
710
+ num_opened += 1 if openFile( file, Buffer::READ_ONLY )
711
+ end
712
+ end
713
+
714
+ if num_opened > 0
715
+ switchToBufferNumber 1
716
+
717
+ updateStatusLine
718
+ updateContextLine
719
+
720
+ if @post_load_script != nil
721
+ eval @post_load_script
722
+ end
723
+
724
+ runHookProcs( :after_startup )
725
+
726
+ begin
727
+ # Main keyboard loop.
728
+ while not @quitting
729
+ processKeystroke
730
+ @win_main.refresh
731
+ end
732
+ rescue SignalException => e
733
+ debugLog "Terminated by signal (#{e.message})"
734
+ end
735
+
736
+ @debug.close
737
+ end
738
+ end
739
+
740
+ # context is an array of characters (bytes) which are keystrokes previously
741
+ # typed (in a chain of keystrokes)
742
+ def processKeystroke( context = [] )
743
+ c = @win_main.getch
744
+
745
+ if @capturing_keychain
746
+ if c == ENTER
747
+ @capturing_keychain = false
748
+ @current_buffer.deleteSelection
749
+ str = context.to_keychain_s.strip
750
+ @current_buffer.insertString str
751
+ cursorRight( Buffer::STILL_TYPING, str.length )
752
+ else
753
+ keychain_pressed = context.concat [ c ]
754
+
755
+ function_and_args = @keychains.getLeaf( keychain_pressed )
756
+
757
+ if function_and_args != nil
758
+ function, args = function_and_args
759
+ end
760
+
761
+ partial_keychain = @keychains.getNode( keychain_pressed )
762
+ if partial_keychain != nil
763
+ setILine( "Part of existing keychain: " + keychain_pressed.to_keychain_s + "..." )
764
+ else
765
+ setILine keychain_pressed.to_keychain_s + "..."
766
+ end
767
+ processKeystroke( keychain_pressed )
768
+ end
769
+ else
770
+
771
+ if context.empty?
772
+ if c > 31 and c < 255 and c != BACKSPACE
773
+ debugLog "char: #{c}"
774
+ if @macro_history != nil
775
+ @macro_history.push "typeCharacter #{c}"
776
+ end
777
+ if not @there_was_non_movement
778
+ @there_was_non_movement = true
779
+ end
780
+ typeCharacter c
781
+ return
782
+ end
783
+ end
784
+ keychain_pressed = context.concat [ c ]
785
+
786
+ function_and_args = @keychains.getLeaf( keychain_pressed )
787
+
788
+ if function_and_args != nil
789
+ function, args = function_and_args
790
+ setILine if not @settings[ "context.combined" ]
791
+
792
+ if args != nil
793
+ to_eval = "#{function}( #{args} )"
794
+ else
795
+ to_eval = function
796
+ end
797
+
798
+ if @macro_history != nil
799
+ @macro_history.push to_eval
800
+ end
801
+
802
+ begin
803
+ eval to_eval, nil, "eval"
804
+ @last_commands << to_eval unless to_eval == "repeatLast"
805
+ if not @there_was_non_movement
806
+ @there_was_non_movement = ( not to_eval.movement? )
807
+ end
808
+ rescue Exception => e
809
+ debugLog e.message
810
+ debugLog e.backtrace.join( "\n\t" )
811
+ showException e
812
+ end
813
+ else
814
+ partial_keychain = @keychains.getNode( keychain_pressed )
815
+ if partial_keychain != nil
816
+ setILine( keychain_pressed.to_keychain_s + "..." )
817
+ processKeystroke( keychain_pressed )
818
+ else
819
+ setILine "Nothing assigned to #{keychain_pressed.to_keychain_s}"
820
+ end
821
+ end
822
+ end
823
+ end
824
+ protected :processKeystroke
825
+
826
+ # Display text on the interaction line.
827
+ def setILine( string = "" )
828
+ Curses::curs_set 0
829
+ @win_interaction.setpos( 0, 0 )
830
+ @win_interaction.addstr( "%-#{Curses::cols}s" % string )
831
+ @win_interaction.refresh
832
+ Curses::curs_set 1
833
+ return string.length
834
+ end
835
+
836
+ def showClips
837
+ clip_filename = @diakonos_home + "/clips.txt"
838
+ File.open( clip_filename, "w" ) do |f|
839
+ @clipboard.each do |clip|
840
+ log clip
841
+ f.puts clip
842
+ f.puts "---------------------------"
843
+ end
844
+ end
845
+ openFile clip_filename
846
+ end
847
+
848
+ def switchTo( buffer )
849
+ switched = false
850
+ if buffer != nil
851
+ @buffer_stack -= [ @current_buffer ]
852
+ @buffer_stack.push @current_buffer if @current_buffer != nil
853
+ @current_buffer = buffer
854
+ updateStatusLine
855
+ updateContextLine
856
+ buffer.display
857
+ switched = true
858
+ end
859
+
860
+ return switched
861
+ end
862
+ protected :switchTo
863
+
864
+ def buildStatusLine( truncation = 0 )
865
+ var_array = Array.new
866
+ @settings[ "status.vars" ].each do |var|
867
+ case var
868
+ when "buffer_number"
869
+ var_array.push bufferToNumber( @current_buffer )
870
+ when "col"
871
+ var_array.push( @current_buffer.last_screen_col + 1 )
872
+ when "filename"
873
+ name = @current_buffer.nice_name
874
+ var_array.push( name[ ([ truncation, name.length ].min)..-1 ] )
875
+ when "modified"
876
+ if @current_buffer.modified
877
+ var_array.push @settings[ "status.modified_str" ]
878
+ else
879
+ var_array.push ""
880
+ end
881
+ when "num_buffers"
882
+ var_array.push @buffers.length
883
+ when "num_lines"
884
+ var_array.push @current_buffer.length
885
+ when "row", "line"
886
+ var_array.push( @current_buffer.last_row + 1 )
887
+ when "read_only"
888
+ if @current_buffer.read_only
889
+ var_array.push @settings[ "status.read_only_str" ]
890
+ else
891
+ var_array.push ""
892
+ end
893
+ when "selecting"
894
+ if @current_buffer.changing_selection
895
+ var_array.push @settings[ "status.selecting_str" ]
896
+ else
897
+ var_array.push ""
898
+ end
899
+ when "type"
900
+ var_array.push @current_buffer.original_language
901
+ end
902
+ end
903
+ str = nil
904
+ begin
905
+ status_left = @settings[ "status.left" ]
906
+ field_count = status_left.count "%"
907
+ status_left = status_left % var_array[ 0...field_count ]
908
+ status_right = @settings[ "status.right" ] % var_array[ field_count..-1 ]
909
+ filler_string = @settings[ "status.filler" ]
910
+ fill_amount = (Curses::cols - status_left.length - status_right.length) / filler_string.length
911
+ if fill_amount > 0
912
+ filler = filler_string * fill_amount
913
+ else
914
+ filler = ""
915
+ end
916
+ str = status_left + filler + status_right
917
+ rescue ArgumentError => e
918
+ str = "%-#{Curses::cols}s" % "(status line configuration error)"
919
+ end
920
+ return str
921
+ end
922
+ protected :buildStatusLine
923
+
924
+ def updateStatusLine
925
+ str = buildStatusLine
926
+ if str.length > Curses::cols
927
+ str = buildStatusLine( str.length - Curses::cols )
928
+ end
929
+ Curses::curs_set 0
930
+ @win_status.setpos( 0, 0 )
931
+ @win_status.addstr str
932
+ @win_status.refresh
933
+ Curses::curs_set 1
934
+ end
935
+
936
+ def updateContextLine
937
+ if @win_context != nil
938
+ @context_thread.exit if @context_thread != nil
939
+ @context_thread = Thread.new do ||
940
+
941
+ context = @current_buffer.context
942
+
943
+ Curses::curs_set 0
944
+ @win_context.setpos( 0, 0 )
945
+ chars_printed = 0
946
+ if context.length > 0
947
+ truncation = [ @settings[ "context.max_levels" ], context.length ].min
948
+ max_length = [
949
+ ( Curses::cols / truncation ) - @settings[ "context.separator" ].length,
950
+ ( @settings[ "context.max_segment_width" ] or Curses::cols )
951
+ ].min
952
+ line = nil
953
+ context_subset = context[ 0...truncation ]
954
+ context_subset = context_subset.collect do |line|
955
+ line.strip[ 0...max_length ]
956
+ end
957
+
958
+ context_subset.each do |line|
959
+ @win_context.attrset @settings[ "context.format" ]
960
+ @win_context.addstr line
961
+ chars_printed += line.length
962
+ @win_context.attrset @settings[ "context.separator.format" ]
963
+ @win_context.addstr @settings[ "context.separator" ]
964
+ chars_printed += @settings[ "context.separator" ].length
965
+ end
966
+ end
967
+
968
+ @iline_mutex.synchronize do
969
+ @win_context.attrset @settings[ "context.format" ]
970
+ @win_context.addstr( " " * ( Curses::cols - chars_printed ) )
971
+ @win_context.refresh
972
+ end
973
+ @display_mutex.synchronize do
974
+ @win_main.setpos( @current_buffer.last_screen_y, @current_buffer.last_screen_x )
975
+ @win_main.refresh
976
+ end
977
+ Curses::curs_set 1
978
+ end
979
+
980
+ @context_thread.priority = -2
981
+ end
982
+ end
983
+
984
+ def displayEnqueue( buffer )
985
+ @display_queue_mutex.synchronize do
986
+ @display_queue = buffer
987
+ end
988
+ end
989
+
990
+ def displayDequeue
991
+ @display_queue_mutex.synchronize do
992
+ if @display_queue != nil
993
+ Thread.new( @display_queue ) do |b|
994
+ @display_mutex.lock
995
+ @display_mutex.unlock
996
+ b.display
997
+ end
998
+ @display_queue = nil
999
+ end
1000
+ end
1001
+ end
1002
+
1003
+ # completion_array is the array of strings that tab completion can use
1004
+ def getUserInput( prompt, history = @rlh_general, initial_text = "", completion_array = nil )
1005
+ if @playing_macro
1006
+ retval = @macro_input_history.shift
1007
+ else
1008
+ pos = setILine prompt
1009
+ @win_interaction.setpos( 0, pos )
1010
+ retval = Readline.new( self, @win_interaction, initial_text, completion_array, history ).readline
1011
+ if @macro_history != nil
1012
+ @macro_input_history.push retval
1013
+ end
1014
+ setILine
1015
+ end
1016
+ return retval
1017
+ end
1018
+
1019
+ def getLanguageFromName( name )
1020
+ retval = nil
1021
+ @filemasks.each do |language,filemask|
1022
+ if name =~ filemask
1023
+ retval = language
1024
+ break
1025
+ end
1026
+ end
1027
+ return retval
1028
+ end
1029
+
1030
+ def getLanguageFromShaBang( first_line )
1031
+ retval = nil
1032
+ @bangmasks.each do |language,bangmask|
1033
+ if first_line =~ /^#!/ and first_line =~ bangmask
1034
+ retval = language
1035
+ break
1036
+ end
1037
+ end
1038
+ return retval
1039
+ end
1040
+
1041
+ def showException( e, probable_causes = [ "Unknown" ] )
1042
+ begin
1043
+ File.open( @diakonos_home + "/diakonos.err", "w" ) do |f|
1044
+ f.puts "Diakonos Error:"
1045
+ f.puts
1046
+ f.puts e.message
1047
+ f.puts
1048
+ f.puts "Probable Causes:"
1049
+ f.puts
1050
+ probable_causes.each do |pc|
1051
+ f.puts "- #{pc}"
1052
+ end
1053
+ f.puts
1054
+ f.puts "----------------------------------------------------"
1055
+ f.puts "If you can reproduce this error, please report it at"
1056
+ f.puts "http://rome.purepistos.net/issues/diakonos/newticket !"
1057
+ f.puts "----------------------------------------------------"
1058
+ f.puts e.backtrace
1059
+ end
1060
+ openFile( @diakonos_home + "/diakonos.err" )
1061
+ rescue Exception => e2
1062
+ debugLog "EXCEPTION: #{e.message}"
1063
+ debugLog "\t#{e.backtrace}"
1064
+ end
1065
+ end
1066
+
1067
+ def logBacktrace
1068
+ begin
1069
+ raise Exception
1070
+ rescue Exception => e
1071
+ e.backtrace[ 1..-1 ].each do |x|
1072
+ debugLog x
1073
+ end
1074
+ end
1075
+ end
1076
+
1077
+ # The given buffer_number should be 1-based, not zero-based.
1078
+ # Returns nil if no such buffer exists.
1079
+ def bufferNumberToName( buffer_number )
1080
+ return nil if buffer_number < 1
1081
+
1082
+ number = 1
1083
+ buffer_name = nil
1084
+ @buffers.each_key do |name|
1085
+ if number == buffer_number
1086
+ buffer_name = name
1087
+ break
1088
+ end
1089
+ number += 1
1090
+ end
1091
+ return buffer_name
1092
+ end
1093
+
1094
+ # The returned value is 1-based, not zero-based.
1095
+ # Returns nil if no such buffer exists.
1096
+ def bufferToNumber( buffer )
1097
+ number = 1
1098
+ buffer_number = nil
1099
+ @buffers.each_value do |b|
1100
+ if b == buffer
1101
+ buffer_number = number
1102
+ break
1103
+ end
1104
+ number += 1
1105
+ end
1106
+ return buffer_number
1107
+ end
1108
+
1109
+ def subShellVariables( string )
1110
+ return nil if string == nil
1111
+
1112
+ retval = string
1113
+ retval = retval.subHome
1114
+
1115
+ # Current buffer filename
1116
+ retval.gsub!( /\$f/, ( $1 or "" ) + ( @current_buffer.name or "" ) )
1117
+
1118
+ # space-separated list of all buffer filenames
1119
+ name_array = Array.new
1120
+ @buffers.each_value do |b|
1121
+ name_array.push b.name
1122
+ end
1123
+ retval.gsub!( /\$F/, ( $1 or "" ) + ( name_array.join(' ') or "" ) )
1124
+
1125
+ # Get user input, sub it in
1126
+ if retval =~ /\$i/
1127
+ user_input = getUserInput( "Argument: ", @rlh_shell )
1128
+ retval.gsub!( /\$i/, user_input )
1129
+ end
1130
+
1131
+ # Current clipboard text
1132
+ if retval =~ /\$c/
1133
+ clip_filename = @diakonos_home + "/clip.txt"
1134
+ File.open( clip_filename, "w" ) do |clipfile|
1135
+ if @clipboard.clip != nil
1136
+ clipfile.puts( @clipboard.clip.join( "\n" ) )
1137
+ end
1138
+ end
1139
+ retval.gsub!( /\$c/, clip_filename )
1140
+ end
1141
+
1142
+ # Currently selected text
1143
+ if retval =~ /\$s/
1144
+ text_filename = @diakonos_home + "/selected.txt"
1145
+
1146
+ File.open( text_filename, "w" ) do |textfile|
1147
+ selected_text = @current_buffer.selected_text
1148
+ if selected_text != nil
1149
+ textfile.puts( selected_text.join( "\n" ) )
1150
+ end
1151
+ end
1152
+ retval.gsub!( /\$s/, text_filename )
1153
+ end
1154
+
1155
+ return retval
1156
+ end
1157
+
1158
+ def showMessage( message, non_interaction_duration = @settings[ 'interaction.choice_delay' ] )
1159
+ terminateMessage
1160
+
1161
+ @message_expiry = Time.now + non_interaction_duration
1162
+ @message_thread = Thread.new do
1163
+ time_left = @message_expiry - Time.now
1164
+ while time_left > 0
1165
+ setILine "(#{time_left.round}) #{message}"
1166
+ @win_main.setpos( @saved_main_y, @saved_main_x )
1167
+ sleep 1
1168
+ time_left = @message_expiry - Time.now
1169
+ end
1170
+ setILine message
1171
+ @win_main.setpos( @saved_main_y, @saved_main_x )
1172
+ end
1173
+ end
1174
+
1175
+ def terminateMessage
1176
+ if @message_thread != nil and @message_thread.alive?
1177
+ @message_thread.terminate
1178
+ @message_thread = nil
1179
+ end
1180
+ end
1181
+
1182
+ def interactionBlink( message = nil )
1183
+ terminateMessage
1184
+ setILine @settings[ 'interaction.blink_string' ]
1185
+ sleep @settings[ 'interaction.blink_duration' ]
1186
+ setILine message if message != nil
1187
+ end
1188
+
1189
+ # choices should be an array of CHOICE_* constants.
1190
+ # default is what is returned when Enter is pressed.
1191
+ def getChoice( prompt, choices, default = nil )
1192
+ retval = @iterated_choice
1193
+ if retval != nil
1194
+ @choice_iterations -= 1
1195
+ if @choice_iterations < 1
1196
+ @iterated_choice = nil
1197
+ @do_display = true
1198
+ end
1199
+ return retval
1200
+ end
1201
+
1202
+ @saved_main_x = @win_main.curx
1203
+ @saved_main_y = @win_main.cury
1204
+
1205
+ msg = prompt + " "
1206
+ choice_strings = choices.collect do |choice|
1207
+ CHOICE_STRINGS[ choice ]
1208
+ end
1209
+ msg << choice_strings.join( ", " )
1210
+
1211
+ if default.nil?
1212
+ showMessage msg
1213
+ else
1214
+ setILine msg
1215
+ end
1216
+
1217
+ c = nil
1218
+ while retval.nil?
1219
+ c = @win_interaction.getch
1220
+
1221
+ case c
1222
+ when Curses::KEY_NPAGE
1223
+ pageDown
1224
+ when Curses::KEY_PPAGE
1225
+ pageUp
1226
+ else
1227
+ if @message_expiry != nil and Time.now < @message_expiry
1228
+ interactionBlink
1229
+ showMessage msg
1230
+ else
1231
+ case c
1232
+ when ENTER
1233
+ retval = default
1234
+ when ?0..?9
1235
+ if @choice_iterations < 1
1236
+ @choice_iterations = ( c - ?0 )
1237
+ else
1238
+ @choice_iterations = @choice_iterations * 10 + ( c - ?0 )
1239
+ end
1240
+ else
1241
+ choices.each do |choice|
1242
+ if CHOICE_KEYS[ choice ].include? c
1243
+ retval = choice
1244
+ break
1245
+ end
1246
+ end
1247
+ end
1248
+
1249
+ if retval.nil?
1250
+ interactionBlink( msg )
1251
+ end
1252
+ end
1253
+ end
1254
+ end
1255
+
1256
+ terminateMessage
1257
+ setILine
1258
+
1259
+ if @choice_iterations > 0
1260
+ @choice_iterations -= 1
1261
+ @iterated_choice = retval
1262
+ @do_display = false
1263
+ end
1264
+
1265
+ return retval
1266
+ end
1267
+
1268
+ def startRecordingMacro( name = nil )
1269
+ return if @macro_history != nil
1270
+ @macro_name = name
1271
+ @macro_history = Array.new
1272
+ @macro_input_history = Array.new
1273
+ setILine "Started macro recording."
1274
+ end
1275
+ protected :startRecordingMacro
1276
+
1277
+ def stopRecordingMacro
1278
+ @macro_history.pop # Remove the stopRecordingMacro command itself
1279
+ @macros[ @macro_name ] = [ @macro_history, @macro_input_history ]
1280
+ @macro_history = nil
1281
+ @macro_input_history = nil
1282
+ setILine "Stopped macro recording."
1283
+ end
1284
+ protected :stopRecordingMacro
1285
+
1286
+ def typeCharacter( c )
1287
+ @current_buffer.deleteSelection( Buffer::DONT_DISPLAY )
1288
+ @current_buffer.insertChar c
1289
+ cursorRight( Buffer::STILL_TYPING )
1290
+ end
1291
+
1292
+ def loadTags
1293
+ @tags = Hash.new
1294
+ if @current_buffer != nil and @current_buffer.name != nil
1295
+ path = File.expand_path( File.dirname( @current_buffer.name ) )
1296
+ tagfile = path + "/tags"
1297
+ else
1298
+ tagfile = "./tags"
1299
+ end
1300
+ if FileTest.exists? tagfile
1301
+ IO.foreach( tagfile ) do |line_|
1302
+ line = line_.chomp
1303
+ # <tagname>\t<filepath>\t<line number or regexp>\t<kind of tag>
1304
+ tag, file, command, kind, rest = line.split( /\t/ )
1305
+ command.gsub!( /;"$/, "" )
1306
+ if command =~ /^\/.*\/$/
1307
+ command = command[ 1...-1 ]
1308
+ end
1309
+ @tags[ tag ] ||= Array.new
1310
+ @tags[ tag ].push CTag.new( file, command, kind, rest )
1311
+ end
1312
+ else
1313
+ setILine "(tags file not found)"
1314
+ end
1315
+ end
1316
+
1317
+ def refreshAll
1318
+ @win_main.refresh
1319
+ if @win_context != nil
1320
+ @win_context.refresh
1321
+ end
1322
+ @win_status.refresh
1323
+ @win_interaction.refresh
1324
+ end
1325
+
1326
+ def openListBuffer
1327
+ @list_buffer = openFile( @list_filename )
1328
+ end
1329
+
1330
+ def closeListBuffer
1331
+ closeFile( @list_buffer )
1332
+ end
1333
+
1334
+ def runHookProcs( hook_id, *args )
1335
+ @hooks[ hook_id ].each do |hook_proc|
1336
+ hook_proc[ :proc ].call( *args )
1337
+ end
1338
+ end
1339
+
1340
+ # --------------------------------------------------------------------
1341
+ #
1342
+ # Program Functions
1343
+
1344
+ def addNamedBookmark( name_ = nil )
1345
+ if name_ == nil
1346
+ name = getUserInput "Bookmark name: "
1347
+ else
1348
+ name = name_
1349
+ end
1350
+
1351
+ if name != nil
1352
+ @bookmarks[ name ] = Bookmark.new( @current_buffer, @current_buffer.currentRow, @current_buffer.currentColumn, name )
1353
+ setILine "Added bookmark #{@bookmarks[ name ].to_s}."
1354
+ end
1355
+ end
1356
+
1357
+ def anchorSelection
1358
+ @current_buffer.anchorSelection
1359
+ updateStatusLine
1360
+ end
1361
+
1362
+ def backspace
1363
+ delete if( @current_buffer.changing_selection or cursorLeft( Buffer::STILL_TYPING ) )
1364
+ end
1365
+
1366
+ def carriageReturn
1367
+ @current_buffer.carriageReturn
1368
+ @current_buffer.deleteSelection
1369
+ end
1370
+
1371
+ def changeSessionSetting( key_ = nil, value = nil, do_redraw = DONT_REDRAW )
1372
+ if key_ == nil
1373
+ key = getUserInput( "Setting: " )
1374
+ else
1375
+ key = key_
1376
+ end
1377
+
1378
+ if key != nil
1379
+ if value == nil
1380
+ value = getUserInput( "Value: " )
1381
+ end
1382
+ case @settings[ key ]
1383
+ when String
1384
+ value = value.to_s
1385
+ when Fixnum
1386
+ value = value.to_i
1387
+ when TrueClass, FalseClass
1388
+ value = value.to_b
1389
+ end
1390
+ @session_settings[ key ] = value
1391
+ redraw if do_redraw
1392
+ setILine "#{key} = #{value}"
1393
+ end
1394
+ end
1395
+
1396
+ def clearMatches
1397
+ @current_buffer.clearMatches Buffer::DO_DISPLAY
1398
+ end
1399
+
1400
+ # Returns the choice the user made, or nil if the user was not prompted to choose.
1401
+ def closeFile( buffer = @current_buffer, to_all = nil )
1402
+ return nil if buffer == nil
1403
+
1404
+ choice = nil
1405
+ if @buffers.has_value?( buffer )
1406
+ do_closure = true
1407
+
1408
+ if buffer.modified
1409
+ if not buffer.read_only
1410
+ if to_all == nil
1411
+ choices = [ CHOICE_YES, CHOICE_NO, CHOICE_CANCEL ]
1412
+ if @quitting
1413
+ choices.concat [ CHOICE_YES_TO_ALL, CHOICE_NO_TO_ALL ]
1414
+ end
1415
+ choice = getChoice(
1416
+ "Save changes to #{buffer.nice_name}?",
1417
+ choices,
1418
+ CHOICE_CANCEL
1419
+ )
1420
+ else
1421
+ choice = to_all
1422
+ end
1423
+ case choice
1424
+ when CHOICE_YES, CHOICE_YES_TO_ALL
1425
+ do_closure = true
1426
+ saveFile( buffer )
1427
+ when CHOICE_NO, CHOICE_NO_TO_ALL
1428
+ do_closure = true
1429
+ when CHOICE_CANCEL
1430
+ do_closure = false
1431
+ end
1432
+ end
1433
+ end
1434
+
1435
+ if do_closure
1436
+ del_buffer_key = nil
1437
+ previous_buffer = nil
1438
+ to_switch_to = nil
1439
+ switching = false
1440
+
1441
+ # Search the buffer hash for the buffer we want to delete,
1442
+ # and mark the one we will switch to after deletion.
1443
+ @buffers.each do |buffer_key,buf|
1444
+ if switching
1445
+ to_switch_to = buf
1446
+ break
1447
+ end
1448
+ if buf == buffer
1449
+ del_buffer_key = buffer_key
1450
+ switching = true
1451
+ next
1452
+ end
1453
+ previous_buffer = buf
1454
+ end
1455
+
1456
+ buf = nil
1457
+ while(
1458
+ ( not @buffer_stack.empty? ) and
1459
+ ( not @buffers.values.include?( buf ) ) or
1460
+ ( @buffers.index( buf ) == del_buffer_key )
1461
+ ) do
1462
+ buf = @buffer_stack.pop
1463
+ end
1464
+ if @buffers.values.include?( buf )
1465
+ to_switch_to = buf
1466
+ end
1467
+
1468
+ if to_switch_to != nil
1469
+ switchTo( to_switch_to )
1470
+ elsif previous_buffer != nil
1471
+ switchTo( previous_buffer )
1472
+ else
1473
+ # No buffers left. Open a new blank one.
1474
+ openFile
1475
+ end
1476
+
1477
+ @buffers.delete del_buffer_key
1478
+
1479
+ updateStatusLine
1480
+ updateContextLine
1481
+ end
1482
+ else
1483
+ log "No such buffer: #{buffer.name}"
1484
+ end
1485
+
1486
+ return choice
1487
+ end
1488
+
1489
+ def collapseWhitespace
1490
+ @current_buffer.collapseWhitespace
1491
+ end
1492
+
1493
+ def copySelection
1494
+ @clipboard.addClip @current_buffer.copySelection
1495
+ removeSelection
1496
+ end
1497
+
1498
+ # Returns true iff the cursor changed positions
1499
+ def cursorDown
1500
+ return @current_buffer.cursorTo( @current_buffer.last_row + 1, @current_buffer.last_col, Buffer::DO_DISPLAY, Buffer::STOPPED_TYPING, DONT_ADJUST_ROW )
1501
+ end
1502
+
1503
+ # Returns true iff the cursor changed positions
1504
+ def cursorLeft( stopped_typing = Buffer::STOPPED_TYPING )
1505
+ return @current_buffer.cursorTo( @current_buffer.last_row, @current_buffer.last_col - 1, Buffer::DO_DISPLAY, stopped_typing )
1506
+ end
1507
+
1508
+ def cursorRight( stopped_typing = Buffer::STOPPED_TYPING, amount = 1 )
1509
+ return @current_buffer.cursorTo( @current_buffer.last_row, @current_buffer.last_col + amount, Buffer::DO_DISPLAY, stopped_typing )
1510
+ end
1511
+
1512
+ # Returns true iff the cursor changed positions
1513
+ def cursorUp
1514
+ return @current_buffer.cursorTo( @current_buffer.last_row - 1, @current_buffer.last_col, Buffer::DO_DISPLAY, Buffer::STOPPED_TYPING, DONT_ADJUST_ROW )
1515
+ end
1516
+
1517
+ def cursorBOF
1518
+ @current_buffer.cursorTo( 0, 0, Buffer::DO_DISPLAY )
1519
+ end
1520
+
1521
+ def cursorBOL
1522
+ @current_buffer.cursorToBOL
1523
+ end
1524
+
1525
+ def cursorEOL
1526
+ y = @win_main.cury
1527
+ @current_buffer.cursorTo( @current_buffer.last_row, @current_buffer.lineAt( y ).length, Buffer::DO_DISPLAY )
1528
+ end
1529
+
1530
+ def cursorEOF
1531
+ @current_buffer.cursorToEOF
1532
+ end
1533
+
1534
+ # Top of view
1535
+ def cursorTOV
1536
+ @current_buffer.cursorToTOV
1537
+ end
1538
+
1539
+ # Bottom of view
1540
+ def cursorBOV
1541
+ @current_buffer.cursorToBOV
1542
+ end
1543
+
1544
+ def cursorReturn( dir_str = "backward" )
1545
+ stack_pointer, stack_size = @current_buffer.cursorReturn( dir_str.toDirection( :backward ) )
1546
+ setILine( "Location: #{stack_pointer+1}/#{stack_size}" )
1547
+ end
1548
+
1549
+ def cutSelection
1550
+ delete if @clipboard.addClip( @current_buffer.copySelection )
1551
+ end
1552
+
1553
+ def delete
1554
+ @current_buffer.delete
1555
+ end
1556
+
1557
+ def deleteAndStoreLine
1558
+ removed_text = @current_buffer.deleteLine
1559
+ if removed_text
1560
+ if @last_commands[ -1 ] =~ /^deleteAndStoreLine/
1561
+ @clipboard.appendToClip( [ removed_text, "" ] )
1562
+ else
1563
+ @clipboard.addClip( [ removed_text, "" ] )
1564
+ end
1565
+ end
1566
+ end
1567
+
1568
+ def deleteLine
1569
+ removed_text = @current_buffer.deleteLine
1570
+ @clipboard.addClip( [ removed_text, "" ] ) if removed_text
1571
+ end
1572
+
1573
+ def deleteToEOL
1574
+ removed_text = @current_buffer.deleteToEOL
1575
+ @clipboard.addClip( removed_text ) if removed_text
1576
+ end
1577
+
1578
+ def evaluate( code_ = nil )
1579
+ if code_ == nil
1580
+ if @current_buffer.changing_selection
1581
+ selected_text = @current_buffer.copySelection[ 0 ]
1582
+ end
1583
+ code = getUserInput( "Ruby code: ", @rlh_general, ( selected_text or "" ), FUNCTIONS )
1584
+ else
1585
+ code = code_
1586
+ end
1587
+
1588
+ if code != nil
1589
+ begin
1590
+ eval code
1591
+ rescue Exception => e
1592
+ showException(
1593
+ e,
1594
+ [
1595
+ "The code given to evaluate has a syntax error.",
1596
+ "The code given to evaluate refers to a Diakonos command which does not exist, or is misspelled.",
1597
+ "The code given to evaluate refers to a Diakonos command with missing arguments.",
1598
+ "The code given to evaluate refers to a variable or method which does not exist.",
1599
+ ]
1600
+ )
1601
+ end
1602
+ end
1603
+ end
1604
+
1605
+ def find( dir_str = "down", case_sensitive = CASE_INSENSITIVE, regexp_source_ = nil, replacement = nil )
1606
+ if regexp_source_ == nil
1607
+ if @current_buffer.changing_selection
1608
+ selected_text = @current_buffer.copySelection[ 0 ]
1609
+ end
1610
+ regexp_source = getUserInput( "Search regexp: ", @rlh_search, ( selected_text or "" ) )
1611
+ else
1612
+ regexp_source = regexp_source_
1613
+ end
1614
+
1615
+ if regexp_source != nil
1616
+ direction = dir_str.toDirection
1617
+ rs_array = regexp_source.newlineSplit
1618
+ regexps = Array.new
1619
+ begin
1620
+ rs_array.each do |regexp_source|
1621
+ if not case_sensitive
1622
+ regexps.push Regexp.new( regexp_source, Regexp::IGNORECASE )
1623
+ else
1624
+ regexps.push Regexp.new( regexp_source )
1625
+ end
1626
+ end
1627
+ rescue Exception => e
1628
+ exception_thrown = true
1629
+ rs_array.each do |regexp_source|
1630
+ if not case_sensitive
1631
+ regexps.push Regexp.new( Regexp.escape( regexp_source ), Regexp::IGNORECASE )
1632
+ else
1633
+ regexps.push Regexp.new( Regexp.escape( regexp_source ) )
1634
+ end
1635
+ end
1636
+ end
1637
+ if replacement == ASK_REPLACEMENT
1638
+ replacement = getUserInput( "Replace with: ", @rlh_search )
1639
+ end
1640
+
1641
+ setILine( "Searching literally; #{e.message}" ) if exception_thrown
1642
+
1643
+ @current_buffer.find( regexps, direction, replacement )
1644
+ @last_search_regexps = regexps
1645
+ end
1646
+ end
1647
+
1648
+ def findAgain( dir_str = nil )
1649
+ if dir_str != nil
1650
+ direction = dir_str.toDirection
1651
+ @current_buffer.findAgain( @last_search_regexps, direction )
1652
+ else
1653
+ @current_buffer.findAgain( @last_search_regexps )
1654
+ end
1655
+ end
1656
+
1657
+ def findAndReplace
1658
+ searchAndReplace
1659
+ end
1660
+
1661
+ def findExact( dir_str = "down", search_term_ = nil )
1662
+ if search_term_ == nil
1663
+ if @current_buffer.changing_selection
1664
+ selected_text = @current_buffer.copySelection[ 0 ]
1665
+ end
1666
+ search_term = getUserInput( "Search for: ", @rlh_search, ( selected_text or "" ) )
1667
+ else
1668
+ search_term = search_term_
1669
+ end
1670
+ if search_term != nil
1671
+ direction = dir_str.toDirection
1672
+ regexp = Regexp.new( Regexp.escape( search_term ) )
1673
+ @current_buffer.find( regexp, direction )
1674
+ @last_search_regexps = regexp
1675
+ end
1676
+ end
1677
+
1678
+ def goToLineAsk
1679
+ input = getUserInput( "Go to [line number|+lines][,column number]: " )
1680
+ if input != nil
1681
+ row = nil
1682
+
1683
+ if input =~ /([+-]\d+)/
1684
+ row = @current_buffer.last_row + $1.to_i
1685
+ col = @current_buffer.last_col
1686
+ else
1687
+ input = input.split( /\D+/ ).collect { |n| n.to_i }
1688
+ if input.size > 0
1689
+ if input[ 0 ] == 0
1690
+ row = nil
1691
+ else
1692
+ row = input[ 0 ] - 1
1693
+ end
1694
+ if input[ 1 ] != nil
1695
+ col = input[ 1 ] - 1
1696
+ end
1697
+ end
1698
+ end
1699
+
1700
+ if row
1701
+ @current_buffer.goToLine( row, col )
1702
+ end
1703
+ end
1704
+ end
1705
+
1706
+ def goToNamedBookmark( name_ = nil )
1707
+ if name_ == nil
1708
+ name = getUserInput "Bookmark name: "
1709
+ else
1710
+ name = name_
1711
+ end
1712
+
1713
+ if name != nil
1714
+ bookmark = @bookmarks[ name ]
1715
+ if bookmark != nil
1716
+ switchTo( bookmark.buffer )
1717
+ bookmark.buffer.cursorTo( bookmark.row, bookmark.col, Buffer::DO_DISPLAY )
1718
+ else
1719
+ setILine "No bookmark named '#{name}'."
1720
+ end
1721
+ end
1722
+ end
1723
+
1724
+ def goToNextBookmark
1725
+ @current_buffer.goToNextBookmark
1726
+ end
1727
+
1728
+ def goToPreviousBookmark
1729
+ @current_buffer.goToPreviousBookmark
1730
+ end
1731
+
1732
+ def goToTag( tag_ = nil )
1733
+ loadTags
1734
+
1735
+ # If necessary, prompt for tag name.
1736
+
1737
+ if tag_ == nil
1738
+ if @current_buffer.changing_selection
1739
+ selected_text = @current_buffer.copySelection[ 0 ]
1740
+ end
1741
+ tag_name = getUserInput( "Tag name: ", @rlh_general, ( selected_text or "" ), @tags.keys )
1742
+ else
1743
+ tag_name = tag_
1744
+ end
1745
+
1746
+ tag_array = @tags[ tag_name ]
1747
+ if tag_array != nil and tag_array.length > 0
1748
+ if i = tag_array.index( @last_tag )
1749
+ tag = ( tag_array[ i + 1 ] or tag_array[ 0 ] )
1750
+ else
1751
+ tag = tag_array[ 0 ]
1752
+ end
1753
+ @last_tag = tag
1754
+ @tag_stack.push [ @current_buffer.name, @current_buffer.last_row, @current_buffer.last_col ]
1755
+ if switchTo( @buffers[ tag.file ] )
1756
+ #@current_buffer.goToLine( 0 )
1757
+ else
1758
+ openFile( tag.file )
1759
+ end
1760
+ line_number = tag.command.to_i
1761
+ if line_number > 0
1762
+ @current_buffer.goToLine( line_number - 1 )
1763
+ else
1764
+ find( "down", CASE_SENSITIVE, tag.command )
1765
+ end
1766
+ elsif tag_name != nil
1767
+ setILine "No such tag: '#{tag_name}'"
1768
+ end
1769
+ end
1770
+
1771
+ def goToTagUnderCursor
1772
+ goToTag @current_buffer.wordUnderCursor
1773
+ end
1774
+
1775
+ def help
1776
+ help_filename = @diakonos_home + "/diakonos.help"
1777
+ File.open( help_filename, "w" ) do |help_file|
1778
+ sorted_keychains = @keychains.paths_and_leaves.sort { |a,b|
1779
+ a[ :leaf ][ 0 ] <=> b[ :leaf ][ 0 ]
1780
+ }
1781
+ sorted_keychains.each do |keystrokes_and_function_and_args|
1782
+ keystrokes = keystrokes_and_function_and_args[ :path ]
1783
+ function, args = keystrokes_and_function_and_args[ :leaf ]
1784
+ function_string = function.deep_clone
1785
+ if args != nil and args.length > 0
1786
+ function_string << "( #{args} )"
1787
+ end
1788
+ keychain_width = [ Curses::cols - function_string.length - 2, Curses::cols / 2 ].min
1789
+ help_file.puts(
1790
+ "%-#{keychain_width}s%s" % [
1791
+ keystrokes.to_keychain_s,
1792
+ function_string
1793
+ ]
1794
+ )
1795
+ end
1796
+ end
1797
+ openFile help_filename
1798
+ end
1799
+
1800
+ def indent
1801
+ if( @current_buffer.changing_selection )
1802
+ @do_display = false
1803
+ mark = @current_buffer.selection_mark
1804
+ if mark.end_col > 0
1805
+ end_row = mark.end_row
1806
+ else
1807
+ end_row = mark.end_row - 1
1808
+ end
1809
+ (mark.start_row...end_row).each do |row|
1810
+ @current_buffer.indent row, Buffer::DONT_DISPLAY
1811
+ end
1812
+ @do_display = true
1813
+ @current_buffer.indent( end_row )
1814
+ else
1815
+ @current_buffer.indent
1816
+ end
1817
+ end
1818
+
1819
+ def insertSpaces( num_spaces )
1820
+ if num_spaces > 0
1821
+ @current_buffer.deleteSelection
1822
+ @current_buffer.insertString( " " * num_spaces )
1823
+ cursorRight( Buffer::STILL_TYPING, num_spaces )
1824
+ end
1825
+ end
1826
+
1827
+ def insertTab
1828
+ typeCharacter( TAB )
1829
+ end
1830
+
1831
+ def loadScript( name_ = nil )
1832
+ if name_ == nil
1833
+ name = getUserInput( "File to load as script: ", @rlh_files )
1834
+ else
1835
+ name = name_
1836
+ end
1837
+
1838
+ if name != nil
1839
+ thread = Thread.new( name ) do |f|
1840
+ begin
1841
+ load( f )
1842
+ rescue Exception => e
1843
+ showException(
1844
+ e,
1845
+ [
1846
+ "The filename given does not exist.",
1847
+ "The filename given is not accessible or readable.",
1848
+ "The loaded script does not reference Diakonos commands as members of the global Diakonos object. e.g. cursorBOL instead of $diakonos.cursorBOL",
1849
+ "The loaded script has syntax errors.",
1850
+ "The loaded script references objects or object members which do not exist."
1851
+ ]
1852
+ )
1853
+ end
1854
+ setILine "Loaded script '#{name}'."
1855
+ end
1856
+
1857
+ loop do
1858
+ if thread.status != "run"
1859
+ break
1860
+ else
1861
+ sleep 0.1
1862
+ end
1863
+ end
1864
+ thread.join
1865
+ end
1866
+ end
1867
+
1868
+ def newFile
1869
+ openFile
1870
+ end
1871
+
1872
+ # Returns the buffer of the opened file, or nil.
1873
+ def openFile( filename = nil, read_only = false, force_revert = ASK_REVERT )
1874
+ do_open = true
1875
+ buffer = nil
1876
+ if filename != nil
1877
+ buffer_key = filename
1878
+ if(
1879
+ (not force_revert) and
1880
+ ( (existing_buffer = @buffers[ filename ]) != nil ) and
1881
+ ( filename !~ /\.diakonos/ )
1882
+ )
1883
+ switchTo( existing_buffer )
1884
+ choice = getChoice(
1885
+ "Revert to on-disk version of #{existing_buffer.nice_name}?",
1886
+ [ CHOICE_YES, CHOICE_NO ]
1887
+ )
1888
+ case choice
1889
+ when CHOICE_NO
1890
+ do_open = false
1891
+ end
1892
+ end
1893
+
1894
+ if FileTest.exist?( filename )
1895
+ # Don't try to open non-files (i.e. directories, pipes, sockets, etc.)
1896
+ do_open &&= FileTest.file?( filename )
1897
+ end
1898
+ else
1899
+ buffer_key = @untitled_id
1900
+ @untitled_id += 1
1901
+ end
1902
+
1903
+ if do_open
1904
+ # Is file readable?
1905
+
1906
+ # Does the "file" utility exist?
1907
+ if @settings[ 'use_magic_file' ] and FileTest.exist?( "/usr/bin/file" ) and filename != nil and FileTest.exist?( filename ) and /\blisting\.txt\b/ !~ filename
1908
+ file_type = `/usr/bin/file -L #{filename}`
1909
+ if file_type !~ /text/ and file_type !~ /empty$/
1910
+ choice = getChoice(
1911
+ "#{filename} does not appear to be readable. Try to open it anyway?",
1912
+ [ CHOICE_YES, CHOICE_NO ],
1913
+ CHOICE_NO
1914
+ )
1915
+ case choice
1916
+ when CHOICE_NO
1917
+ do_open = false
1918
+ end
1919
+
1920
+ end
1921
+ end
1922
+
1923
+ if do_open
1924
+ buffer = Buffer.new( self, filename, read_only )
1925
+ @buffers[ buffer_key ] = buffer
1926
+ switchTo( buffer )
1927
+ end
1928
+ end
1929
+
1930
+ return buffer
1931
+ end
1932
+
1933
+ def openFileAsk
1934
+ if @current_buffer != nil and @current_buffer.name != nil
1935
+ path = File.expand_path( File.dirname( @current_buffer.name ) ) + "/"
1936
+ file = getUserInput( "Filename: ", @rlh_files, path )
1937
+ else
1938
+ file = getUserInput( "Filename: ", @rlh_files )
1939
+ end
1940
+ if file != nil
1941
+ openFile file
1942
+ updateStatusLine
1943
+ updateContextLine
1944
+ end
1945
+ end
1946
+
1947
+ def operateOnString(
1948
+ ruby_code = getUserInput( 'Ruby code: ', @rlh_general, 'str.' )
1949
+ )
1950
+ if ruby_code != nil
1951
+ str = @current_buffer.selected_string
1952
+ if str != nil and not str.empty?
1953
+ @current_buffer.paste eval( ruby_code )
1954
+ end
1955
+ end
1956
+ end
1957
+
1958
+ def operateOnLines(
1959
+ ruby_code = getUserInput( 'Ruby code: ', @rlh_general, 'lines.collect { |l| l }' )
1960
+ )
1961
+ if ruby_code != nil
1962
+ lines = @current_buffer.selected_text
1963
+ if lines != nil and not lines.empty?
1964
+ if lines[ -1 ].empty?
1965
+ lines.pop
1966
+ popped = true
1967
+ end
1968
+ new_lines = eval( ruby_code )
1969
+ if popped
1970
+ new_lines << ''
1971
+ end
1972
+ @current_buffer.paste new_lines
1973
+ end
1974
+ end
1975
+ end
1976
+
1977
+ def operateOnEachLine(
1978
+ ruby_code = getUserInput( 'Ruby code: ', @rlh_general, 'line.' )
1979
+ )
1980
+ if ruby_code != nil
1981
+ lines = @current_buffer.selected_text
1982
+ if lines != nil and not lines.empty?
1983
+ if lines[ -1 ].empty?
1984
+ lines.pop
1985
+ popped = true
1986
+ end
1987
+ new_lines = eval( "lines.collect { |line| #{ruby_code} }" )
1988
+ if popped
1989
+ new_lines << ''
1990
+ end
1991
+ @current_buffer.paste new_lines
1992
+ end
1993
+ end
1994
+ end
1995
+
1996
+ def pageUp
1997
+ if @current_buffer.pitchView( -main_window_height, Buffer::DO_PITCH_CURSOR ) == 0
1998
+ cursorBOF
1999
+ end
2000
+ updateStatusLine
2001
+ updateContextLine
2002
+ end
2003
+
2004
+ def pageDown
2005
+ if @current_buffer.pitchView( main_window_height, Buffer::DO_PITCH_CURSOR ) == 0
2006
+ @current_buffer.cursorToEOF
2007
+ end
2008
+ updateStatusLine
2009
+ updateContextLine
2010
+ end
2011
+
2012
+ def parsedIndent
2013
+ if( @current_buffer.changing_selection )
2014
+ @do_display = false
2015
+ mark = @current_buffer.selection_mark
2016
+ (mark.start_row...mark.end_row).each do |row|
2017
+ @current_buffer.parsedIndent row, Buffer::DONT_DISPLAY
2018
+ end
2019
+ @do_display = true
2020
+ @current_buffer.parsedIndent mark.end_row
2021
+ else
2022
+ @current_buffer.parsedIndent
2023
+ end
2024
+ updateStatusLine
2025
+ updateContextLine
2026
+ end
2027
+
2028
+ def paste
2029
+ @current_buffer.paste @clipboard.clip
2030
+ end
2031
+
2032
+ def playMacro( name = nil )
2033
+ macro, input_history = @macros[ name ]
2034
+ if input_history != nil
2035
+ @macro_input_history = input_history.deep_clone
2036
+ if macro != nil
2037
+ @playing_macro = true
2038
+ macro.each do |command|
2039
+ eval command
2040
+ end
2041
+ @playing_macro = false
2042
+ @macro_input_history = nil
2043
+ end
2044
+ end
2045
+ end
2046
+
2047
+ def popTag
2048
+ tag = @tag_stack.pop
2049
+ if tag != nil
2050
+ if not switchTo( @buffers[ tag[ 0 ] ] )
2051
+ openFile( tag[ 0 ] )
2052
+ end
2053
+ @current_buffer.cursorTo( tag[ 1 ], tag[ 2 ], Buffer::DO_DISPLAY )
2054
+ else
2055
+ setILine "Tag stack empty."
2056
+ end
2057
+ end
2058
+
2059
+ def printKeychain
2060
+ @capturing_keychain = true
2061
+ setILine "Type any chain of keystrokes or key chords, then press Enter..."
2062
+ end
2063
+
2064
+ def quit
2065
+ @quitting = true
2066
+ to_all = nil
2067
+ @buffers.each_value do |buffer|
2068
+ if buffer.modified
2069
+ switchTo buffer
2070
+ closure_choice = closeFile( buffer, to_all )
2071
+ case closure_choice
2072
+ when CHOICE_CANCEL
2073
+ @quitting = false
2074
+ break
2075
+ when CHOICE_YES_TO_ALL, CHOICE_NO_TO_ALL
2076
+ to_all = closure_choice
2077
+ end
2078
+ end
2079
+ end
2080
+ end
2081
+
2082
+ def removeNamedBookmark( name_ = nil )
2083
+ if name_ == nil
2084
+ name = getUserInput "Bookmark name: "
2085
+ else
2086
+ name = name_
2087
+ end
2088
+
2089
+ if name != nil
2090
+ bookmark = @bookmarks.delete name
2091
+ setILine "Removed bookmark #{bookmark.to_s}."
2092
+ end
2093
+ end
2094
+
2095
+ def removeSelection
2096
+ @current_buffer.removeSelection
2097
+ updateStatusLine
2098
+ end
2099
+
2100
+ def repeatLast
2101
+ eval @last_commands[ -1 ] if not @last_commands.empty?
2102
+ end
2103
+
2104
+ # If the prompt is non-nil, ask the user yes or no question first.
2105
+ def revert( prompt = nil )
2106
+ do_revert = true
2107
+
2108
+ current_text_file = @diakonos_home + '/current-buffer'
2109
+ @current_buffer.saveCopy( current_text_file )
2110
+ `#{@settings[ 'diff_command' ]} #{current_text_file} #{@current_buffer.name} > #{@diff_filename}`
2111
+ diff_buffer = openFile( @diff_filename )
2112
+
2113
+ if prompt != nil
2114
+ choice = getChoice(
2115
+ prompt,
2116
+ [ CHOICE_YES, CHOICE_NO ]
2117
+ )
2118
+ case choice
2119
+ when CHOICE_NO
2120
+ do_revert = false
2121
+ end
2122
+ end
2123
+
2124
+ closeFile( diff_buffer )
2125
+
2126
+ if do_revert
2127
+ openFile( @current_buffer.name, Buffer::READ_WRITE, FORCE_REVERT )
2128
+ end
2129
+ end
2130
+
2131
+ def saveFile( buffer = @current_buffer )
2132
+ buffer.save
2133
+ runHookProcs( :after_save, buffer )
2134
+ end
2135
+
2136
+ def saveFileAs
2137
+ if @current_buffer != nil and @current_buffer.name != nil
2138
+ path = File.expand_path( File.dirname( @current_buffer.name ) ) + "/"
2139
+ file = getUserInput( "Filename: ", @rlh_files, path )
2140
+ else
2141
+ file = getUserInput( "Filename: ", @rlh_files )
2142
+ end
2143
+ if file != nil
2144
+ #old_name = @current_buffer.name
2145
+ @current_buffer.save( file, PROMPT_OVERWRITE )
2146
+ #if not @current_buffer.modified
2147
+ # Save was okay.
2148
+ #@buffers.delete old_name
2149
+ #@buffers[ @current_buffer.name ] = @current_buffer
2150
+ #switchTo( @current_buffer )
2151
+ #end
2152
+ end
2153
+ end
2154
+
2155
+ def scrollDown
2156
+ @current_buffer.pitchView( @settings[ "view.scroll_amount" ] || 1 )
2157
+ updateStatusLine
2158
+ updateContextLine
2159
+ end
2160
+
2161
+ def scrollUp
2162
+ if @settings[ "view.scroll_amount" ] != nil
2163
+ @current_buffer.pitchView( -@settings[ "view.scroll_amount" ] )
2164
+ else
2165
+ @current_buffer.pitchView( -1 )
2166
+ end
2167
+ updateStatusLine
2168
+ updateContextLine
2169
+ end
2170
+
2171
+ def searchAndReplace( case_sensitive = CASE_INSENSITIVE )
2172
+ find( "down", case_sensitive, nil, ASK_REPLACEMENT )
2173
+ end
2174
+
2175
+ def seek( regexp_source, dir_str = "down" )
2176
+ if regexp_source != nil
2177
+ direction = dir_str.toDirection
2178
+ regexp = Regexp.new( regexp_source )
2179
+ @current_buffer.seek( regexp, direction )
2180
+ end
2181
+ end
2182
+
2183
+ def setBufferType( type_ = nil )
2184
+ if type_ == nil
2185
+ type = getUserInput "Content type: "
2186
+ else
2187
+ type = type_
2188
+ end
2189
+
2190
+ if type != nil
2191
+ if @current_buffer.setType( type )
2192
+ updateStatusLine
2193
+ updateContextLine
2194
+ end
2195
+ end
2196
+ end
2197
+
2198
+ # If read_only is nil, the read_only state of the current buffer is toggled.
2199
+ # Otherwise, the read_only state of the current buffer is set to read_only.
2200
+ def setReadOnly( read_only = nil )
2201
+ if read_only != nil
2202
+ @current_buffer.read_only = read_only
2203
+ else
2204
+ @current_buffer.read_only = ( not @current_buffer.read_only )
2205
+ end
2206
+ updateStatusLine
2207
+ end
2208
+
2209
+ def shell( command_ = nil )
2210
+ if command_ == nil
2211
+ command = getUserInput( "Command: ", @rlh_shell )
2212
+ else
2213
+ command = command_
2214
+ end
2215
+
2216
+ if command != nil
2217
+ command = subShellVariables( command )
2218
+
2219
+ result_file = @diakonos_home + "/shell-result.txt"
2220
+ File.open( result_file , "w" ) do |f|
2221
+ f.puts command
2222
+ f.puts
2223
+ Curses::close_screen
2224
+
2225
+ stdin, stdout, stderr = Open3.popen3( command )
2226
+ t1 = Thread.new do
2227
+ stdout.each_line do |line|
2228
+ f.puts line
2229
+ end
2230
+ end
2231
+ t2 = Thread.new do
2232
+ stderr.each_line do |line|
2233
+ f.puts line
2234
+ end
2235
+ end
2236
+
2237
+ t1.join
2238
+ t2.join
2239
+
2240
+ Curses::init_screen
2241
+ refreshAll
2242
+ end
2243
+ openFile result_file
2244
+ end
2245
+ end
2246
+
2247
+ def execute( command_ = nil )
2248
+ if command_ == nil
2249
+ command = getUserInput( "Command: ", @rlh_shell )
2250
+ else
2251
+ command = command_
2252
+ end
2253
+
2254
+ if command != nil
2255
+ command = subShellVariables( command )
2256
+
2257
+ Curses::close_screen
2258
+
2259
+ success = system( command )
2260
+ if not success
2261
+ result = "Could not execute: #{command}"
2262
+ else
2263
+ result = "Return code: #{$?}"
2264
+ end
2265
+
2266
+ Curses::init_screen
2267
+ refreshAll
2268
+
2269
+ setILine result
2270
+ end
2271
+ end
2272
+
2273
+ def pasteShellResult( command_ = nil )
2274
+ if command_ == nil
2275
+ command = getUserInput( "Command: ", @rlh_shell )
2276
+ else
2277
+ command = command_
2278
+ end
2279
+
2280
+ if command != nil
2281
+ command = subShellVariables( command )
2282
+
2283
+ Curses::close_screen
2284
+
2285
+ begin
2286
+ @current_buffer.paste( `#{command} 2<&1`.split( /\n/, -1 ) )
2287
+ rescue Exception => e
2288
+ debugLog e.message
2289
+ debugLog e.backtrace.join( "\n\t" )
2290
+ showException e
2291
+ end
2292
+
2293
+ Curses::init_screen
2294
+ refreshAll
2295
+ end
2296
+ end
2297
+
2298
+ # Send the Diakonos job to background, as if with Ctrl-Z
2299
+ def suspend
2300
+ Curses::close_screen
2301
+ Process.kill( "SIGSTOP", $PID )
2302
+ Curses::init_screen
2303
+ refreshAll
2304
+ end
2305
+
2306
+ def toggleMacroRecording( name = nil )
2307
+ if @macro_history != nil
2308
+ stopRecordingMacro
2309
+ else
2310
+ startRecordingMacro( name )
2311
+ end
2312
+ end
2313
+
2314
+ def switchToBufferNumber( buffer_number_ )
2315
+ buffer_number = buffer_number_.to_i
2316
+ return if buffer_number < 1
2317
+ buffer_name = bufferNumberToName( buffer_number )
2318
+ if buffer_name != nil
2319
+ switchTo( @buffers[ buffer_name ] )
2320
+ end
2321
+ end
2322
+
2323
+ def switchToNextBuffer
2324
+ buffer_number = bufferToNumber( @current_buffer )
2325
+ switchToBufferNumber( buffer_number + 1 )
2326
+ end
2327
+
2328
+ def switchToPreviousBuffer
2329
+ buffer_number = bufferToNumber( @current_buffer )
2330
+ switchToBufferNumber( buffer_number - 1 )
2331
+ end
2332
+
2333
+ def toggleBookmark
2334
+ @current_buffer.toggleBookmark
2335
+ end
2336
+
2337
+ def toggleSelection
2338
+ @current_buffer.toggleSelection
2339
+ updateStatusLine
2340
+ end
2341
+
2342
+ def toggleSessionSetting( key_ = nil, do_redraw = DONT_REDRAW )
2343
+ if key_ == nil
2344
+ key = getUserInput( "Setting: " )
2345
+ else
2346
+ key = key_
2347
+ end
2348
+
2349
+ if key != nil
2350
+ value = nil
2351
+ if @session_settings[ key ].class == TrueClass or @session_settings[ key ].class == FalseClass
2352
+ value = ! @session_settings[ key ]
2353
+ elsif @settings[ key ].class == TrueClass or @settings[ key ].class == FalseClass
2354
+ value = ! @settings[ key ]
2355
+ end
2356
+ if value != nil
2357
+ @session_settings[ key ] = value
2358
+ redraw if do_redraw
2359
+ setILine "#{key} = #{value}"
2360
+ end
2361
+ end
2362
+ end
2363
+
2364
+ def undo( buffer = @current_buffer )
2365
+ buffer.undo
2366
+ end
2367
+
2368
+ def unindent
2369
+ if( @current_buffer.changing_selection )
2370
+ @do_display = false
2371
+ mark = @current_buffer.selection_mark
2372
+ if mark.end_col > 0
2373
+ end_row = mark.end_row
2374
+ else
2375
+ end_row = mark.end_row - 1
2376
+ end
2377
+ (mark.start_row...end_row).each do |row|
2378
+ @current_buffer.unindent row, Buffer::DONT_DISPLAY
2379
+ end
2380
+ @do_display = true
2381
+ @current_buffer.unindent( end_row )
2382
+ else
2383
+ @current_buffer.unindent
2384
+ end
2385
+ end
2386
+
2387
+ def unundo( buffer = @current_buffer )
2388
+ buffer.unundo
2389
+ end
2390
+ end
2391
+
2392
+ end
2393
+
2394
+ if __FILE__ == $PROGRAM_NAME
2395
+ $diakonos = Diakonos::Diakonos.new( ARGV )
2396
+ $diakonos.start
2397
+ end