diakonos 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ class Array
2
+ def to_keychain_s
3
+ chain_str = ""
4
+ each do |key|
5
+ chain_str << key.keyString + " "
6
+ end
7
+ return chain_str
8
+ end
9
+ end
10
+
@@ -0,0 +1,6 @@
1
+ require 'diakonos/keycode'
2
+
3
+ class Bignum
4
+ include Diakonos::KeyCode
5
+ end
6
+
@@ -0,0 +1,51 @@
1
+ module Diakonos
2
+
3
+ class Bookmark
4
+ attr_reader :buffer, :row, :col, :name
5
+
6
+ def initialize( buffer, row, col, name = nil )
7
+ @buffer = buffer
8
+ @row = row
9
+ @col = col
10
+ @name = name
11
+ end
12
+
13
+ def == (other)
14
+ return false if other == nil
15
+ return ( @buffer == other.buffer and @row == other.row and @col == other.col )
16
+ end
17
+
18
+ def <=> (other)
19
+ return nil if other == nil
20
+ comparison = ( $diakonos.bufferToNumber( @buffer ) <=> $diakonos.bufferToNumber( other.buffer ) )
21
+ return comparison if comparison != 0
22
+ comparison = ( @row <=> other.row )
23
+ return comparison if comparison != 0
24
+ comparison = ( @col <=> other.col )
25
+ return comparison
26
+ end
27
+
28
+ def < (other)
29
+ return ( ( self <=> other ) < 0 )
30
+ end
31
+ def > (other)
32
+ return ( ( self <=> other ) > 0 )
33
+ end
34
+
35
+ def incRow( increment )
36
+ row += increment
37
+ end
38
+ def incCol( increment )
39
+ col += increment
40
+ end
41
+ def shift( row_inc, col_inc )
42
+ row += row_inc
43
+ col += col_inc
44
+ end
45
+
46
+ def to_s
47
+ return "[#{@name}|#{@buffer.name}:#{@row+1},#{@col+1}]"
48
+ end
49
+ end
50
+
51
+ end
@@ -0,0 +1,18 @@
1
+ class BufferHash < Hash
2
+ def [] ( key )
3
+ case key
4
+ when String
5
+ key = File.expand_path( key )
6
+ end
7
+ return super
8
+ end
9
+
10
+ def []= ( key, value )
11
+ case key
12
+ when String
13
+ key = File.expand_path( key )
14
+ end
15
+ super
16
+ end
17
+ end
18
+
@@ -0,0 +1,1700 @@
1
+ module Diakonos
2
+
3
+ class Buffer
4
+ attr_reader :name, :modified, :original_language, :changing_selection, :read_only,
5
+ :last_col, :last_row, :tab_size, :last_screen_x, :last_screen_y, :last_screen_col
6
+ attr_writer :desired_column, :read_only
7
+
8
+ SELECTION = 0
9
+ TYPING = true
10
+ STOPPED_TYPING = true
11
+ STILL_TYPING = false
12
+ NO_SNAPSHOT = true
13
+ DO_DISPLAY = true
14
+ DONT_DISPLAY = false
15
+ READ_ONLY = true
16
+ READ_WRITE = false
17
+ ROUND_DOWN = false
18
+ ROUND_UP = true
19
+ PAD_END = true
20
+ DONT_PAD_END = false
21
+ MATCH_CLOSE = true
22
+ MATCH_ANY = false
23
+ START_FROM_BEGINNING = -1
24
+ DO_PITCH_CURSOR = true
25
+ DONT_PITCH_CURSOR = false
26
+ CLEAR_STACK_POINTER = true
27
+ DONT_CLEAR_STACK_POINTER = false
28
+
29
+ # Set name to nil to create a buffer that is not associated with a file.
30
+ def initialize( diakonos, name, read_only = false )
31
+ @diakonos = diakonos
32
+ @name = name
33
+ @modified = false
34
+ @last_modification_check = Time.now
35
+
36
+ @buffer_states = Array.new
37
+ @cursor_states = Array.new
38
+ if @name != nil
39
+ @name = @name.subHome
40
+ if FileTest.exists? @name
41
+ @lines = IO.readlines( @name )
42
+ if ( @lines.length == 0 ) or ( @lines[ -1 ][ -1..-1 ] == "\n" )
43
+ @lines.push ""
44
+ end
45
+ @lines = @lines.collect do |line|
46
+ line.chomp
47
+ end
48
+ else
49
+ @lines = Array.new
50
+ @lines[ 0 ] = ""
51
+ end
52
+ else
53
+ @lines = Array.new
54
+ @lines[ 0 ] = ""
55
+ end
56
+ @current_buffer_state = 0
57
+
58
+ @top_line = 0
59
+ @left_column = 0
60
+ @desired_column = 0
61
+ @mark_anchor = nil
62
+ @text_marks = Array.new
63
+ @last_search_regexps = nil
64
+ @highlight_regexp = nil
65
+ @last_search = nil
66
+ @changing_selection = false
67
+ @typing = false
68
+ @last_col = 0
69
+ @last_screen_col = 0
70
+ @last_screen_y = 0
71
+ @last_screen_x = 0
72
+ @last_row = 0
73
+ @read_only = read_only
74
+ @bookmarks = Array.new
75
+ @lang_stack = Array.new
76
+ @cursor_stack = Array.new
77
+ @cursor_stack_pointer = nil
78
+
79
+ configure
80
+
81
+ if @settings[ "convert_tabs" ]
82
+ tabs_subbed = false
83
+ @lines.collect! do |line|
84
+ new_line = line.expandTabs( @tab_size )
85
+ tabs_subbed = ( tabs_subbed or new_line != line )
86
+ # Return value for collect:
87
+ new_line
88
+ end
89
+ @modified = ( @modified or tabs_subbed )
90
+ if tabs_subbed
91
+ @diakonos.setILine "(spaces substituted for tab characters)"
92
+ end
93
+ end
94
+
95
+ @buffer_states[ @current_buffer_state ] = @lines
96
+ @cursor_states[ @current_buffer_state ] = [ @last_row, @last_col ]
97
+ end
98
+
99
+ def configure(
100
+ language = (
101
+ @diakonos.getLanguageFromShaBang( @lines[ 0 ] ) or
102
+ @diakonos.getLanguageFromName( @name ) or
103
+ LANG_TEXT
104
+ )
105
+ )
106
+ reset_win_main
107
+ setLanguage language
108
+ @original_language = @language
109
+ end
110
+
111
+ def reset_win_main
112
+ @win_main = @diakonos.win_main
113
+ end
114
+
115
+ def setLanguage( language )
116
+ @settings = @diakonos.settings
117
+ @language = language
118
+ @token_regexps = ( @diakonos.token_regexps[ @language ] or Hash.new )
119
+ @close_token_regexps = ( @diakonos.close_token_regexps[ @language ] or Hash.new )
120
+ @token_formats = ( @diakonos.token_formats[ @language ] or Hash.new )
121
+ @indenters = @diakonos.indenters[ @language ]
122
+ @unindenters = @diakonos.unindenters[ @language ]
123
+ @preventers = @settings[ "lang.#{@language}.indent.preventers" ]
124
+ @auto_indent = @settings[ "lang.#{@language}.indent.auto" ]
125
+ @indent_size = ( @settings[ "lang.#{@language}.indent.size" ] or 4 )
126
+ @indent_roundup = ( @settings[ "lang.#{@language}.indent.roundup" ] or true )
127
+ @default_formatting = ( @settings[ "lang.#{@language}.format.default" ] or Curses::A_NORMAL )
128
+ @selection_formatting = ( @settings[ "lang.#{@language}.format.selection" ] or Curses::A_REVERSE )
129
+ @indent_ignore_charset = ( @settings[ "lang.#{@language}.indent.ignore.charset" ] or "" )
130
+ @tab_size = ( @settings[ "lang.#{@language}.tabsize" ] or DEFAULT_TAB_SIZE )
131
+ end
132
+ protected :setLanguage
133
+
134
+ def [] ( arg )
135
+ return @lines[ arg ]
136
+ end
137
+
138
+ def == (other)
139
+ return false if other == nil
140
+ return ( name == other.name )
141
+ end
142
+
143
+ def length
144
+ return @lines.length
145
+ end
146
+
147
+ def nice_name
148
+ return ( @name || @settings[ "status.unnamed_str" ] )
149
+ end
150
+
151
+ def display
152
+ return if not @diakonos.do_display
153
+
154
+ Thread.new do
155
+ #if $profiling
156
+ #RubyProf.start
157
+ #end
158
+
159
+ if @diakonos.display_mutex.try_lock
160
+ begin
161
+ Curses::curs_set 0
162
+
163
+ @continued_format_class = nil
164
+
165
+ @pen_down = true
166
+
167
+ # First, we have to "draw" off-screen, in order to check for opening of
168
+ # multi-line highlights.
169
+
170
+ # So, first look backwards from the @top_line to find the first opening
171
+ # regexp match, if any.
172
+ index = @top_line - 1
173
+ @lines[ [ 0, @top_line - @settings[ "view.lookback" ] ].max...@top_line ].reverse_each do |line|
174
+ open_index = -1
175
+ open_token_class = nil
176
+ open_match_text = nil
177
+
178
+ open_index, open_token_class, open_match_text = findOpeningMatch( line )
179
+
180
+ if open_token_class != nil
181
+ @pen_down = false
182
+ @lines[ index...@top_line ].each do |line|
183
+ printLine line
184
+ end
185
+ @pen_down = true
186
+
187
+ break
188
+ end
189
+
190
+ index = index - 1
191
+ end
192
+
193
+ # Draw each on-screen line.
194
+ y = 0
195
+ @lines[ @top_line...(@diakonos.main_window_height + @top_line) ].each_with_index do |line, row|
196
+ @win_main.setpos( y, 0 )
197
+ printLine line.expandTabs( @tab_size )
198
+ @win_main.setpos( y, 0 )
199
+ paintMarks @top_line + row
200
+ y += 1
201
+ end
202
+
203
+ # Paint the empty space below the file if the file is too short to fit in one screen.
204
+ ( y...@diakonos.main_window_height ).each do |y|
205
+ @win_main.setpos( y, 0 )
206
+ @win_main.attrset @default_formatting
207
+ linestr = " " * Curses::cols
208
+ if @settings[ "view.nonfilelines.visible" ]
209
+ linestr[ 0 ] = ( @settings[ "view.nonfilelines.character" ] or "~" )
210
+ end
211
+
212
+ @win_main.addstr_ linestr
213
+ end
214
+
215
+ @win_main.setpos( @last_screen_y , @last_screen_x )
216
+ @win_main.refresh
217
+
218
+ if @language != @original_language
219
+ setLanguage( @original_language )
220
+ end
221
+
222
+ Curses::curs_set 1
223
+ rescue Exception => e
224
+ $diakonos.log( "Display Exception:" )
225
+ $diakonos.log( e.message )
226
+ $diakonos.log( e.backtrace.join( "\n" ) )
227
+ showException e
228
+ end
229
+ @diakonos.display_mutex.unlock
230
+ @diakonos.displayDequeue
231
+ else
232
+ @diakonos.displayEnqueue( self )
233
+ end
234
+
235
+ #if $profiling
236
+ #result = RubyProf.stop
237
+ #printer = RubyProf::GraphHtmlPrinter.new( result )
238
+ #File.open( "#{ENV['HOME']}/svn/diakonos/profiling/diakonos-profile-#{Time.now.to_i}.html", 'w' ) do |f|
239
+ #printer.print( f )
240
+ #end
241
+ #end
242
+ end
243
+
244
+ end
245
+
246
+ def findOpeningMatch( line, match_close = true, bos_allowed = true )
247
+ open_index = line.length
248
+ open_token_class = nil
249
+ open_match_text = nil
250
+ match = nil
251
+ match_text = nil
252
+ @token_regexps.each do |token_class,regexp|
253
+ if match = regexp.match( line )
254
+ if match.length > 1
255
+ index = match.begin 1
256
+ match_text = match[ 1 ]
257
+ whole_match_index = match.begin 0
258
+ else
259
+ whole_match_index = index = match.begin( 0 )
260
+ match_text = match[ 0 ]
261
+ end
262
+ if ( not regexp.uses_bos ) or ( bos_allowed and ( whole_match_index == 0 ) )
263
+ if index < open_index
264
+ if ( ( not match_close ) or @close_token_regexps[ token_class ] != nil )
265
+ open_index = index
266
+ open_token_class = token_class
267
+ open_match_text = match_text
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ return [ open_index, open_token_class, open_match_text ]
275
+ end
276
+
277
+ def findClosingMatch( line_, regexp, bos_allowed = true, start_at = 0 )
278
+ close_match_text = nil
279
+ close_index = nil
280
+ if start_at > 0
281
+ line = line_[ start_at..-1 ]
282
+ else
283
+ line = line_
284
+ end
285
+ line.scan( regexp ) do |m|
286
+ match = Regexp.last_match
287
+ if match.length > 1
288
+ index = match.begin 1
289
+ match_text = match[ 1 ]
290
+ else
291
+ index = match.begin 0
292
+ match_text = match[ 0 ]
293
+ end
294
+ if ( not regexp.uses_bos ) or ( bos_allowed and ( index == 0 ) )
295
+ close_index = index
296
+ close_match_text = match_text
297
+ break
298
+ end
299
+ end
300
+
301
+ return [ close_index, close_match_text ]
302
+ end
303
+ protected :findClosingMatch
304
+
305
+ # @mark_start[ "col" ] is inclusive,
306
+ # @mark_end[ "col" ] is exclusive.
307
+ def recordMarkStartAndEnd
308
+ if @mark_anchor != nil
309
+ crow = @last_row
310
+ ccol = @last_col
311
+ anchor_first = true
312
+ if crow < @mark_anchor[ "row" ]
313
+ anchor_first = false
314
+ elsif crow > @mark_anchor[ "row" ]
315
+ anchor_first = true
316
+ else
317
+ if ccol < @mark_anchor[ "col" ]
318
+ anchor_first = false
319
+ end
320
+ end
321
+ if anchor_first
322
+ @text_marks[ SELECTION ] = TextMark.new(
323
+ @mark_anchor[ "row" ],
324
+ @mark_anchor[ "col" ],
325
+ crow,
326
+ ccol,
327
+ @selection_formatting
328
+ )
329
+ else
330
+ @text_marks[ SELECTION ] = TextMark.new(
331
+ crow,
332
+ ccol,
333
+ @mark_anchor[ "row" ],
334
+ @mark_anchor[ "col" ],
335
+ @selection_formatting
336
+ )
337
+ end
338
+ else
339
+ @text_marks[ SELECTION ] = nil
340
+ end
341
+ end
342
+
343
+ def selection_mark
344
+ return @text_marks[ SELECTION ]
345
+ end
346
+
347
+ # Prints text to the screen, truncating where necessary.
348
+ # Returns nil if the string is completely off-screen.
349
+ # write_cursor_col is buffer-relative, not screen-relative
350
+ def truncateOffScreen( string, write_cursor_col )
351
+ retval = string
352
+
353
+ # Truncate based on left edge of display area
354
+ if write_cursor_col < @left_column
355
+ retval = retval[ (@left_column - write_cursor_col)..-1 ]
356
+ write_cursor_col = @left_column
357
+ end
358
+
359
+ if retval != nil
360
+ # Truncate based on right edge of display area
361
+ if write_cursor_col + retval.length > @left_column + Curses::cols - 1
362
+ new_length = ( @left_column + Curses::cols - write_cursor_col )
363
+ if new_length <= 0
364
+ retval = nil
365
+ else
366
+ retval = retval[ 0...new_length ]
367
+ end
368
+ end
369
+ end
370
+
371
+ return ( retval == "" ? nil : retval )
372
+ end
373
+
374
+ # For debugging purposes
375
+ def quotedOrNil( str )
376
+ if str == nil
377
+ return "nil"
378
+ else
379
+ return "'#{str}'"
380
+ end
381
+ end
382
+
383
+ def paintMarks( row )
384
+ string = @lines[ row ][ @left_column ... @left_column + Curses::cols ]
385
+ return if string == nil or string == ""
386
+ string = string.expandTabs( @tab_size )
387
+ cury = @win_main.cury
388
+ curx = @win_main.curx
389
+
390
+ @text_marks.reverse_each do |text_mark|
391
+ if text_mark != nil
392
+ @win_main.attrset text_mark.formatting
393
+ if ( (text_mark.start_row + 1) .. (text_mark.end_row - 1) ) === row
394
+ @win_main.setpos( cury, curx )
395
+ @win_main.addstr_ string
396
+ elsif row == text_mark.start_row and row == text_mark.end_row
397
+ expanded_col = tabExpandedColumn( text_mark.start_col, row )
398
+ if expanded_col < @left_column + Curses::cols
399
+ left = [ expanded_col - @left_column, 0 ].max
400
+ right = tabExpandedColumn( text_mark.end_col, row ) - @left_column
401
+ if left < right
402
+ @win_main.setpos( cury, curx + left )
403
+ @win_main.addstr_ string[ left...right ]
404
+ end
405
+ end
406
+ elsif row == text_mark.start_row
407
+ expanded_col = tabExpandedColumn( text_mark.start_col, row )
408
+ if expanded_col < @left_column + Curses::cols
409
+ left = [ expanded_col - @left_column, 0 ].max
410
+ @win_main.setpos( cury, curx + left )
411
+ @win_main.addstr_ string[ left..-1 ]
412
+ end
413
+ elsif row == text_mark.end_row
414
+ right = tabExpandedColumn( text_mark.end_col, row ) - @left_column
415
+ @win_main.setpos( cury, curx )
416
+ @win_main.addstr_ string[ 0...right ]
417
+ else
418
+ # This row not in selection.
419
+ end
420
+ end
421
+ end
422
+ end
423
+
424
+ def printString( string, formatting = ( @token_formats[ @continued_format_class ] or @default_formatting ) )
425
+ return if not @pen_down
426
+ return if string == nil
427
+
428
+ @win_main.attrset formatting
429
+ @win_main.addstr_ string
430
+ end
431
+
432
+ # This method assumes that the cursor has been setup already at
433
+ # the left-most column of the correct on-screen row.
434
+ # It merely unintelligently prints the characters on the current curses line,
435
+ # refusing to print characters of the in-buffer line which are offscreen.
436
+ def printLine( line )
437
+ i = 0
438
+ substr = nil
439
+ index = nil
440
+ while i < line.length
441
+ substr = line[ i..-1 ]
442
+ if @continued_format_class != nil
443
+ close_index, close_match_text = findClosingMatch( substr, @close_token_regexps[ @continued_format_class ], i == 0 )
444
+
445
+ if close_match_text == nil
446
+ printString truncateOffScreen( substr, i )
447
+ printPaddingFrom( line.length )
448
+ i = line.length
449
+ else
450
+ end_index = close_index + close_match_text.length
451
+ printString truncateOffScreen( substr[ 0...end_index ], i )
452
+ @continued_format_class = nil
453
+ i += end_index
454
+ end
455
+ else
456
+ first_index, first_token_class, first_word = findOpeningMatch( substr, MATCH_ANY, i == 0 )
457
+
458
+ if @lang_stack.length > 0
459
+ prev_lang, close_token_class = @lang_stack[ -1 ]
460
+ close_index, close_match_text = findClosingMatch( substr, @diakonos.close_token_regexps[ prev_lang ][ close_token_class ], i == 0 )
461
+ if close_match_text != nil and close_index <= first_index
462
+ if close_index > 0
463
+ # Print any remaining text in the embedded language
464
+ printString truncateOffScreen( substr[ 0...close_index ], i )
465
+ i += substr[ 0...close_index ].length
466
+ end
467
+
468
+ @lang_stack.pop
469
+ setLanguage prev_lang
470
+
471
+ printString(
472
+ truncateOffScreen( substr[ close_index...(close_index + close_match_text.length) ], i ),
473
+ @token_formats[ close_token_class ]
474
+ )
475
+ i += close_match_text.length
476
+
477
+ # Continue printing from here.
478
+ next
479
+ end
480
+ end
481
+
482
+ if first_word != nil
483
+ if first_index > 0
484
+ # Print any preceding text in the default format
485
+ printString truncateOffScreen( substr[ 0...first_index ], i )
486
+ i += substr[ 0...first_index ].length
487
+ end
488
+ printString( truncateOffScreen( first_word, i ), @token_formats[ first_token_class ] )
489
+ i += first_word.length
490
+ if @close_token_regexps[ first_token_class ] != nil
491
+ if change_to = @settings[ "lang.#{@language}.tokens.#{first_token_class}.change_to" ]
492
+ @lang_stack.push [ @language, first_token_class ]
493
+ setLanguage change_to
494
+ else
495
+ @continued_format_class = first_token_class
496
+ end
497
+ end
498
+ else
499
+ printString truncateOffScreen( substr, i )
500
+ i += substr.length
501
+ break
502
+ end
503
+ end
504
+ end
505
+
506
+ printPaddingFrom i
507
+ end
508
+
509
+ def printPaddingFrom( col )
510
+ return if not @pen_down
511
+
512
+ if col < @left_column
513
+ remainder = Curses::cols
514
+ else
515
+ remainder = @left_column + Curses::cols - col
516
+ end
517
+
518
+ if remainder > 0
519
+ printString( " " * remainder )
520
+ end
521
+ end
522
+
523
+ def save( filename = nil, prompt_overwrite = DONT_PROMPT_OVERWRITE )
524
+ if filename != nil
525
+ name = filename.subHome
526
+ else
527
+ name = @name
528
+ end
529
+
530
+ if @read_only and FileTest.exists?( @name ) and FileTest.exists?( name ) and ( File.stat( @name ).ino == File.stat( name ).ino )
531
+ @diakonos.setILine "#{name} cannot be saved since it is read-only."
532
+ else
533
+ @name = name
534
+ @read_only = false
535
+ if @name == nil
536
+ @diakonos.saveFileAs
537
+ #elsif name.empty?
538
+ #@diakonos.setILine "(file not saved)"
539
+ #@name = nil
540
+ else
541
+ proceed = true
542
+
543
+ if prompt_overwrite and FileTest.exists? @name
544
+ proceed = false
545
+ choice = @diakonos.getChoice(
546
+ "Overwrite existing '#{@name}'?",
547
+ [ CHOICE_YES, CHOICE_NO ],
548
+ CHOICE_NO
549
+ )
550
+ case choice
551
+ when CHOICE_YES
552
+ proceed = true
553
+ when CHOICE_NO
554
+ proceed = false
555
+ end
556
+ end
557
+
558
+ if file_modified
559
+ proceed = ! @diakonos.revert( "File has been altered externally. Load on-disk version?" )
560
+ end
561
+
562
+ if proceed
563
+ File.open( @name, "w" ) do |f|
564
+ @lines[ 0..-2 ].each do |line|
565
+ f.puts line
566
+ end
567
+ if @lines[ -1 ] != ""
568
+ # No final newline character
569
+ f.print @lines[ -1 ]
570
+ f.print "\n" if @settings[ "eof_newline" ]
571
+ end
572
+ end
573
+ @last_modification_check = File.mtime( @name )
574
+
575
+ if @name == @diakonos.diakonos_conf
576
+ @diakonos.loadConfiguration
577
+ @diakonos.initializeDisplay
578
+ end
579
+
580
+ @modified = false
581
+
582
+ display
583
+ @diakonos.updateStatusLine
584
+ end
585
+ end
586
+ end
587
+ end
588
+
589
+ # Returns true on successful write.
590
+ def saveCopy( filename )
591
+ return false if filename.nil?
592
+
593
+ name = filename.subHome
594
+
595
+ File.open( name, "w" ) do |f|
596
+ @lines[ 0..-2 ].each do |line|
597
+ f.puts line
598
+ end
599
+ if @lines[ -1 ] != ""
600
+ # No final newline character
601
+ f.print @lines[ -1 ]
602
+ f.print "\n" if @settings[ "eof_newline" ]
603
+ end
604
+ end
605
+
606
+ return true
607
+ end
608
+
609
+ def replaceChar( c )
610
+ row = @last_row
611
+ col = @last_col
612
+ takeSnapshot( TYPING )
613
+ @lines[ row ][ col ] = c
614
+ setModified
615
+ end
616
+
617
+ def insertChar( c )
618
+ row = @last_row
619
+ col = @last_col
620
+ takeSnapshot( TYPING )
621
+ line = @lines[ row ]
622
+ @lines[ row ] = line[ 0...col ] + c.chr + line[ col..-1 ]
623
+ setModified
624
+ end
625
+
626
+ def insertString( str )
627
+ row = @last_row
628
+ col = @last_col
629
+ takeSnapshot( TYPING )
630
+ line = @lines[ row ]
631
+ @lines[ row ] = line[ 0...col ] + str + line[ col..-1 ]
632
+ setModified
633
+ end
634
+
635
+ # x and y are given window-relative, not buffer-relative.
636
+ def delete
637
+ if selection_mark != nil
638
+ deleteSelection
639
+ else
640
+ row = @last_row
641
+ col = @last_col
642
+ if ( row >= 0 ) and ( col >= 0 )
643
+ line = @lines[ row ]
644
+ if col == line.length
645
+ if row < @lines.length - 1
646
+ # Delete newline, and concat next line
647
+ takeSnapshot( TYPING )
648
+ @lines[ row ] << @lines.delete_at( row + 1 )
649
+ cursorTo( @last_row, @last_col )
650
+ setModified
651
+ end
652
+ else
653
+ takeSnapshot( TYPING )
654
+ @lines[ row ] = line[ 0...col ] + line[ (col + 1)..-1 ]
655
+ setModified
656
+ end
657
+ end
658
+ end
659
+ end
660
+
661
+ def collapseWhitespace
662
+ removeSelection( DONT_DISPLAY ) if selection_mark != nil
663
+
664
+ line = @lines[ @last_row ]
665
+ head = line[ 0...@last_col ]
666
+ tail = line[ @last_col..-1 ]
667
+ new_head = head.sub( /\s+$/, '' )
668
+ new_line = new_head + tail.sub( /^\s+/, ' ' )
669
+ if new_line != line
670
+ takeSnapshot( TYPING )
671
+ @lines[ @last_row ] = new_line
672
+ cursorTo( @last_row, @last_col - ( head.length - new_head.length ) )
673
+ setModified
674
+ end
675
+ end
676
+
677
+ def deleteLine
678
+ removeSelection( DONT_DISPLAY ) if selection_mark != nil
679
+
680
+ row = @last_row
681
+ takeSnapshot
682
+ retval = nil
683
+ if @lines.length == 1
684
+ retval = @lines[ 0 ]
685
+ @lines[ 0 ] = ""
686
+ else
687
+ retval = @lines[ row ]
688
+ @lines.delete_at row
689
+ end
690
+ cursorTo( row, 0 )
691
+ setModified
692
+
693
+ return retval
694
+ end
695
+
696
+ def deleteToEOL
697
+ removeSelection( DONT_DISPLAY ) if selection_mark != nil
698
+
699
+ row = @last_row
700
+ col = @last_col
701
+ takeSnapshot
702
+ retval = [ @lines[ row ][ col..-1 ] ]
703
+ @lines[ row ] = @lines[ row ][ 0...col ]
704
+ setModified
705
+
706
+ return retval
707
+ end
708
+
709
+ def carriageReturn
710
+ takeSnapshot
711
+ row = @last_row
712
+ col = @last_col
713
+ @lines = @lines[ 0...row ] +
714
+ [ @lines[ row ][ 0...col ] ] +
715
+ [ @lines[ row ][ col..-1 ] ] +
716
+ @lines[ (row+1)..-1 ]
717
+ cursorTo( row + 1, 0 )
718
+ parsedIndent if @auto_indent
719
+ setModified
720
+ end
721
+
722
+ def lineAt( y )
723
+ row = @top_line + y
724
+ if row < 0
725
+ return nil
726
+ else
727
+ return @lines[ row ]
728
+ end
729
+ end
730
+
731
+ # Returns true iff the given column, x, is less than the length of the given line, y.
732
+ def inLine( x, y )
733
+ return ( x + @left_column < lineAt( y ).length )
734
+ end
735
+
736
+ # Translates the window column, x, to a buffer-relative column index.
737
+ def columnOf( x )
738
+ return @left_column + x
739
+ end
740
+
741
+ # Translates the window row, y, to a buffer-relative row index.
742
+ def rowOf( y )
743
+ return @top_line + y
744
+ end
745
+
746
+ # Returns nil if the row is off-screen.
747
+ def rowToY( row )
748
+ return nil if row == nil
749
+ y = row - @top_line
750
+ y = nil if ( y < 0 ) or ( y > @top_line + @diakonos.main_window_height - 1 )
751
+ return y
752
+ end
753
+
754
+ # Returns nil if the column is off-screen.
755
+ def columnToX( col )
756
+ return nil if col == nil
757
+ x = col - @left_column
758
+ x = nil if ( x < 0 ) or ( x > @left_column + Curses::cols - 1 )
759
+ return x
760
+ end
761
+
762
+ def currentRow
763
+ return @last_row
764
+ end
765
+
766
+ def currentColumn
767
+ return @last_col
768
+ end
769
+
770
+ # Returns the amount the view was actually panned.
771
+ def panView( x = 1, do_display = DO_DISPLAY )
772
+ old_left_column = @left_column
773
+ @left_column = [ @left_column + x, 0 ].max
774
+ recordMarkStartAndEnd
775
+ display if do_display
776
+ return ( @left_column - old_left_column )
777
+ end
778
+
779
+ # Returns the amount the view was actually pitched.
780
+ def pitchView( y = 1, do_pitch_cursor = DONT_PITCH_CURSOR, do_display = DO_DISPLAY )
781
+ old_top_line = @top_line
782
+ new_top_line = @top_line + y
783
+
784
+ if new_top_line < 0
785
+ @top_line = 0
786
+ elsif new_top_line + @diakonos.main_window_height > @lines.length
787
+ @top_line = [ @lines.length - @diakonos.main_window_height, 0 ].max
788
+ else
789
+ @top_line = new_top_line
790
+ end
791
+
792
+ old_row = @last_row
793
+ old_col = @last_col
794
+
795
+ changed = ( @top_line - old_top_line )
796
+ if changed != 0 and do_pitch_cursor
797
+ @last_row += changed
798
+ end
799
+
800
+ height = [ @diakonos.main_window_height, @lines.length ].min
801
+
802
+ @last_row = @last_row.fit( @top_line, @top_line + height - 1 )
803
+ if @last_row - @top_line < @settings[ "view.margin.y" ]
804
+ @last_row = @top_line + @settings[ "view.margin.y" ]
805
+ @last_row = @last_row.fit( @top_line, @top_line + height - 1 )
806
+ elsif @top_line + height - 1 - @last_row < @settings[ "view.margin.y" ]
807
+ @last_row = @top_line + height - 1 - @settings[ "view.margin.y" ]
808
+ @last_row = @last_row.fit( @top_line, @top_line + height - 1 )
809
+ end
810
+ @last_col = @last_col.fit( @left_column, [ @left_column + Curses::cols - 1, @lines[ @last_row ].length ].min )
811
+ @last_screen_y = @last_row - @top_line
812
+ @last_screen_x = tabExpandedColumn( @last_col, @last_row ) - @left_column
813
+
814
+ recordMarkStartAndEnd
815
+
816
+ if changed != 0
817
+ highlightMatches
818
+ if @diakonos.there_was_non_movement
819
+ pushCursorState( old_top_line, old_row, old_col )
820
+ end
821
+ end
822
+
823
+ display if do_display
824
+
825
+ return changed
826
+ end
827
+
828
+ def pushCursorState( top_line, row, col, clear_stack_pointer = CLEAR_STACK_POINTER )
829
+ new_state = {
830
+ :top_line => top_line,
831
+ :row => row,
832
+ :col => col
833
+ }
834
+ if not @cursor_stack.include? new_state
835
+ @cursor_stack << new_state
836
+ if clear_stack_pointer
837
+ @cursor_stack_pointer = nil
838
+ end
839
+ @diakonos.clearNonMovementFlag
840
+ end
841
+ end
842
+
843
+ # Returns true iff the cursor changed positions in the buffer.
844
+ def cursorTo( row, col, do_display = DONT_DISPLAY, stopped_typing = STOPPED_TYPING, adjust_row = ADJUST_ROW )
845
+ old_last_row = @last_row
846
+ old_last_col = @last_col
847
+
848
+ row = row.fit( 0, @lines.length - 1 )
849
+
850
+ if col < 0
851
+ if adjust_row
852
+ if row > 0
853
+ row = row - 1
854
+ col = @lines[ row ].length
855
+ else
856
+ col = 0
857
+ end
858
+ else
859
+ col = 0
860
+ end
861
+ elsif col > @lines[ row ].length
862
+ if adjust_row
863
+ if row < @lines.length - 1
864
+ row = row + 1
865
+ col = 0
866
+ else
867
+ col = @lines[ row ].length
868
+ end
869
+ else
870
+ col = @lines[ row ].length
871
+ end
872
+ end
873
+
874
+ if adjust_row
875
+ @desired_column = col
876
+ else
877
+ goto_col = [ @desired_column, @lines[ row ].length ].min
878
+ if col < goto_col
879
+ col = goto_col
880
+ end
881
+ end
882
+
883
+ new_col = tabExpandedColumn( col, row )
884
+ view_changed = showCharacter( row, new_col )
885
+ @last_screen_y = row - @top_line
886
+ @last_screen_x = new_col - @left_column
887
+
888
+ @typing = false if stopped_typing
889
+ @last_row = row
890
+ @last_col = col
891
+ @last_screen_col = new_col
892
+ changed = ( @last_row != old_last_row or @last_col != old_last_col )
893
+ if changed
894
+ recordMarkStartAndEnd
895
+
896
+ removed = false
897
+ if not @changing_selection and selection_mark != nil
898
+ removeSelection( DONT_DISPLAY )
899
+ removed = true
900
+ end
901
+ if removed or ( do_display and ( selection_mark != nil or view_changed ) )
902
+ display
903
+ else
904
+ @diakonos.display_mutex.synchronize do
905
+ @win_main.setpos( @last_screen_y, @last_screen_x )
906
+ end
907
+ end
908
+ @diakonos.updateStatusLine
909
+ @diakonos.updateContextLine
910
+ end
911
+
912
+ return changed
913
+ end
914
+
915
+ def cursorReturn( direction )
916
+ delta = 0
917
+ if @cursor_stack_pointer.nil?
918
+ pushCursorState( @top_line, @last_row, @last_col, DONT_CLEAR_STACK_POINTER )
919
+ delta = 1
920
+ end
921
+ case direction
922
+ when :forward
923
+ @cursor_stack_pointer = ( @cursor_stack_pointer || 0 ) + 1
924
+ #when :backward
925
+ else
926
+ @cursor_stack_pointer = ( @cursor_stack_pointer || @cursor_stack.length ) - 1 - delta
927
+ end
928
+
929
+ return_pointer = @cursor_stack_pointer
930
+
931
+ if @cursor_stack_pointer < 0
932
+ return_pointer = @cursor_stack_pointer = 0
933
+ elsif @cursor_stack_pointer >= @cursor_stack.length
934
+ return_pointer = @cursor_stack_pointer = @cursor_stack.length - 1
935
+ else
936
+ cursor_state = @cursor_stack[ @cursor_stack_pointer ]
937
+ if cursor_state != nil
938
+ pitchView( cursor_state[ :top_line ] - @top_line, DONT_PITCH_CURSOR, DO_DISPLAY )
939
+ cursorTo( cursor_state[ :row ], cursor_state[ :col ] )
940
+ @diakonos.updateStatusLine
941
+ end
942
+ end
943
+
944
+ return return_pointer, @cursor_stack.size
945
+ end
946
+
947
+ def tabExpandedColumn( col, row )
948
+ delta = 0
949
+ line = @lines[ row ]
950
+ for i in 0...col
951
+ if line[ i ] == TAB
952
+ delta += ( @tab_size - ( (i+delta) % @tab_size ) ) - 1
953
+ end
954
+ end
955
+ return ( col + delta )
956
+ end
957
+
958
+ def cursorToEOF
959
+ cursorTo( @lines.length - 1, @lines[ -1 ].length, DO_DISPLAY )
960
+ end
961
+
962
+ def cursorToBOL
963
+ row = @last_row
964
+ case @settings[ "bol_behaviour" ]
965
+ when BOL_ZERO
966
+ col = 0
967
+ when BOL_FIRST_CHAR
968
+ col = ( ( @lines[ row ] =~ /\S/ ) or 0 )
969
+ when BOL_ALT_ZERO
970
+ if @last_col == 0
971
+ col = ( @lines[ row ] =~ /\S/ )
972
+ else
973
+ col = 0
974
+ end
975
+ #when BOL_ALT_FIRST_CHAR
976
+ else
977
+ first_char_col = ( ( @lines[ row ] =~ /\S/ ) or 0 )
978
+ if @last_col == first_char_col
979
+ col = 0
980
+ else
981
+ col = first_char_col
982
+ end
983
+ end
984
+ cursorTo( row, col, DO_DISPLAY )
985
+ end
986
+
987
+ # Top of view
988
+ def cursorToTOV
989
+ cursorTo( rowOf( 0 ), @last_col, DO_DISPLAY )
990
+ end
991
+ # Bottom of view
992
+ def cursorToBOV
993
+ cursorTo( rowOf( 0 + @diakonos.main_window_height - 1 ), @last_col, DO_DISPLAY )
994
+ end
995
+
996
+ # col and row are given relative to the buffer, not any window or screen.
997
+ # Returns true if the view changed positions.
998
+ def showCharacter( row, col )
999
+ old_top_line = @top_line
1000
+ old_left_column = @left_column
1001
+
1002
+ while row < @top_line + @settings[ "view.margin.y" ]
1003
+ amount = (-1) * @settings[ "view.jump.y" ]
1004
+ break if( pitchView( amount, DONT_PITCH_CURSOR, DONT_DISPLAY ) != amount )
1005
+ end
1006
+ while row > @top_line + @diakonos.main_window_height - 1 - @settings[ "view.margin.y" ]
1007
+ amount = @settings[ "view.jump.y" ]
1008
+ break if( pitchView( amount, DONT_PITCH_CURSOR, DONT_DISPLAY ) != amount )
1009
+ end
1010
+
1011
+ while col < @left_column + @settings[ "view.margin.x" ]
1012
+ amount = (-1) * @settings[ "view.jump.x" ]
1013
+ break if( panView( amount, DONT_DISPLAY ) != amount )
1014
+ end
1015
+ while col > @left_column + @diakonos.main_window_width - @settings[ "view.margin.x" ] - 2
1016
+ amount = @settings[ "view.jump.x" ]
1017
+ break if( panView( amount, DONT_DISPLAY ) != amount )
1018
+ end
1019
+
1020
+ return ( @top_line != old_top_line or @left_column != old_left_column )
1021
+ end
1022
+
1023
+ def setIndent( row, level, do_display = DO_DISPLAY )
1024
+ @lines[ row ] =~ /^([\s#{@indent_ignore_charset}]*)(.*)$/
1025
+ current_indent_text = ( $1 or "" )
1026
+ rest = ( $2 or "" )
1027
+ current_indent_text.gsub!( /\t/, ' ' * @tab_size )
1028
+ indentation = @indent_size * [ level, 0 ].max
1029
+ if current_indent_text.length >= indentation
1030
+ indent_text = current_indent_text[ 0...indentation ]
1031
+ else
1032
+ indent_text = current_indent_text + " " * ( indentation - current_indent_text.length )
1033
+ end
1034
+ if @settings[ "lang.#{@language}.indent.using_tabs" ]
1035
+ num_tabs = 0
1036
+ indent_text.gsub!( / {#{@tab_size}}/ ) { |match|
1037
+ num_tabs += 1
1038
+ "\t"
1039
+ }
1040
+ indentation -= num_tabs * ( @tab_size - 1 )
1041
+ end
1042
+
1043
+ takeSnapshot( TYPING ) if do_display
1044
+ @lines[ row ] = indent_text + rest
1045
+ cursorTo( row, indentation ) if do_display
1046
+ setModified
1047
+ end
1048
+
1049
+ def parsedIndent( row = @last_row, do_display = DO_DISPLAY )
1050
+ if row == 0
1051
+ level = 0
1052
+ else
1053
+ # Look upwards for the nearest line on which to base this line's indentation.
1054
+ i = 1
1055
+ while ( @lines[ row - i ] =~ /^[\s#{@indent_ignore_charset}]*$/ ) or
1056
+ ( @lines[ row - i ] =~ @settings[ "lang.#{@language}.indent.ignore" ] )
1057
+ i += 1
1058
+ end
1059
+ if row - i < 0
1060
+ level = 0
1061
+ else
1062
+ prev_line = @lines[ row - i ]
1063
+ level = prev_line.indentation_level( @indent_size, @indent_roundup, @tab_size, @indent_ignore_charset )
1064
+
1065
+ line = @lines[ row ]
1066
+ if @preventers != nil
1067
+ prev_line = prev_line.gsub( @preventers, "" )
1068
+ line = line.gsub( @preventers, "" )
1069
+ end
1070
+
1071
+ indenter_index = ( prev_line =~ @indenters )
1072
+ if indenter_index
1073
+ level += 1
1074
+ unindenter_index = (prev_line =~ @unindenters)
1075
+ if unindenter_index and unindenter_index != indenter_index
1076
+ level += -1
1077
+ end
1078
+ end
1079
+ if line =~ @unindenters
1080
+ level += -1
1081
+ end
1082
+ end
1083
+ end
1084
+
1085
+ setIndent( row, level, do_display )
1086
+
1087
+ end
1088
+
1089
+ def indent( row = @last_row, do_display = DO_DISPLAY )
1090
+ level = @lines[ row ].indentation_level( @indent_size, @indent_roundup, @tab_size )
1091
+ setIndent( row, level + 1, do_display )
1092
+ end
1093
+
1094
+ def unindent( row = @last_row, do_display = DO_DISPLAY )
1095
+ level = @lines[ row ].indentation_level( @indent_size, @indent_roundup, @tab_size )
1096
+ setIndent( row, level - 1, do_display )
1097
+ end
1098
+
1099
+ def anchorSelection( row = @last_row, col = @last_col, do_display = DO_DISPLAY )
1100
+ @mark_anchor = ( @mark_anchor or Hash.new )
1101
+ @mark_anchor[ "row" ] = row
1102
+ @mark_anchor[ "col" ] = col
1103
+ recordMarkStartAndEnd
1104
+ @changing_selection = true
1105
+ display if do_display
1106
+ end
1107
+
1108
+ def removeSelection( do_display = DO_DISPLAY )
1109
+ return if selection_mark.nil?
1110
+ @mark_anchor = nil
1111
+ recordMarkStartAndEnd
1112
+ @changing_selection = false
1113
+ @last_finding = nil
1114
+ display if do_display
1115
+ end
1116
+
1117
+ def toggleSelection
1118
+ if @changing_selection
1119
+ removeSelection
1120
+ else
1121
+ anchorSelection
1122
+ end
1123
+ end
1124
+
1125
+ def copySelection
1126
+ return selected_text
1127
+ end
1128
+ def selected_text
1129
+ selection = selection_mark
1130
+ if selection == nil
1131
+ text = nil
1132
+ elsif selection.start_row == selection.end_row
1133
+ text = [ @lines[ selection.start_row ][ selection.start_col...selection.end_col ] ]
1134
+ else
1135
+ text = [ @lines[ selection.start_row ][ selection.start_col..-1 ] ] +
1136
+ ( @lines[ (selection.start_row + 1) .. (selection.end_row - 1) ] or [] ) +
1137
+ [ @lines[ selection.end_row ][ 0...selection.end_col ] ]
1138
+ end
1139
+
1140
+ return text
1141
+ end
1142
+ def selected_string
1143
+ lines = selected_text
1144
+ if lines
1145
+ lines.join( "\n" )
1146
+ else
1147
+ nil
1148
+ end
1149
+ end
1150
+
1151
+ def deleteSelection( do_display = DO_DISPLAY )
1152
+ return if @text_marks[ SELECTION ] == nil
1153
+
1154
+ takeSnapshot
1155
+
1156
+ selection = @text_marks[ SELECTION ]
1157
+ start_row = selection.start_row
1158
+ start_col = selection.start_col
1159
+ start_line = @lines[ start_row ]
1160
+
1161
+ if selection.end_row == selection.start_row
1162
+ @lines[ start_row ] = start_line[ 0...start_col ] + start_line[ selection.end_col..-1 ]
1163
+ else
1164
+ end_line = @lines[ selection.end_row ]
1165
+ @lines[ start_row ] = start_line[ 0...start_col ] + end_line[ selection.end_col..-1 ]
1166
+ @lines = @lines[ 0..start_row ] + @lines[ (selection.end_row + 1)..-1 ]
1167
+ end
1168
+
1169
+ cursorTo( start_row, start_col )
1170
+ removeSelection( DONT_DISPLAY )
1171
+ setModified( do_display )
1172
+ end
1173
+
1174
+ # text is an array of Strings
1175
+ def paste( text )
1176
+ return if text == nil
1177
+
1178
+ if not text.kind_of? Array
1179
+ s = text.to_s
1180
+ if s.include?( "\n" )
1181
+ text = s.split( "\n", -1 )
1182
+ else
1183
+ text = [ s ]
1184
+ end
1185
+ end
1186
+
1187
+ takeSnapshot
1188
+
1189
+ deleteSelection( DONT_DISPLAY )
1190
+
1191
+ row = @last_row
1192
+ col = @last_col
1193
+ line = @lines[ row ]
1194
+ if text.length == 1
1195
+ @lines[ row ] = line[ 0...col ] + text[ 0 ] + line[ col..-1 ]
1196
+ cursorTo( @last_row, @last_col + text[ 0 ].length )
1197
+ elsif text.length > 1
1198
+ @lines[ row ] = line[ 0...col ] + text[ 0 ]
1199
+ @lines[ row + 1, 0 ] = text[ -1 ] + line[ col..-1 ]
1200
+ @lines[ row + 1, 0 ] = text[ 1..-2 ]
1201
+ cursorTo( @last_row + text.length - 1, columnOf( text[ -1 ].length ) )
1202
+ end
1203
+
1204
+ setModified
1205
+ end
1206
+
1207
+ # Takes an array of Regexps, which represents a user-provided regexp,
1208
+ # split across newline characters. Once the first element is found,
1209
+ # each successive element must match against lines following the first
1210
+ # element.
1211
+ def find( regexps, direction = :down, replacement = nil )
1212
+ return if regexps.nil?
1213
+ regexp = regexps[ 0 ]
1214
+ return if regexp == nil or regexp == //
1215
+
1216
+ if direction == :opposite
1217
+ case @last_search_direction
1218
+ when :up
1219
+ direction = :down
1220
+ else
1221
+ direction = :up
1222
+ end
1223
+ end
1224
+ @last_search_regexps = regexps
1225
+ @last_search_direction = direction
1226
+
1227
+ finding = nil
1228
+ wrapped = false
1229
+
1230
+ catch :found do
1231
+
1232
+ if direction == :down
1233
+ # Check the current row first.
1234
+
1235
+ if index = @lines[ @last_row ].index( regexp, ( @last_finding ? @last_finding.start_col : @last_col ) + 1 )
1236
+ found_text = Regexp.last_match[ 0 ]
1237
+ finding = Finding.new( @last_row, index, @last_row, index + found_text.length )
1238
+ if finding.match( regexps, @lines )
1239
+ throw :found
1240
+ else
1241
+ finding = nil
1242
+ end
1243
+ end
1244
+
1245
+ # Check below the cursor.
1246
+
1247
+ ( (@last_row + 1)...@lines.length ).each do |i|
1248
+ if index = @lines[ i ].index( regexp )
1249
+ found_text = Regexp.last_match[ 0 ]
1250
+ finding = Finding.new( i, index, i, index + found_text.length )
1251
+ if finding.match( regexps, @lines )
1252
+ throw :found
1253
+ else
1254
+ finding = nil
1255
+ end
1256
+ end
1257
+ end
1258
+
1259
+ # Wrap around.
1260
+
1261
+ wrapped = true
1262
+
1263
+ ( 0...@last_row ).each do |i|
1264
+ if index = @lines[ i ].index( regexp )
1265
+ found_text = Regexp.last_match[ 0 ]
1266
+ finding = Finding.new( i, index, i, index + found_text.length )
1267
+ if finding.match( regexps, @lines )
1268
+ throw :found
1269
+ else
1270
+ finding = nil
1271
+ end
1272
+ end
1273
+ end
1274
+
1275
+ # And finally, the other side of the current row.
1276
+
1277
+ #if index = @lines[ @last_row ].index( regexp, ( @last_finding ? @last_finding.start_col : @last_col ) - 1 )
1278
+ if index = @lines[ @last_row ].index( regexp )
1279
+ if index <= ( @last_finding ? @last_finding.start_col : @last_col )
1280
+ found_text = Regexp.last_match[ 0 ]
1281
+ finding = Finding.new( @last_row, index, @last_row, index + found_text.length )
1282
+ if finding.match( regexps, @lines )
1283
+ throw :found
1284
+ else
1285
+ finding = nil
1286
+ end
1287
+ end
1288
+ end
1289
+
1290
+ elsif direction == :up
1291
+ # Check the current row first.
1292
+
1293
+ col_to_check = ( @last_finding ? @last_finding.end_col : @last_col ) - 1
1294
+ if ( col_to_check >= 0 ) and ( index = @lines[ @last_row ][ 0...col_to_check ].rindex( regexp ) )
1295
+ found_text = Regexp.last_match[ 0 ]
1296
+ finding = Finding.new( @last_row, index, @last_row, index + found_text.length )
1297
+ if finding.match( regexps, @lines )
1298
+ throw :found
1299
+ else
1300
+ finding = nil
1301
+ end
1302
+ end
1303
+
1304
+ # Check above the cursor.
1305
+
1306
+ (@last_row - 1).downto( 0 ) do |i|
1307
+ if index = @lines[ i ].rindex( regexp )
1308
+ found_text = Regexp.last_match[ 0 ]
1309
+ finding = Finding.new( i, index, i, index + found_text.length )
1310
+ if finding.match( regexps, @lines )
1311
+ throw :found
1312
+ else
1313
+ finding = nil
1314
+ end
1315
+ end
1316
+ end
1317
+
1318
+ # Wrap around.
1319
+
1320
+ wrapped = true
1321
+
1322
+ (@lines.length - 1).downto(@last_row + 1) do |i|
1323
+ if index = @lines[ i ].rindex( regexp )
1324
+ found_text = Regexp.last_match[ 0 ]
1325
+ finding = Finding.new( i, index, i, index + found_text.length )
1326
+ if finding.match( regexps, @lines )
1327
+ throw :found
1328
+ else
1329
+ finding = nil
1330
+ end
1331
+ end
1332
+ end
1333
+
1334
+ # And finally, the other side of the current row.
1335
+
1336
+ search_col = ( @last_finding ? @last_finding.start_col : @last_col ) + 1
1337
+ if index = @lines[ @last_row ].rindex( regexp )
1338
+ if index > search_col
1339
+ found_text = Regexp.last_match[ 0 ]
1340
+ finding = Finding.new( @last_row, index, @last_row, index + found_text.length )
1341
+ if finding.match( regexps, @lines )
1342
+ throw :found
1343
+ else
1344
+ finding = nil
1345
+ end
1346
+ end
1347
+ end
1348
+ end
1349
+ end
1350
+
1351
+ if finding != nil
1352
+ @diakonos.setILine( "(search wrapped around BOF/EOF)" ) if wrapped
1353
+
1354
+ removeSelection( DONT_DISPLAY )
1355
+ @last_finding = finding
1356
+ if @settings[ "found_cursor_start" ]
1357
+ anchorSelection( finding.end_row, finding.end_col, DONT_DISPLAY )
1358
+ cursorTo( finding.start_row, finding.start_col )
1359
+ else
1360
+ anchorSelection( finding.start_row, finding.start_col, DONT_DISPLAY )
1361
+ cursorTo( finding.end_row, finding.end_col )
1362
+ end
1363
+
1364
+ @changing_selection = false
1365
+
1366
+ if regexps.length == 1
1367
+ @highlight_regexp = regexp
1368
+ highlightMatches
1369
+ else
1370
+ clearMatches
1371
+ end
1372
+ display
1373
+
1374
+ if replacement != nil
1375
+ choice = @diakonos.getChoice(
1376
+ "Replace?",
1377
+ [ CHOICE_YES, CHOICE_NO, CHOICE_ALL, CHOICE_CANCEL ],
1378
+ CHOICE_YES
1379
+ )
1380
+ case choice
1381
+ when CHOICE_YES
1382
+ paste [ replacement ]
1383
+ find( regexps, direction, replacement )
1384
+ when CHOICE_ALL
1385
+ replaceAll( regexp, replacement )
1386
+ when CHOICE_NO
1387
+ find( regexps, direction, replacement )
1388
+ when CHOICE_CANCEL
1389
+ # Do nothing further.
1390
+ end
1391
+ end
1392
+ else
1393
+ @diakonos.setILine "/#{regexp.source}/ not found."
1394
+ end
1395
+ end
1396
+
1397
+ def replaceAll( regexp, replacement )
1398
+ return if( regexp == nil or replacement == nil )
1399
+
1400
+ @lines = @lines.collect { |line|
1401
+ line.gsub( regexp, replacement )
1402
+ }
1403
+ setModified
1404
+
1405
+ clearMatches
1406
+
1407
+ display
1408
+ end
1409
+
1410
+ def highlightMatches
1411
+ if @highlight_regexp != nil
1412
+ found_marks = @lines[ @top_line...(@top_line + @diakonos.main_window_height) ].grep_indices( @highlight_regexp ).collect do |line_index, start_col, end_col|
1413
+ TextMark.new( @top_line + line_index, start_col, @top_line + line_index, end_col, @settings[ "lang.#{@language}.format.found" ] )
1414
+ end
1415
+ #@text_marks = [ nil ] + found_marks
1416
+ @text_marks = [ @text_marks[ 0 ] ] + found_marks
1417
+ end
1418
+ end
1419
+
1420
+ def clearMatches( do_display = DONT_DISPLAY )
1421
+ selection = @text_marks[ SELECTION ]
1422
+ @text_marks = Array.new
1423
+ @text_marks[ SELECTION ] = selection
1424
+ @highlight_regexp = nil
1425
+ display if do_display
1426
+ end
1427
+
1428
+ def findAgain( last_search_regexps, direction = @last_search_direction )
1429
+ if @last_search_regexps == nil
1430
+ @last_search_regexps = last_search_regexps
1431
+ end
1432
+ find( @last_search_regexps, direction ) if( @last_search_regexps != nil )
1433
+ end
1434
+
1435
+ def seek( regexp, direction = :down )
1436
+ return if regexp == nil or regexp == //
1437
+
1438
+ found_row = nil
1439
+ found_col = nil
1440
+ found_text = nil
1441
+ wrapped = false
1442
+
1443
+ catch :found do
1444
+ if direction == :down
1445
+ # Check the current row first.
1446
+
1447
+ index, match_text = @lines[ @last_row ].group_index( regexp, @last_col + 1 )
1448
+ if index != nil
1449
+ found_row = @last_row
1450
+ found_col = index
1451
+ found_text = match_text
1452
+ throw :found
1453
+ end
1454
+
1455
+ # Check below the cursor.
1456
+
1457
+ ( (@last_row + 1)...@lines.length ).each do |i|
1458
+ index, match_text = @lines[ i ].group_index( regexp )
1459
+ if index != nil
1460
+ found_row = i
1461
+ found_col = index
1462
+ found_text = match_text
1463
+ throw :found
1464
+ end
1465
+ end
1466
+
1467
+ else
1468
+ # Check the current row first.
1469
+
1470
+ #col_to_check = ( @last_found_col or @last_col ) - 1
1471
+ col_to_check = @last_col - 1
1472
+ if col_to_check >= 0
1473
+ index, match_text = @lines[ @last_row ].group_rindex( regexp, col_to_check )
1474
+ if index != nil
1475
+ found_row = @last_row
1476
+ found_col = index
1477
+ found_text = match_text
1478
+ throw :found
1479
+ end
1480
+ end
1481
+
1482
+ # Check above the cursor.
1483
+
1484
+ (@last_row - 1).downto( 0 ) do |i|
1485
+ index, match_text = @lines[ i ].group_rindex( regexp )
1486
+ if index != nil
1487
+ found_row = i
1488
+ found_col = index
1489
+ found_text = match_text
1490
+ throw :found
1491
+ end
1492
+ end
1493
+ end
1494
+ end
1495
+
1496
+ if found_text != nil
1497
+ #@last_found_row = found_row
1498
+ #@last_found_col = found_col
1499
+ cursorTo( found_row, found_col )
1500
+
1501
+ display
1502
+ end
1503
+ end
1504
+
1505
+ def setModified( do_display = DO_DISPLAY )
1506
+ if @read_only
1507
+ @diakonos.setILine "Warning: Modifying a read-only file."
1508
+ end
1509
+
1510
+ fmod = false
1511
+ if not @modified
1512
+ @modified = true
1513
+ fmod = file_modified
1514
+ end
1515
+
1516
+ reverted = false
1517
+ if fmod
1518
+ reverted = @diakonos.revert( "File has been altered externally. Load on-disk version?" )
1519
+ end
1520
+
1521
+ if not reverted
1522
+ clearMatches
1523
+ if do_display
1524
+ @diakonos.updateStatusLine
1525
+ display
1526
+ end
1527
+ end
1528
+ end
1529
+
1530
+ # Check if the file which is being edited has been modified since
1531
+ # the last time we checked it; return true if so, false otherwise.
1532
+ def file_modified
1533
+ modified = false
1534
+
1535
+ if @name != nil
1536
+ begin
1537
+ mtime = File.mtime( @name )
1538
+
1539
+ if mtime > @last_modification_check
1540
+ modified = true
1541
+ @last_modification_check = mtime
1542
+ end
1543
+ rescue Errno::ENOENT
1544
+ # Ignore if file doesn't exist
1545
+ end
1546
+ end
1547
+
1548
+ return modified
1549
+ end
1550
+
1551
+ def takeSnapshot( typing = false )
1552
+ take_snapshot = false
1553
+ if @typing != typing
1554
+ @typing = typing
1555
+ # If we just started typing, take a snapshot, but don't continue
1556
+ # taking snapshots for every keystroke
1557
+ if typing
1558
+ take_snapshot = true
1559
+ end
1560
+ end
1561
+ if not @typing
1562
+ take_snapshot = true
1563
+ end
1564
+
1565
+ if take_snapshot
1566
+ undo_size = 0
1567
+ @buffer_states[ 1..-1 ].each do |state|
1568
+ undo_size += state.length
1569
+ end
1570
+ while ( ( undo_size + @lines.length ) >= @settings[ "max_undo_lines" ] ) and @buffer_states.length > 1
1571
+ @cursor_states.pop
1572
+ popped_state = @buffer_states.pop
1573
+ undo_size = undo_size - popped_state.length
1574
+ end
1575
+ if @current_buffer_state > 0
1576
+ @buffer_states.unshift @lines.deep_clone
1577
+ @cursor_states.unshift [ @last_row, @last_col ]
1578
+ end
1579
+ @buffer_states.unshift @lines.deep_clone
1580
+ @cursor_states.unshift [ @last_row, @last_col ]
1581
+ @current_buffer_state = 0
1582
+ @lines = @buffer_states[ @current_buffer_state ]
1583
+ end
1584
+ end
1585
+
1586
+ def undo
1587
+ if @current_buffer_state < @buffer_states.length - 1
1588
+ @current_buffer_state += 1
1589
+ @lines = @buffer_states[ @current_buffer_state ]
1590
+ cursorTo( @cursor_states[ @current_buffer_state - 1 ][ 0 ], @cursor_states[ @current_buffer_state - 1 ][ 1 ] )
1591
+ @diakonos.setILine "Undo level: #{@current_buffer_state} of #{@buffer_states.length - 1}"
1592
+ setModified
1593
+ end
1594
+ end
1595
+
1596
+ # Since redo is a Ruby keyword...
1597
+ def unundo
1598
+ if @current_buffer_state > 0
1599
+ @current_buffer_state += -1
1600
+ @lines = @buffer_states[ @current_buffer_state ]
1601
+ cursorTo( @cursor_states[ @current_buffer_state ][ 0 ], @cursor_states[ @current_buffer_state ][ 1 ] )
1602
+ @diakonos.setILine "Undo level: #{@current_buffer_state} of #{@buffer_states.length - 1}"
1603
+ setModified
1604
+ end
1605
+ end
1606
+
1607
+ def goToLine( line = nil, column = nil )
1608
+ cursorTo( line || @last_row, column || 0, DO_DISPLAY )
1609
+ end
1610
+
1611
+ def goToNextBookmark
1612
+ cur_pos = Bookmark.new( self, @last_row, @last_col )
1613
+ next_bm = @bookmarks.find do |bm|
1614
+ bm > cur_pos
1615
+ end
1616
+ if next_bm != nil
1617
+ cursorTo( next_bm.row, next_bm.col, DO_DISPLAY )
1618
+ end
1619
+ end
1620
+
1621
+ def goToPreviousBookmark
1622
+ cur_pos = Bookmark.new( self, @last_row, @last_col )
1623
+ # There's no reverse_find method, so, we have to do this manually.
1624
+ prev = nil
1625
+ @bookmarks.reverse_each do |bm|
1626
+ if bm < cur_pos
1627
+ prev = bm
1628
+ break
1629
+ end
1630
+ end
1631
+ if prev != nil
1632
+ cursorTo( prev.row, prev.col, DO_DISPLAY )
1633
+ end
1634
+ end
1635
+
1636
+ def toggleBookmark
1637
+ bookmark = Bookmark.new( self, @last_row, @last_col )
1638
+ existing = @bookmarks.find do |bm|
1639
+ bm == bookmark
1640
+ end
1641
+ if existing
1642
+ @bookmarks.delete existing
1643
+ @diakonos.setILine "Bookmark #{existing.to_s} deleted."
1644
+ else
1645
+ @bookmarks.push bookmark
1646
+ @bookmarks.sort
1647
+ @diakonos.setILine "Bookmark #{bookmark.to_s} set."
1648
+ end
1649
+ end
1650
+
1651
+ def context
1652
+ retval = Array.new
1653
+ row = @last_row
1654
+ clevel = @lines[ row ].indentation_level( @indent_size, @indent_roundup, @tab_size, @indent_ignore_charset )
1655
+ while row > 0 and clevel < 0
1656
+ row = row - 1
1657
+ clevel = @lines[ row ].indentation_level( @indent_size, @indent_roundup, @tab_size, @indent_ignore_charset )
1658
+ end
1659
+ clevel = 0 if clevel < 0
1660
+ while row > 0
1661
+ row = row - 1
1662
+ line = @lines[ row ]
1663
+ if line !~ @settings[ "lang.#{@language}.context.ignore" ]
1664
+ level = line.indentation_level( @indent_size, @indent_roundup, @tab_size, @indent_ignore_charset )
1665
+ if level < clevel and level > -1
1666
+ retval.unshift line
1667
+ clevel = level
1668
+ break if clevel == 0
1669
+ end
1670
+ end
1671
+ end
1672
+ return retval
1673
+ end
1674
+
1675
+ def setType( type )
1676
+ success = false
1677
+ if type != nil
1678
+ configure( type )
1679
+ display
1680
+ success = true
1681
+ end
1682
+ return success
1683
+ end
1684
+
1685
+ def wordUnderCursor
1686
+ word = nil
1687
+
1688
+ @lines[ @last_row ].scan( /\w+/ ) do |match_text|
1689
+ last_match = Regexp.last_match
1690
+ if last_match.begin( 0 ) <= @last_col and @last_col < last_match.end( 0 )
1691
+ word = match_text
1692
+ break
1693
+ end
1694
+ end
1695
+
1696
+ return word
1697
+ end
1698
+ end
1699
+
1700
+ end