diakonos 0.8.8 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +433 -0
  3. data/LICENCE.md +675 -0
  4. data/bin/diakonos +6 -0
  5. data/diakonos-256-colour.conf +220 -0
  6. data/diakonos.conf +1802 -0
  7. data/help/about-help.dhf +31 -0
  8. data/help/clipboard.dhf +45 -0
  9. data/help/close-file.dhf +6 -0
  10. data/help/code-block-navigation.dhf +16 -0
  11. data/help/column-markers.dhf +15 -0
  12. data/help/config.dhf +69 -0
  13. data/help/cursor-stack.dhf +19 -0
  14. data/help/delete.dhf +41 -0
  15. data/help/extensions.dhf +125 -0
  16. data/help/file-type.dhf +24 -0
  17. data/help/key-mapping.dhf +127 -0
  18. data/help/line-numbers.dhf +22 -0
  19. data/help/macros.dhf +27 -0
  20. data/help/new-file.dhf +6 -0
  21. data/help/open-file.dhf +21 -0
  22. data/help/quit.dhf +7 -0
  23. data/help/resizing.dhf +19 -0
  24. data/help/ruby.dhf +17 -0
  25. data/help/save-file.dhf +10 -0
  26. data/help/scripting.dhf +92 -0
  27. data/help/search.dhf +103 -0
  28. data/help/shell.dhf +60 -0
  29. data/help/speed.dhf +23 -0
  30. data/help/support.dhf +15 -0
  31. data/help/switch-buffers.dhf +15 -0
  32. data/help/tabs.dhf +36 -0
  33. data/help/undo.dhf +9 -0
  34. data/help/uninstall.dhf +18 -0
  35. data/help/welcome.dhf +32 -0
  36. data/help/word-wrap.dhf +17 -0
  37. data/lib/diakonos/about.rb +69 -0
  38. data/lib/diakonos/bookmark.rb +46 -0
  39. data/lib/diakonos/buffer/bookmarking.rb +47 -0
  40. data/lib/diakonos/buffer/cursor.rb +335 -0
  41. data/lib/diakonos/buffer/delete.rb +170 -0
  42. data/lib/diakonos/buffer/display.rb +356 -0
  43. data/lib/diakonos/buffer/file.rb +157 -0
  44. data/lib/diakonos/buffer/indentation.rb +175 -0
  45. data/lib/diakonos/buffer/searching.rb +552 -0
  46. data/lib/diakonos/buffer/selection.rb +360 -0
  47. data/lib/diakonos/buffer/undo.rb +73 -0
  48. data/lib/diakonos/buffer-hash.rb +60 -0
  49. data/lib/diakonos/buffer-management.rb +59 -0
  50. data/lib/diakonos/buffer.rb +698 -0
  51. data/lib/diakonos/clipboard-klipper-dbus.rb +62 -0
  52. data/lib/diakonos/clipboard-klipper.rb +62 -0
  53. data/lib/diakonos/clipboard-osx.rb +59 -0
  54. data/lib/diakonos/clipboard-xclip.rb +60 -0
  55. data/lib/diakonos/clipboard.rb +47 -0
  56. data/lib/diakonos/config-file.rb +67 -0
  57. data/lib/diakonos/config.rb +381 -0
  58. data/lib/diakonos/core-ext/enumerable.rb +15 -0
  59. data/lib/diakonos/core-ext/hash.rb +60 -0
  60. data/lib/diakonos/core-ext/object.rb +6 -0
  61. data/lib/diakonos/core-ext/regexp.rb +6 -0
  62. data/lib/diakonos/core-ext/string.rb +122 -0
  63. data/lib/diakonos/ctag.rb +28 -0
  64. data/lib/diakonos/cursor.rb +27 -0
  65. data/lib/diakonos/display/format.rb +75 -0
  66. data/lib/diakonos/display.rb +336 -0
  67. data/lib/diakonos/extension-set.rb +49 -0
  68. data/lib/diakonos/extension.rb +34 -0
  69. data/lib/diakonos/finding.rb +40 -0
  70. data/lib/diakonos/functions/basics.rb +34 -0
  71. data/lib/diakonos/functions/bookmarking.rb +61 -0
  72. data/lib/diakonos/functions/buffers.rb +489 -0
  73. data/lib/diakonos/functions/clipboard.rb +70 -0
  74. data/lib/diakonos/functions/cursor.rb +264 -0
  75. data/lib/diakonos/functions/grepping.rb +83 -0
  76. data/lib/diakonos/functions/indentation.rb +71 -0
  77. data/lib/diakonos/functions/readline.rb +93 -0
  78. data/lib/diakonos/functions/search.rb +179 -0
  79. data/lib/diakonos/functions/selection.rb +98 -0
  80. data/lib/diakonos/functions/sessions.rb +78 -0
  81. data/lib/diakonos/functions/shell.rb +250 -0
  82. data/lib/diakonos/functions/tags.rb +65 -0
  83. data/lib/diakonos/functions/text-manipulation.rb +196 -0
  84. data/lib/diakonos/functions-deprecated.rb +77 -0
  85. data/lib/diakonos/functions.rb +292 -0
  86. data/lib/diakonos/grep.rb +98 -0
  87. data/lib/diakonos/help.rb +47 -0
  88. data/lib/diakonos/hooks.rb +13 -0
  89. data/lib/diakonos/installation.rb +19 -0
  90. data/lib/diakonos/interaction-handler.rb +216 -0
  91. data/lib/diakonos/interaction.rb +52 -0
  92. data/lib/diakonos/key-map.rb +62 -0
  93. data/lib/diakonos/keying.rb +442 -0
  94. data/lib/diakonos/line-mover.rb +42 -0
  95. data/lib/diakonos/list.rb +59 -0
  96. data/lib/diakonos/logging.rb +27 -0
  97. data/lib/diakonos/mode.rb +17 -0
  98. data/lib/diakonos/mouse.rb +18 -0
  99. data/lib/diakonos/number-fitter.rb +11 -0
  100. data/lib/diakonos/range.rb +31 -0
  101. data/lib/diakonos/readline/functions.rb +82 -0
  102. data/lib/diakonos/readline.rb +222 -0
  103. data/lib/diakonos/search.rb +58 -0
  104. data/lib/diakonos/sessions.rb +257 -0
  105. data/lib/diakonos/sized-array.rb +48 -0
  106. data/lib/diakonos/text-mark.rb +19 -0
  107. data/lib/diakonos/vendor/fuzzy_file_finder.rb +365 -0
  108. data/lib/diakonos/version.rb +25 -0
  109. data/lib/diakonos/window.rb +43 -0
  110. data/lib/diakonos.rb +592 -0
  111. metadata +160 -66
