Class: VER::Text

Inherits:
Tk::Text
  • Object
show all
Includes:
Keymapped
Defined in:
lib/ver/text.rb,
lib/ver/text/index.rb

Defined Under Namespace

Classes: Index

Constant Summary collapse

None =
Object.new
MATCH_WORD_RIGHT =
/[^a-zA-Z0-9]+[a-zA-Z0-9'"{}\[\]\n-]/
MATCH_WORD_LEFT =
/(^|\b)\S+(\b|$)/
MODE_STYLES =
{
  :insert   => {insertbackground: 'red', blockcursor: false},
  /select/  => {insertbackground: 'yellow', blockcursor: true},
  /replace/ => {insertbackground: 'orange', blockcursor: true},
}
TAG_ALL_MATCHING_OPTIONS =
{ from: '1.0', to: 'end - 1 chars' }

Instance Attribute Summary collapse

Attributes included from Keymapped

#major_mode

Instance Method Summary collapse

Methods included from Keymapped

#minor_mode, #minor_mode?

Constructor Details

#initialize(buffer, options = {}) ⇒ Text

Returns a new instance of Text.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/ver/text.rb', line 20

def initialize(buffer, options = {})
  @project_repo = @project_root = @highlighter = nil

  if peer = options.delete(:peer)
    @tag_commands = {}
    @tk_parent = buffer
    @store_hash = peer.store_hash
    @default_theme_config = peer.default_theme_config
    Tk.execute(peer.tk_pathname, 'peer', 'create', assign_pathname, options)
    self.filename = peer.filename
    configure(peer.configure)
  else
    @default_theme_config = nil
    @store_hash = Hash.new{|h,k| h[k] = {} }
    super
  end

  widget_setup(buffer)
end

Instance Attribute Details

#bufferObject

Returns the value of attribute buffer.



15
16
17
# File 'lib/ver/text.rb', line 15

def buffer
  @buffer
end

#default_theme_configObject

Returns the value of attribute default_theme_config.



17
18
19
# File 'lib/ver/text.rb', line 17

def default_theme_config
  @default_theme_config
end

#encodingObject

Returns the value of attribute encoding.



17
18
19
# File 'lib/ver/text.rb', line 17

def encoding
  @encoding
end

#filenameObject

Returns the value of attribute filename.



17
18
19
# File 'lib/ver/text.rb', line 17

def filename
  @filename
end

#nameObject

Returns the value of attribute name.



17
18
19
# File 'lib/ver/text.rb', line 17

def name
  @name
end

#optionsObject (readonly)

Returns the value of attribute options.



17
18
19
# File 'lib/ver/text.rb', line 17

def options
  @options
end

#preferencesObject (readonly)

Returns the value of attribute preferences.



17
18
19
# File 'lib/ver/text.rb', line 17

def preferences
  @preferences
end

#prefix_argObject

Returns the value of attribute prefix_arg.



15
16
17
# File 'lib/ver/text.rb', line 15

def prefix_arg
  @prefix_arg
end

#pristineObject

Returns the value of attribute pristine.



15
16
17
# File 'lib/ver/text.rb', line 15

def pristine
  @pristine
end

#project_repoObject

Returns the value of attribute project_repo.



15
16
17
# File 'lib/ver/text.rb', line 15

def project_repo
  @project_repo
end

#project_rootObject

Returns the value of attribute project_root.



15
16
17
# File 'lib/ver/text.rb', line 15

def project_root
  @project_root
end

#readonlyObject

Returns the value of attribute readonly.



15
16
17
# File 'lib/ver/text.rb', line 15

def readonly
  @readonly
end

#snippetsObject (readonly)

Returns the value of attribute snippets.



17
18
19
# File 'lib/ver/text.rb', line 17

def snippets
  @snippets
end

#statusObject

Returns the value of attribute status.



15
16
17
# File 'lib/ver/text.rb', line 15

def status
  @status
end

#store_hashObject (readonly)

Returns the value of attribute store_hash.



17
18
19
# File 'lib/ver/text.rb', line 17

def store_hash
  @store_hash
end

#syntaxObject

Returns the value of attribute syntax.



17
18
19
# File 'lib/ver/text.rb', line 17

def syntax
  @syntax
end

#undoerObject

Returns the value of attribute undoer.



15
16
17
# File 'lib/ver/text.rb', line 15

def undoer
  @undoer
end

Instance Method Details

#ask(prompt, options = {}, &action) ⇒ Object



453
454
455
456
# File 'lib/ver/text.rb', line 453

def ask(prompt, options = {}, &action)
  options[:caller] ||= self
  VER.minibuf.ask(prompt, options, &action)
end

#delete(*indices) ⇒ Object



348
349
350
# File 'lib/ver/text.rb', line 348

def delete(*indices)
  Methods::Delete.delete(self, *indices)
end

#event_setupObject



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/ver/text.rb', line 121

def event_setup
  bind '<<EnterMinorMode>>' do |event|
    sync_mode_status
    sync_mode_style(event.detail)
  end

  bind '<<Modified>>' do |event|
    see :insert
    sync_position_status
  end

  bind '<<Movement>>' do |event|
    see :insert
    Methods::Selection.refresh(self)
    show_matching_brace
    sync_position_status
  end

  bind('<FocusIn>') do |event|
    on_focus_in(event)
    Tk.callback_break
  end

  bind('<FocusOut>') do |event|
    on_focus_out(event)
    Tk.callback_break
  end

  bind '<Destroy>' do |event|
    VER.cancel_block(@highlighter)
  end
end

#handle_pending_syntax_highlightsObject



426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/ver/text.rb', line 426

def handle_pending_syntax_highlights
  ignore_tags = %w[ver.highlight.pending sel]
  tag_ranges('ver.highlight.pending').each do |from, to|
    (tag_names(from) - ignore_tags).each do |tag_name|
      tag_from, _ = tag_prevrange(tag_name, from)
      from = tag_from if tag_from && from > tag_from
    end

    (tag_names(to) - ignore_tags).each do |tag_name|
      _, tag_to = tag_nextrange(tag_name, to)
      to = tag_to if tag_to && to < tag_to
    end

    from, to = index(from).linestart, index(to).lineend
    lineno = from.y - 1
    syntax.highlight(self, lineno, from, to)
    tag_all_trailing_whitespace(from: from, to: to)
    tag_all_uris(from: from, to: to)
    tag_remove('ver.highlight.pending', from, to)
  end
end

#index(idx) ⇒ Object



169
170
171
# File 'lib/ver/text.rb', line 169

def index(idx)
  Index.new(self, execute('index', idx).to_s)
end

#insert(index, string, tag = Tk::None) ⇒ Object



317
318
319
320
321
322
323
# File 'lib/ver/text.rb', line 317

def insert(index, string, tag = Tk::None)
  index = index(index) unless index.respond_to?(:to_index)

  Methods::Undo.record self do |record|
    record.insert(index, string, tag)
  end
end

#inspectObject



87
88
89
90
91
92
# File 'lib/ver/text.rb', line 87

def inspect
  details = {
    mode: major_mode
  }.map{|key, value| "%s=%p" % [key, value ] }.join(' ')
  "#<VER::Text #{details}>"
end

#layoutObject



213
214
215
# File 'lib/ver/text.rb', line 213

def layout
  buffer.layout
end

#load_preferencesObject



458
459
460
461
462
463
464
465
466
# File 'lib/ver/text.rb', line 458

def load_preferences
  return unless syntax

  name = syntax.name
  return unless file = VER.find_in_loadpath("preferences/#{name}.rb")
  @preferences = eval(file.read)
rescue Errno::ENOENT, TypeError => ex
  VER.error(ex)
end

#load_snippetsObject



468
469
470
471
472
473
474
475
476
# File 'lib/ver/text.rb', line 468

def load_snippets
  return unless syntax

  name = syntax.name
  return unless file = VER.find_in_loadpath("snippets/#{name}.rb")
  @snippets = eval(file.read)
rescue Errno::ENOENT, TypeError => ex
  VER.error(ex)
end

#load_syntax(name) ⇒ Object



410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/ver/text.rb', line 410

def load_syntax(name)
  return false unless syntax

  theme = syntax.theme

  if name.is_a?(Syntax)
    self.syntax = Syntax.new(name.name, theme)
  elsif found = Syntax.find(name)
    self.syntax = Syntax.new(name, theme)
  else
    return false
  end

  message "Syntax #{syntax.name} loaded"
end

#load_theme(name) ⇒ Object



400
401
402
403
404
405
406
407
408
# File 'lib/ver/text.rb', line 400

def load_theme(name)
  return unless syntax
  return unless found = Theme.find(name)

  syntax.theme = Theme.load(found)
  touch!('1.0', 'end')

  message "Theme #{found} loaded"
end

#mark_set(mark_name, index) ⇒ Object

Wrap Tk methods to behave as we want and to generate events



311
312
313
314
315
# File 'lib/ver/text.rb', line 311

def mark_set(mark_name, index)
  super
  return unless mark_name == :insert
  Tk::Event.generate(self, '<<Movement>>')
end

#message(*args) ⇒ Object



173
174
175
# File 'lib/ver/text.rb', line 173

def message(*args)
  VER.message(*args)
end

#noop(*args) ⇒ Object



177
178
179
# File 'lib/ver/text.rb', line 177

def noop(*args)
  # message "Noop %p in mode %p" % [args, keymap.mode]
end

#on_focus_in(event) ⇒ Object



154
155
156
157
158
159
# File 'lib/ver/text.rb', line 154

def on_focus_in(event)
  Dir.chdir(filename.dirname.to_s) if options.auto_chdir
  set_window_title
  see(:insert)
  Tk::Tile::Style.configure(buffer.style, border: 1, background: '#f00')
end

#on_focus_out(event) ⇒ Object



161
162
163
# File 'lib/ver/text.rb', line 161

def on_focus_out(event)
  Tk::Tile::Style.configure(buffer.style, border: 1, background: '#fff')
end

#peer_create(buffer) ⇒ Object



99
100
101
# File 'lib/ver/text.rb', line 99

def peer_create(buffer)
  self.class.new(buffer, peer: self)
end

#persisted?Boolean

Returns:

  • (Boolean)


69
70
71
72
73
74
75
76
77
# File 'lib/ver/text.rb', line 69

def persisted?
  return false unless filename
  return false unless filename.file?
  require 'digest/md5'

  on_disk = Digest::MD5.hexdigest(filename.read)
  in_memory = Digest::MD5.hexdigest(value)
  on_disk == in_memory
end

#prefix_countObject

Same as [prefix_arg], but returns 1 if there is no argument. Useful for [Move] methods and the like. Please note that calling this method is destructive. It will reset the state of the prefix_arg in order to avoid persistent arguments. So use it only once while your action is running, and store the result in a variable if you need it more than once.



63
64
65
66
67
# File 'lib/ver/text.rb', line 63

def prefix_count
  count = prefix_arg || 1
  update_prefix_arg(self)
  count
end

#pristine?Boolean

Returns:

  • (Boolean)


165
166
167
# File 'lib/ver/text.rb', line 165

def pristine?
  @pristine
end

#replace(index1, index2, string) ⇒ Object

Replaces the range of characters between index1 and index2 with the given characters and tags. See the section on [insert] for an explanation of the handling of the tag_list arguments, and the section on [delete] for an explanation of the handling of the indices. If index2 corresponds to an index earlier in the text than index1, an error will be generated. The deletion and insertion are arranged so that no unnecessary scrolling of the window or movement of insertion cursor occurs. In addition the undo/redo stack are correctly modified, if undo operations are active in the text widget.

replace index1 index2 chars ?tagList chars tagList …?



338
339
340
341
342
343
344
345
346
# File 'lib/ver/text.rb', line 338

def replace(index1, index2, string)
  index1 = index(index1) unless index1.respond_to?(:to_index)
  index2 = index(index2) unless index2.respond_to?(:to_index)
  return if index1 == index2

  Methods::Undo.record self do |record|
    record.replace(index1, index2, string)
  end
end

#rsearch_all(regexp, start = 'end', stop = '1.0') ⇒ Object



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/ver/text.rb', line 272

def rsearch_all(regexp, start = 'end', stop = '1.0')
  unless block_given?
    return Enumerator.new(self, :rsearch_all, regexp, start, stop)
  end

  while result = rsearch(regexp, start, stop, :count)
    pos, len = result
    break if !pos || len == 0

    from = index(pos)
    to   = index("#{pos} + #{len} chars")
    match = get(from, to)

    yield(match, from, to)

    start = from
  end
end

#search_all(regexp, start = '1.0', stop = 'end - 1 chars') ⇒ Object



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/ver/text.rb', line 253

def search_all(regexp, start = '1.0', stop = 'end - 1 chars')
  unless block_given?
    return Enumerator.new(self, :search_all, regexp, start, stop)
  end

  while result = search(regexp, start, stop, :count)
    pos, len = result
    return if !pos || len == 0

    from  = index(pos)
    to    = index("#{pos} + #{len} chars")
    match = get(from, to)

    yield(match, from, to)

    start = to
  end
end

#set_window_titleObject



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/ver/text.rb', line 352

def set_window_title
  if filename
    home = Pathname(ENV['HOME'])
    dir, file = filename.split
    dir_relative_to_home = dir.relative_path_from(home)

    if dir_relative_to_home.to_s.start_with?('../')
      title = "#{file} (#{dir}) - VER"
    else
      title = "#{file} (#{dir_relative_to_home}) - VER"
    end
  elsif name
    title = "[#{name}] - VER"
  else
    title = "[No Name] - VER"
  end

  VER.root.wm_title = title
end

#setup_highlightObject



372
373
374
# File 'lib/ver/text.rb', line 372

def setup_highlight
  setup_highlight_for(Syntax.from_filename(filename)) if filename
end

#setup_highlight_for(syntax) ⇒ Object



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/ver/text.rb', line 376

def setup_highlight_for(syntax)
  return if encoding == Encoding::BINARY
  return unless syntax

  self.syntax = syntax
  VER.cancel_block(@highlighter)

  interval = options.syntax_highlight_interval.to_int
  @highlighter = VER.when_inactive_for(interval){
    handle_pending_syntax_highlights
  }

  touch!('1.0', 'end')

  sync_mode_status
end

#short_filenameObject



181
182
183
184
185
186
187
188
189
190
191
# File 'lib/ver/text.rb', line 181

def short_filename
  if filename
    if root = @project_root
      filename.relative_path_from(root).to_s
    else
      filename.sub(Dir.pwd + '/', '').to_s
    end
  elsif name
    name.to_s
  end
end

#store(namespace, key, value = None) ⇒ Object



79
80
81
82
83
84
85
# File 'lib/ver/text.rb', line 79

def store(namespace, key, value = None)
  if None == value
    @store_hash[namespace][key]
  else
    @store_hash[namespace][key] = value
  end
end

#sync_encoding_statusObject



225
226
227
# File 'lib/ver/text.rb', line 225

def sync_encoding_status
  status.event :encoding
end

#sync_mode_statusObject



217
218
219
# File 'lib/ver/text.rb', line 217

def sync_mode_status
  status.event :mode
end

#sync_mode_style(given_mode = nil) ⇒ Object



478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
# File 'lib/ver/text.rb', line 478

def sync_mode_style(given_mode = nil)
  config = (default_theme_config || {}).merge(blockcursor: false)

  modes = given_mode ? [given_mode] : major_mode.minors

  modes.each do |mode|
    mode = MinorMode[mode]

    MODE_STYLES.each do |pattern, style|
      config.merge!(style) if pattern === mode.name
    end
  end

  configure(config)
  return unless status && color = config[:insertbackground]

  status.style = {
    background: cget(:background),
    foreground: color,
  }
end

#sync_percent_statusObject



229
230
231
# File 'lib/ver/text.rb', line 229

def sync_percent_status
  status.event :percent
end

#sync_position_statusObject



221
222
223
# File 'lib/ver/text.rb', line 221

def sync_position_status
  status.event :position, :percent
end

#tag_all_matching(name, regexp, options = {}) ⇒ Object



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/ver/text.rb', line 235

def tag_all_matching(name, regexp, options = {})
  name = name.to_s
  options = TAG_ALL_MATCHING_OPTIONS.merge(options)
  from, to = options.values_at(:from, :to)

  if tag_exists?(name)
    tag_remove(name, from, to)
  else
    fg, bg = options.values_at(:foreground, :background)
    tag_configure(name, foreground: fg, background: bg)
  end

  search_all(regexp, from, to) do |match, match_from, match_to|
    name = yield(match, match_from, match_to) if block_given?
    tag_add name, match_from, match_to
  end
end

#tag_exists?(given_path) ⇒ Boolean

Returns:

  • (Boolean)


304
305
306
307
308
# File 'lib/ver/text.rb', line 304

def tag_exists?(given_path)
  tag_names.include?(given_path)
rescue RuntimeError => ex
  false
end

#touch!(*indices) ⇒ Object

TODO: maybe we can make this one faster when many lines are going to be

highlighted at once by bundling them.


395
396
397
398
# File 'lib/ver/text.rb', line 395

def touch!(*indices)
  tag_add('ver.highlight.pending', *indices) if @syntax
  Tk::Event.generate(self, '<<Modified>>')
end

#up_down_line(count) ⇒ Object



291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/ver/text.rb', line 291

def up_down_line(count)
  insert = index(:insert)

  @udl_pos_orig = insert if @udl_pos_prev != insert

  lines = count(@udl_pos_orig, insert, :displaylines)
  target = index("#@udl_pos_orig + #{lines + count} displaylines")
  @udl_pos_prev = target

  @udl_pos_orig = target if target.x == @udl_pos_orig.x
  target
end

#update_prefix_arg(widget) ⇒ Object

This is a noop, it simply provides a target with a sane name.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/ver/text.rb', line 41

def update_prefix_arg(widget)
  numbers = []

  major_mode.event_history.reverse_each do |event|
    break unless event[:sequence] =~ /^(\d+)$/
    numbers << $1
  end

  if numbers.any? && numbers != ['0']
    self.prefix_arg = numbers.reverse.join.to_i
  else
    self.prefix_arg = nil
  end
end

#value=(string) ⇒ Object



94
95
96
97
# File 'lib/ver/text.rb', line 94

def value=(string)
  super
  touch!('1.0', 'end')
end

#widget_setup(buffer) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/ver/text.rb', line 103

def widget_setup(buffer)
  self.buffer = buffer
  @options = Options.new(:text, VER.options)

  @undoer = VER::Undo::Tree.new(self)

  self.major_mode = :Fundamental

  sync_mode_style
  setup_tags

  @pristine = true
  @syntax = nil
  self.encoding = Encoding.default_internal

  event_setup
end