diakonos 0.8.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +172 -0
- data/README +75 -0
- data/bin/diakonos +6 -0
- data/diakonos.conf +960 -0
- data/home-on-save.rb +6 -0
- data/lib/diakonos.rb +2397 -0
- data/lib/diakonos/array.rb +10 -0
- data/lib/diakonos/bignum.rb +6 -0
- data/lib/diakonos/bookmark.rb +51 -0
- data/lib/diakonos/buffer-hash.rb +18 -0
- data/lib/diakonos/buffer.rb +1700 -0
- data/lib/diakonos/clipboard.rb +47 -0
- data/lib/diakonos/ctag.rb +28 -0
- data/lib/diakonos/enumerable.rb +15 -0
- data/lib/diakonos/finding.rb +32 -0
- data/lib/diakonos/fixnum.rb +13 -0
- data/lib/diakonos/hash.rb +101 -0
- data/lib/diakonos/keycode.rb +110 -0
- data/lib/diakonos/object.rb +6 -0
- data/lib/diakonos/readline.rb +192 -0
- data/lib/diakonos/regexp.rb +6 -0
- data/lib/diakonos/sized-array.rb +48 -0
- data/lib/diakonos/string.rb +318 -0
- data/lib/diakonos/text-mark.rb +19 -0
- data/lib/diakonos/window.rb +32 -0
- data/test/buffer-test.rb +37 -0
- data/test/clipboard-test.rb +70 -0
- data/test/diakonos-test.rb +14 -0
- data/test/hash-test.rb +404 -0
- data/test/regexp-test.rb +26 -0
- data/test/sizedarray-test.rb +113 -0
- data/test/string-test.rb +160 -0
- metadata +77 -0
@@ -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
|