@@ -0,0 +1,356 @@
1
+ module Diakonos
2
+
3
+ class Buffer
4
+
5
+ attr_reader :top_line, :left_column
6
+
7
+ def find_opening_match( line, match_close = true, bos_allowed = true )
8
+ open_index = line.length
9
+ open_token_class = nil
10
+ open_match_text = nil
11
+ match = nil
12
+ match_text = nil
13
+ @token_regexps.each do |token_class,regexp|
14
+ if match = regexp.match( line )
15
+ if match.length > 1
16
+ index = match.begin 1
17
+ match_text = match[ 1 ]
18
+ whole_match_index = match.begin 0
19
+ else
20
+ whole_match_index = index = match.begin( 0 )
21
+ match_text = match[ 0 ]
22
+ end
23
+ if ( ! regexp.uses_bos ) || ( bos_allowed && ( whole_match_index == 0 ) )
24
+ if index < open_index
25
+ if ( ( ! match_close ) || @close_token_regexps[ token_class ] )
26
+ open_index = index
27
+ open_token_class = token_class
28
+ open_match_text = match_text
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ [ open_index, open_token_class, open_match_text ]
36
+ end
37
+
38
+ private def find_closing_match(line_segment, regexp, bos_allowed = true)
39
+ close_match_text = nil
40
+ close_index = nil
41
+
42
+ line_segment.scan(regexp) do |m|
43
+ match = Regexp.last_match
44
+ if match.length > 1
45
+ index = match.begin 1
46
+ match_text = match[1]
47
+ else
48
+ index = match.begin 0
49
+ match_text = match[0]
50
+ end
51
+ if ( ! regexp.uses_bos ) || ( bos_allowed && ( index == 0 ) )
52
+ close_index = index
53
+ close_match_text = match_text
54
+ break
55
+ end
56
+ end
57
+
58
+ [close_index, close_match_text]
59
+ end
60
+
61
+ # Prints text to the screen, truncating where necessary.
62
+ # Returns nil if the string is completely off-screen.
63
+ # write_cursor_col is buffer-relative, not screen-relative
64
+ def truncate_off_screen( string, write_cursor_col )
65
+ retval = string
66
+
67
+ # Truncate based on left edge of display area
68
+ if write_cursor_col < @left_column
69
+ retval = retval[ (@left_column - write_cursor_col)..-1 ]
70
+ write_cursor_col = @left_column
71
+ end
72
+
73
+ if retval
74
+ # Truncate based on right edge of display area
75
+ if write_cursor_col + retval.length > @left_column + Curses::cols - 1
76
+ new_length = ( @left_column + Curses::cols - write_cursor_col )
77
+ if new_length <= 0
78
+ retval = nil
79
+ else
80
+ retval = retval[ 0...new_length ]
81
+ end
82
+ end
83
+ end
84
+
85
+ retval == "" ? nil : retval
86
+ end
87
+
88
+ # Worker function for painting only part of a row.
89
+ def paint_single_row_mark( row, text_mark, string, curx, cury )
90
+ expanded_col = tab_expanded_column( text_mark.start_col, row )
91
+ if expanded_col < @left_column + Curses::cols
92
+ left = [ expanded_col - @left_column, 0 ].max
93
+ right = tab_expanded_column( text_mark.end_col, row ) - @left_column
94
+ if left < right
95
+ @win_main.setpos( cury, curx + left )
96
+ @win_main.addstr string[ left...right ]
97
+ end
98
+ end
99
+ end
100
+
101
+ def paint_marks( row )
102
+ string = @lines[ row ][ @left_column ... @left_column + Curses::cols ]
103
+ return if string.nil? || string == ""
104
+ string = string.expand_tabs( @tab_size )
105
+ cury = @win_main.cury
106
+ curx = @win_main.curx
107
+
108
+ @text_marks.values.flatten.reverse_each do |text_mark|
109
+ next if text_mark.nil?
110
+
111
+ @win_main.attrset text_mark.formatting
112
+
113
+ case @selection_mode
114
+ when :normal
115
+ if ( (text_mark.start_row + 1) .. (text_mark.end_row - 1) ) === row
116
+ @win_main.setpos( cury, curx )
117
+ @win_main.addstr string
118
+ elsif row == text_mark.start_row && row == text_mark.end_row
119
+ paint_single_row_mark( row, text_mark, string, curx, cury )
120
+ elsif row == text_mark.start_row
121
+ expanded_col = tab_expanded_column( text_mark.start_col, row )
122
+ if expanded_col < @left_column + Curses::cols
123
+ left = [ expanded_col - @left_column, 0 ].max
124
+ @win_main.setpos( cury, curx + left )
125
+ @win_main.addstr string[ left..-1 ]
126
+ end
127
+ elsif row == text_mark.end_row
128
+ right = tab_expanded_column( text_mark.end_col, row ) - @left_column
129
+ @win_main.setpos( cury, curx )
130
+ @win_main.addstr string[ 0...right ]
131
+ else
132
+ # This row not in selection.
133
+ end
134
+ when :block
135
+ if(
136
+ text_mark.start_row <= row && row <= text_mark.end_row ||
137
+ text_mark.end_row <= row && row <= text_mark.start_row
138
+ )
139
+ paint_single_row_mark( row, text_mark, string, curx, cury )
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ def paint_column_markers
146
+ $diakonos.column_markers.each_value do |data|
147
+ column = data[ :column ]
148
+ next if column.nil?
149
+ next if column > Curses::cols - @left_column || column - @left_column < 0
150
+
151
+ num_lines_to_paint = [ $diakonos.main_window_height, @lines.size - @top_line ].min
152
+ ( 0...num_lines_to_paint ).each do |row|
153
+ @win_main.setpos( row, column - @left_column )
154
+ @win_main.attrset data[ :format ]
155
+ @win_main.addstr( @lines[ @top_line + row ][ column + @left_column ] || ' ' )
156
+ end
157
+ end
158
+ end
159
+
160
+ def print_string( string, formatting = ( @token_formats[ @continued_format_class ] || @default_formatting ) )
161
+ return if ! @pen_down
162
+ return if string.nil?
163
+
164
+ @win_main.attrset formatting
165
+ @win_main.addstr string
166
+ end
167
+
168
+ # This method assumes that the cursor has been set up already at
169
+ # the left-most column of the correct on-screen row.
170
+ # It merely unintelligently prints the characters on the current curses line,
171
+ # refusing to print characters of the in-buffer line which are offscreen.
172
+ def print_line( line )
173
+ i = 0
174
+ substr = nil
175
+ index = nil
176
+ while i < line.length
177
+ substr = line[ i..-1 ]
178
+ if @continued_format_class
179
+ close_index, close_match_text = find_closing_match(
180
+ substr,
181
+ @close_token_regexps[@continued_format_class],
182
+ i == 0
183
+ )
184
+
185
+ if close_match_text.nil?
186
+ print_string truncate_off_screen( substr, i )
187
+ print_padding_from( line.length )
188
+ i = line.length
189
+ else
190
+ end_index = close_index + close_match_text.length
191
+ print_string truncate_off_screen( substr[ 0...end_index ], i )
192
+ @continued_format_class = nil
193
+ i += end_index
194
+ end
195
+ else
196
+ first_index, first_token_class, first_word = find_opening_match( substr, MATCH_ANY, i == 0 )
197
+
198
+ if @lang_stack.length > 0
199
+ prev_lang, close_token_class = @lang_stack[-1]
200
+ close_index, close_match_text = find_closing_match(
201
+ substr,
202
+ $diakonos.close_token_regexps[prev_lang][close_token_class],
203
+ i == 0
204
+ )
205
+
206
+ if close_match_text && close_index <= first_index
207
+ # Print any remaining text in the embedded language
208
+ s = substr[0...close_index]
209
+ print_string( truncate_off_screen(s, i) )
210
+ i += s.length
211
+
212
+ @lang_stack.pop
213
+ set_language prev_lang
214
+
215
+ print_string(
216
+ truncate_off_screen(
217
+ substr[
218
+ close_index...(close_index + close_match_text.length)
219
+ ],
220
+ i
221
+ ),
222
+ @token_formats[close_token_class]
223
+ )
224
+ i += close_match_text.length
225
+
226
+ # Continue printing from here.
227
+ next
228
+ end
229
+ end
230
+
231
+ if first_word
232
+ if first_index > 0
233
+ # Print any preceding text in the default format
234
+ print_string truncate_off_screen( substr[ 0...first_index ], i )
235
+ i += substr[ 0...first_index ].length
236
+ end
237
+ print_string( truncate_off_screen( first_word, i ), @token_formats[ first_token_class ] )
238
+ i += first_word.length
239
+ if @close_token_regexps[ first_token_class ]
240
+ if change_to = @settings[ "lang.#{@language}.tokens.#{first_token_class}.change_to" ]
241
+ @lang_stack.push [ @language, first_token_class ]
242
+ set_language change_to
243
+ else
244
+ @continued_format_class = first_token_class
245
+ end
246
+ end
247
+ else
248
+ print_string truncate_off_screen( substr, i )
249
+ i += substr.length
250
+ break
251
+ end
252
+ end
253
+ end
254
+
255
+ print_padding_from i
256
+ end
257
+
258
+ def print_padding_from( col )
259
+ return if ! @pen_down
260
+
261
+ if col < @left_column
262
+ remainder = Curses::cols
263
+ else
264
+ remainder = @left_column + Curses::cols - col
265
+ end
266
+
267
+ if remainder > 0
268
+ print_string( " " * remainder )
269
+ end
270
+ end
271
+
272
+ def display
273
+ @continued_format_class = nil
274
+ @pen_down = true
275
+ @lang_stack = []
276
+
277
+ # First, we have to "draw" off-screen, in order to check for opening of
278
+ # multi-line highlights.
279
+
280
+ # So, first look backwards from the @top_line to find the first opening
281
+ # regexp match, if any.
282
+ index = @top_line - 1
283
+ @lines[ [ 0, @top_line - @settings[ "view.lookback" ] ].max...@top_line ].reverse_each do |line|
284
+ open_index = -1
285
+ open_token_class = nil
286
+ open_match_text = nil
287
+
288
+ open_index, open_token_class, open_match_text = find_opening_match( line )
289
+
290
+ if open_token_class
291
+ @pen_down = false
292
+ @lines[ index...@top_line ].each do |line|
293
+ print_line line
294
+ end
295
+ @pen_down = true
296
+
297
+ break
298
+ end
299
+
300
+ index = index - 1
301
+ end
302
+
303
+ # Draw each on-screen line.
304
+ y = 0
305
+ @lines[ @top_line...($diakonos.main_window_height + @top_line) ].each_with_index do |line, row|
306
+ if @win_line_numbers
307
+ @win_line_numbers.setpos( y, 0 )
308
+ @win_line_numbers.attrset @settings[ 'view.line_numbers.format' ]
309
+ n = ( @top_line+row+1 ).to_s
310
+ @win_line_numbers.addstr(
311
+ @settings[ 'view.line_numbers.number_format' ] % [
312
+ n[ -[ @settings[ 'view.line_numbers.width' ], n.length ].min..-1 ]
313
+ ]
314
+ )
315
+ end
316
+ @win_main.setpos( y, 0 )
317
+ print_line line.expand_tabs( @tab_size )
318
+ @win_main.setpos( y, 0 )
319
+ paint_marks @top_line + row
320
+ y += 1
321
+ end
322
+
323
+ # Paint the empty space below the file if the file is too short to fit in one screen.
324
+ ( y...$diakonos.main_window_height ).each do |y|
325
+ if @win_line_numbers
326
+ @win_line_numbers.setpos( y, 0 )
327
+ @win_line_numbers.attrset @settings[ 'view.line_numbers.format' ]
328
+ @win_line_numbers.addstr( ' ' * @settings[ 'view.line_numbers.width' ] )
329
+ end
330
+
331
+ @win_main.setpos( y, 0 )
332
+ @win_main.attrset @default_formatting
333
+ linestr = " " * Curses::cols
334
+ if @settings[ "view.nonfilelines.visible" ]
335
+ linestr[ 0 ] = ( @settings[ "view.nonfilelines.character" ] || "~" )
336
+ end
337
+
338
+ @win_main.addstr linestr
339
+ end
340
+
341
+ paint_column_markers
342
+
343
+ if @win_line_numbers
344
+ @win_line_numbers.refresh
345
+ end
346
+ @win_main.setpos( @last_screen_y , @last_screen_x )
347
+ @win_main.refresh
348
+
349
+ if @language != @original_language
350
+ set_language( @original_language )
351
+ end
352
+ end
353
+
354
+ end
355
+
356
+ end
@@ -0,0 +1,157 @@
1
+ module Diakonos
2
+
3
+ class Buffer
4
+
5
+ def save( filename = nil, prompt_overwrite = DONT_PROMPT_OVERWRITE )
6
+ if filename
7
+ name = File.expand_path( filename )
8
+ else
9
+ name = @name
10
+ end
11
+
12
+ if @read_only && FileTest.exists?( @name ) && FileTest.exists?( name ) && ( File.stat( @name ).ino == File.stat( name ).ino )
13
+ $diakonos.set_iline "#{name} cannot be saved since it is read-only."
14
+ else
15
+ @read_only = false
16
+ if name.nil?
17
+ $diakonos.save_file_as
18
+ else
19
+ proceed = true
20
+
21
+ if prompt_overwrite && FileTest.exists?( name )
22
+ proceed = false
23
+ choice = $diakonos.get_choice(
24
+ "Overwrite existing '#{name}'?",
25
+ [ CHOICE_YES, CHOICE_NO ],
26
+ CHOICE_NO
27
+ )
28
+ case choice
29
+ when CHOICE_YES
30
+ proceed = true
31
+ when CHOICE_NO
32
+ proceed = false
33
+ end
34
+ end
35
+
36
+ if file_modified?
37
+ proceed = ! $diakonos.revert( "File has been altered externally. Load on-disk version?" )
38
+ end
39
+
40
+ if proceed
41
+ save_copy name
42
+ @name = name
43
+ @last_modification_check = File.mtime( @name )
44
+ saved = true
45
+
46
+ if @name =~ /#{$diakonos.diakonos_home}\/.*\.conf/
47
+ $diakonos.load_configuration
48
+ $diakonos.initialize_display
49
+ end
50
+
51
+ @modified = false
52
+
53
+ display
54
+ $diakonos.update_status_line
55
+ end
56
+ end
57
+ end
58
+
59
+ saved
60
+ end
61
+
62
+ # Returns true on successful write.
63
+ def save_copy( filename )
64
+ return false if filename.nil?
65
+
66
+ name = File.expand_path( filename )
67
+
68
+ if @settings['save_backup_files']
69
+ begin
70
+ FileUtils.cp name, name+'~', preserve: true
71
+ rescue Errno::ENOENT
72
+ # Do nothing if file didn't exist yet
73
+ end
74
+ end
75
+
76
+ File.open( name, "w" ) do |f|
77
+ @lines[ 0..-2 ].each do |line|
78
+ if @settings[ 'strip_trailing_whitespace_on_save' ]
79
+ line.rstrip!
80
+ end
81
+ f.puts line
82
+ end
83
+
84
+ line = @lines[ -1 ]
85
+ if @settings[ 'strip_trailing_whitespace_on_save' ]
86
+ line.rstrip!
87
+ end
88
+ if line != ""
89
+ # No final newline character
90
+ if @settings[ "eof_newline" ]
91
+ line << "\n"
92
+ @lines << ''
93
+ end
94
+ f.print line
95
+ end
96
+
97
+ if @settings[ 'strip_trailing_whitespace_on_save' ]
98
+ if @last_col > @lines[ @last_row ].size
99
+ cursor_to @last_row, @lines[ @last_row ].size
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ # Check if the file which is being edited has been modified since
106
+ # the last time we checked it.
107
+ # @return true if file has been modified
108
+ # @return false if file has not been modified
109
+ def file_modified?
110
+ modified = false
111
+
112
+ if @name
113
+ begin
114
+ mtime = File.mtime( @name )
115
+
116
+ if mtime > @last_modification_check
117
+ modified = true
118
+ @last_modification_check = mtime
119
+ end
120
+ rescue Errno::ENOENT
121
+ # Ignore if file doesn't exist
122
+ end
123
+ end
124
+
125
+ modified
126
+ end
127
+
128
+ # Compares MD5 sums of buffer and actual file on disk.
129
+ # Returns true if there is no file on disk.
130
+ def file_different?
131
+ if @name && File.exist?( @name )
132
+ Digest::MD5.hexdigest(
133
+ @lines.join( "\n" )
134
+ ) != Digest::MD5.hexdigest(
135
+ File.read( @name )
136
+ )
137
+ else
138
+ true
139
+ end
140
+ end
141
+
142
+ def set_modified( do_display = DO_DISPLAY, use_md5 = DONT_USE_MD5 )
143
+ if @read_only
144
+ $diakonos.set_iline "Warning: Modifying a read-only file."
145
+ end
146
+
147
+ @modified = use_md5 ? file_different? : true
148
+ clear_matches
149
+ if do_display
150
+ $diakonos.update_status_line
151
+ display
152
+ end
153
+ end
154
+
155
+ end
156
+
157
+ end
@@ -0,0 +1,175 @@
1
+ module Diakonos
2
+
3
+ class Buffer
4
+
5
+ def tab_expanded_column( col, row )
6
+ delta = 0
7
+ line = @lines[ row ]
8
+ for i in 0...col
9
+ if line[ i ] == "\t"
10
+ delta += ( @tab_size - ( (i+delta) % @tab_size ) ) - 1
11
+ end
12
+ end
13
+ col + delta
14
+ end
15
+
16
+ def set_indent( row, level, opts = {} )
17
+ do_display = opts.fetch( :do_display, true )
18
+ undoable = opts.fetch( :undoable, true )
19
+ cursor_eol = opts.fetch( :cursor_eol, false )
20
+
21
+ @lines[ row ] =~ /^([\s#{@indent_ignore_charset}]*)(.*)$/
22
+ current_indent_text = ( $1 || "" )
23
+ rest = ( $2 || "" )
24
+ current_indent_text.gsub!( /\t/, ' ' * @tab_size )
25
+ indentation = @indent_size * [ level, 0 ].max
26
+ if current_indent_text.length >= indentation
27
+ indent_text = current_indent_text[ 0...indentation ]
28
+ else
29
+ indent_text = current_indent_text + " " * ( indentation - current_indent_text.length )
30
+ end
31
+ if @settings[ "lang.#{@language}.indent.using_tabs" ]
32
+ num_tabs = 0
33
+ indent_text.gsub!( / {#{@tab_size}}/ ) { |match|
34
+ num_tabs += 1
35
+ "\t"
36
+ }
37
+ indentation -= num_tabs * ( @tab_size - 1 )
38
+ end
39
+
40
+ take_snapshot( TYPING ) if do_display && undoable
41
+ @lines[ row ] = indent_text + rest
42
+ if do_display
43
+ cursor_to(
44
+ row,
45
+ cursor_eol ? @lines[row].length : indentation
46
+ )
47
+ end
48
+ set_modified do_display
49
+ end
50
+
51
+ def indentation_level( row, use_indent_ignore = USE_INDENT_IGNORE )
52
+ line = @lines[ row ]
53
+
54
+ if use_indent_ignore
55
+ if line =~ /^[\s#{@indent_ignore_charset}]*$/ || line == ""
56
+ level = 0
57
+ elsif line =~ /^([\s#{@indent_ignore_charset}]+)[^\s#{@indent_ignore_charset}]/
58
+ whitespace = $1.expand_tabs( @tab_size )
59
+ level = whitespace.length / @indent_size
60
+ if @indent_roundup && ( whitespace.length % @indent_size > 0 )
61
+ level += 1
62
+ end
63
+ else
64
+ level = 0
65
+ end
66
+ else
67
+ level = 0
68
+ if line =~ /^([\s]+)/
69
+ whitespace = $1.expand_tabs( @tab_size )
70
+ level = whitespace.length / @indent_size
71
+ if @indent_roundup && ( whitespace.length % @indent_size > 0 )
72
+ level += 1
73
+ end
74
+ end
75
+ end
76
+
77
+ level
78
+ end
79
+
80
+ # @param starting_row [Integer]
81
+ # @param next_line_check [Boolean]
82
+ # @return [Integer]
83
+ def nearest_basis_row_from(starting_row, next_line_check = true)
84
+ row = starting_row-1
85
+
86
+ if @lines[row] =~ @indenters_next_line || @lines[row] =~ @indenters
87
+ return row
88
+ end
89
+
90
+ loop do
91
+ return nil if row.nil? || row < 0
92
+
93
+ while (
94
+ @lines[row] =~ /^[\s#{@indent_ignore_charset}]*$/ ||
95
+ @lines[row] =~ @settings[ "lang.#{@language}.indent.ignore" ] ||
96
+ @lines[row] =~ @settings[ "lang.#{@language}.indent.not_indented" ]
97
+ )
98
+ row = nearest_basis_row_from(row)
99
+ return nil if row.nil?
100
+ end
101
+
102
+ if next_line_check
103
+ row_before = nearest_basis_row_from(row, false)
104
+ if row_before && @lines[row_before] =~ @indenters_next_line
105
+ row = row_before
106
+ next
107
+ end
108
+ end
109
+
110
+ break
111
+ end
112
+
113
+ row
114
+ end
115
+
116
+ def parsed_indent( opts = {} )
117
+ row = opts.fetch( :row, @last_row )
118
+ do_display = opts.fetch( :do_display, true )
119
+ undoable = opts.fetch( :undoable, true )
120
+ cursor_eol = opts.fetch( :cursor_eol, false )
121
+
122
+ if row == 0 || @lines[ row ] =~ @settings[ "lang.#{@language}.indent.not_indented" ]
123
+ level = 0
124
+ else
125
+ basis_row = nearest_basis_row_from(row)
126
+
127
+ if basis_row.nil?
128
+ level = 0
129
+ else
130
+ # @lines[basis_row] += " // x"
131
+ level = indentation_level(basis_row)
132
+
133
+ prev_line = @lines[basis_row]
134
+ line = @lines[row]
135
+
136
+ if @preventers
137
+ prev_line = prev_line.gsub( @preventers, "" )
138
+ line = line.gsub( @preventers, "" )
139
+ end
140
+
141
+ indenter_index = (prev_line =~ @indenters)
142
+ nl_indenter_index = (prev_line =~ @indenters_next_line)
143
+
144
+ if nl_indenter_index && basis_row == row-1
145
+ level += 1
146
+ elsif indenter_index
147
+ level += 1
148
+ unindenter_index = (prev_line =~ @unindenters)
149
+ if unindenter_index && unindenter_index != indenter_index
150
+ level -= 1
151
+ end
152
+ end
153
+
154
+ if line =~ @unindenters
155
+ level -= 1
156
+ end
157
+ end
158
+ end
159
+
160
+ set_indent row, level, do_display: do_display, undoable: undoable, cursor_eol: cursor_eol
161
+ end
162
+
163
+ def indent( row = @last_row, do_display = DO_DISPLAY )
164
+ level = indentation_level( row, DONT_USE_INDENT_IGNORE )
165
+ set_indent row, level + 1, do_display: do_display
166
+ end
167
+
168
+ def unindent( row = @last_row, do_display = DO_DISPLAY )
169
+ level = indentation_level( row, DONT_USE_INDENT_IGNORE )
170
+ set_indent row, level - 1, do_display: do_display
171
+ end
172
+
173
+ end
174
+
175
+ end