Class: RuVim::Buffer

Inherits:
Object
  • Object
show all
Defined in:
lib/ruvim/buffer.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(id:, path: nil, lines: [""], kind: :file, name: nil, readonly: false, modifiable: true) ⇒ Buffer

Returns a new instance of Buffer.



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/ruvim/buffer.rb', line 67

def initialize(id:, path: nil, lines: [""], kind: :file, name: nil, readonly: false, modifiable: true)
  @id = id
  @path = path
  @kind = kind
  @name = name
  @lines = lines.dup
  @lines = [""] if @lines.empty?
  @modified = false
  @readonly = !!readonly
  @modifiable = !!modifiable
  @stream = nil
  @undo_stack = []
  @redo_stack = []
  @change_group_depth = 0
  @group_before_snapshot = nil
  @group_changed = false
  @recording_suspended = false
  @lang_module = Lang::Base
  @options = {}
end

Instance Attribute Details

#idObject (readonly)

Returns the value of attribute id.



7
8
9
# File 'lib/ruvim/buffer.rb', line 7

def id
  @id
end

#kindObject (readonly)

Returns the value of attribute kind.



7
8
9
# File 'lib/ruvim/buffer.rb', line 7

def kind
  @kind
end

#lang_moduleObject

Returns the value of attribute lang_module.



21
22
23
# File 'lib/ruvim/buffer.rb', line 21

def lang_module
  @lang_module
end

#modified=(value) ⇒ Object (writeonly)

Sets the attribute modified

Parameters:

  • value

    the value to set the attribute modified to.



10
11
12
# File 'lib/ruvim/buffer.rb', line 10

def modified=(value)
  @modified = value
end

#nameObject (readonly)

Returns the value of attribute name.



7
8
9
# File 'lib/ruvim/buffer.rb', line 7

def name
  @name
end

#optionsObject (readonly)

Returns the value of attribute options.



9
10
11
# File 'lib/ruvim/buffer.rb', line 9

def options
  @options
end

#pathObject

Returns the value of attribute path.



8
9
10
# File 'lib/ruvim/buffer.rb', line 8

def path
  @path
end

#streamObject

Returns the value of attribute stream.



11
12
13
# File 'lib/ruvim/buffer.rb', line 11

def stream
  @stream
end

Class Method Details

.decode_text(bytes) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/ruvim/buffer.rb', line 50

def self.decode_text(bytes)
  s = bytes.to_s
  return s.dup if s.encoding == Encoding::UTF_8 && s.valid_encoding?

  utf8 = s.dup.force_encoding(Encoding::UTF_8)
  return utf8 if utf8.valid_encoding?

  ext = Encoding.default_external
  if ext && ext != Encoding::UTF_8
    return utf8.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
  end

  utf8.scrub
rescue StandardError
  bytes.to_s.dup.force_encoding(Encoding::UTF_8).scrub
end

.ensure_regular_file!(path) ⇒ Object



23
24
25
# File 'lib/ruvim/buffer.rb', line 23

def self.ensure_regular_file!(path)
  raise RuVim::CommandError, "Not a regular file: #{path}" if File.exist?(path) && !File.file?(path)
end

.from_file(id:, path:) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
# File 'lib/ruvim/buffer.rb', line 27

def self.from_file(id:, path:)
  ensure_regular_file!(path)
  lines =
    if File.file?(path)
      data = decode_text(File.binread(path))
      split_lines(data)
    else
      [""]
    end
  new(id:, path:, lines:)
end

.split_lines(data) ⇒ Object



39
40
41
42
43
44
45
46
47
48
# File 'lib/ruvim/buffer.rb', line 39

def self.split_lines(data)
  return [""] if data.empty?

  lines = data.split("\n", -1)
  if data.end_with?("\n")
    lines.pop
  end
  lines = [""] if lines.empty?
  lines
end

Instance Method Details

#append_stream_lines!(head_suffix, lines) ⇒ Object

Append pre-split lines (from background thread). head_suffix is concatenated to the current last line.



371
372
373
374
375
376
# File 'lib/ruvim/buffer.rb', line 371

def append_stream_lines!(head_suffix, lines)
  @lines[-1] = @lines[-1] + head_suffix unless head_suffix.empty?
  @lines.concat(lines) unless lines.empty?
  @lines = [""] if @lines.empty?
  @modified = false
end

#append_stream_text!(text) ⇒ Object

Append externally-streamed text without touching undo history or modifiable state.



357
358
359
360
361
362
363
364
365
366
367
# File 'lib/ruvim/buffer.rb', line 357

def append_stream_text!(text)
  return [@lines.length - 1, @lines[-1].length] if text.empty?

  parts = text.split("\n", -1)
  head = parts.shift || ""
  @lines[-1] = @lines[-1] + head
  @lines.concat(parts)
  @lines = [""] if @lines.empty?
  @modified = false
  [@lines.length - 1, @lines[-1].length]
end

#backspace(row, col) ⇒ Object



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/ruvim/buffer.rb', line 250

def backspace(row, col)
  if col > 0
    record_change_before_mutation!
    line = @lines.fetch(row)
    @lines[row] = line[0...(col - 1)] + line[col..].to_s
    @modified = true
    return [row, col - 1]
  end

  return [row, col] if row.zero?

  record_change_before_mutation!
  prev = @lines.fetch(row - 1)
  cur = @lines.fetch(row)
  new_col = prev.length
  @lines[row - 1] = prev + cur
  @lines.delete_at(row)
  @modified = true
  [row - 1, new_col]
end

#become_normal_empty_buffer!Object



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/ruvim/buffer.rb', line 137

def become_normal_empty_buffer!
  @kind = :file
  @name = nil
  @path = nil
  @readonly = false
  @modifiable = true
  @stream = nil
  @lines = [""]
  @modified = false
  @undo_stack.clear
  @redo_stack.clear
  @change_group_depth = 0
  @group_before_snapshot = nil
  @group_changed = false
  self
end

#begin_change_groupObject



162
163
164
165
# File 'lib/ruvim/buffer.rb', line 162

def begin_change_group
  @change_group_depth += 1
  nil
end

#can_redo?Boolean

Returns:

  • (Boolean)


158
159
160
# File 'lib/ruvim/buffer.rb', line 158

def can_redo?
  !@redo_stack.empty?
end

#can_undo?Boolean

Returns:

  • (Boolean)


154
155
156
# File 'lib/ruvim/buffer.rb', line 154

def can_undo?
  !@undo_stack.empty?
end

#configure_special!(kind:, name: nil, readonly: true, modifiable: false) ⇒ Object



128
129
130
131
132
133
134
135
# File 'lib/ruvim/buffer.rb', line 128

def configure_special!(kind:, name: nil, readonly: true, modifiable: false)
  @kind = kind
  @name = name
  @readonly = !!readonly
  @modifiable = !!modifiable
  @stream = nil unless @kind == :stream
  self
end

#delete_char(row, col) ⇒ Object



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/ruvim/buffer.rb', line 271

def delete_char(row, col)
  line = @lines.fetch(row)
  if col < line.length
    record_change_before_mutation!
    @lines[row] = line[0...col] + line[(col + 1)..].to_s
    @modified = true
    return true
  end

  return false if row >= @lines.length - 1

  record_change_before_mutation!
  @lines[row] = line + @lines[row + 1]
  @lines.delete_at(row + 1)
  @modified = true
  true
end

#delete_line(row) ⇒ Object



289
290
291
292
293
294
295
296
# File 'lib/ruvim/buffer.rb', line 289

def delete_line(row)
  row = [[row, 0].max, @lines.length - 1].min
  record_change_before_mutation!
  deleted = @lines.delete_at(row)
  @lines << "" if @lines.empty?
  @modified = true
  deleted
end

#delete_span(start_row, start_col, end_row, end_col) ⇒ Object



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/ruvim/buffer.rb', line 298

def delete_span(start_row, start_col, end_row, end_col)
  s_row, s_col, e_row, e_col = normalize_span(start_row, start_col, end_row, end_col)
  return false if s_row == e_row && s_col == e_col

  record_change_before_mutation!

  if s_row == e_row
    line = @lines.fetch(s_row)
    @lines[s_row] = line[0...s_col] + line[e_col..].to_s
  else
    head = @lines.fetch(s_row)[0...s_col]
    tail = @lines.fetch(e_row)[e_col..].to_s
    @lines[s_row] = head + tail
    (e_row - s_row).times { @lines.delete_at(s_row + 1) }
  end

  @lines << "" if @lines.empty?
  @modified = true
  true
end

#display_nameObject



124
125
126
# File 'lib/ruvim/buffer.rb', line 124

def display_name
  @name || @path || "[No Name]"
end

#end_change_groupObject



167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/ruvim/buffer.rb', line 167

def end_change_group
  return nil if @change_group_depth.zero?

  @change_group_depth -= 1
  return nil unless @change_group_depth.zero?

  if @group_changed && @group_before_snapshot
    @undo_stack << @group_before_snapshot
    @redo_stack.clear
  end
  @group_before_snapshot = nil
  @group_changed = false
  nil
end

#file_buffer?Boolean

Returns:

  • (Boolean)


112
113
114
# File 'lib/ruvim/buffer.rb', line 112

def file_buffer?
  @kind == :file
end

#finalize_async_file_load!(ended_with_newline:) ⇒ Object



378
379
380
381
382
383
384
385
# File 'lib/ruvim/buffer.rb', line 378

def finalize_async_file_load!(ended_with_newline:)
  if ended_with_newline && @lines.length > 1 && @lines[-1] == ""
    @lines.pop
  end
  @lines = [""] if @lines.empty?
  @modified = false
  self
end

#insert_char(row, col, char) ⇒ Object



220
221
222
223
224
225
# File 'lib/ruvim/buffer.rb', line 220

def insert_char(row, col, char)
  record_change_before_mutation!
  line = @lines.fetch(row)
  @lines[row] = line.dup.insert(col, char)
  @modified = true
end

#insert_lines_at(index, new_lines) ⇒ Object



339
340
341
342
343
344
345
346
347
# File 'lib/ruvim/buffer.rb', line 339

def insert_lines_at(index, new_lines)
  lines = Array(new_lines).map(&:to_s)
  return if lines.empty?

  record_change_before_mutation!
  idx = [[index, 0].max, @lines.length].min
  @lines.insert(idx, *lines)
  @modified = true
end

#insert_newline(row, col) ⇒ Object



239
240
241
242
243
244
245
246
247
248
# File 'lib/ruvim/buffer.rb', line 239

def insert_newline(row, col)
  record_change_before_mutation!
  line = @lines.fetch(row)
  head = line[0...col]
  tail = line[col..] || ""
  @lines[row] = head
  @lines.insert(row + 1, tail)
  @modified = true
  [row + 1, 0]
end

#insert_text(row, col, text) ⇒ Object



227
228
229
230
231
232
233
234
235
236
237
# File 'lib/ruvim/buffer.rb', line 227

def insert_text(row, col, text)
  text.each_char do |ch|
    if ch == "\n"
      row, col = insert_newline(row, col)
    else
      insert_char(row, col, ch)
      col += 1
    end
  end
  [row, col]
end

#intro_buffer?Boolean

Returns:

  • (Boolean)


116
117
118
# File 'lib/ruvim/buffer.rb', line 116

def intro_buffer?
  @kind == :intro
end

#line_at(row) ⇒ Object



212
213
214
# File 'lib/ruvim/buffer.rb', line 212

def line_at(row)
  @lines.fetch(row)
end

#line_block_text(start_row, count = 1) ⇒ Object



334
335
336
337
# File 'lib/ruvim/buffer.rb', line 334

def line_block_text(start_row, count = 1)
  rows = @lines[start_row, count] || []
  rows.join("\n") + "\n"
end

#line_countObject



208
209
210
# File 'lib/ruvim/buffer.rb', line 208

def line_count
  @lines.length
end

#line_length(row) ⇒ Object



216
217
218
# File 'lib/ruvim/buffer.rb', line 216

def line_length(row)
  @lines.fetch(row).length
end

#linesObject



88
89
90
# File 'lib/ruvim/buffer.rb', line 88

def lines
  @lines
end

#modifiable=(value) ⇒ Object



108
109
110
# File 'lib/ruvim/buffer.rb', line 108

def modifiable=(value)
  @modifiable = !!value
end

#modifiable?Boolean

Returns:

  • (Boolean)


104
105
106
# File 'lib/ruvim/buffer.rb', line 104

def modifiable?
  @modifiable && !@stream&.live?
end

#modified?Boolean

Returns:

  • (Boolean)


92
93
94
# File 'lib/ruvim/buffer.rb', line 92

def modified?
  !!@modified
end

#readonly=(value) ⇒ Object



100
101
102
# File 'lib/ruvim/buffer.rb', line 100

def readonly=(value)
  @readonly = !!value
end

#readonly?Boolean

Returns:

  • (Boolean)


96
97
98
# File 'lib/ruvim/buffer.rb', line 96

def readonly?
  @readonly
end

#redo!Object



195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/ruvim/buffer.rb', line 195

def redo!
  close_change_group_if_open
  return false unless can_redo?

  current = snapshot
  nxt = @redo_stack.pop
  with_recording_suspended do
    restore_snapshot(nxt)
  end
  @undo_stack << current
  true
end

#reload_from_file!(path = nil) ⇒ Object



399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
# File 'lib/ruvim/buffer.rb', line 399

def reload_from_file!(path = nil)
  target = path || @path
  raise RuVim::CommandError, "No file name" if target.nil? || target.empty?

  self.class.ensure_regular_file!(target)

  data = File.file?(target) ? self.class.decode_text(File.binread(target)) : ""
  @lines = self.class.split_lines(data)
  @path = target
  @modified = false
  @undo_stack.clear
  @redo_stack.clear
  @change_group_depth = 0
  @group_before_snapshot = nil
  @group_changed = false
  target
end

#replace_all_lines!(new_lines) ⇒ Object



349
350
351
352
353
354
# File 'lib/ruvim/buffer.rb', line 349

def replace_all_lines!(new_lines)
  record_change_before_mutation!
  @lines = Array(new_lines).map(&:dup)
  @lines = [""] if @lines.empty?
  @modified = true
end

#span_text(start_row, start_col, end_row, end_col) ⇒ Object



319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/ruvim/buffer.rb', line 319

def span_text(start_row, start_col, end_row, end_col)
  s_row, s_col, e_row, e_col = normalize_span(start_row, start_col, end_row, end_col)
  return "" if s_row == e_row && s_col == e_col

  if s_row == e_row
    return @lines.fetch(s_row)[s_col...e_col].to_s
  end

  parts = []
  parts << @lines.fetch(s_row)[s_col..].to_s
  ((s_row + 1)...e_row).each { |row| parts << @lines.fetch(row) }
  parts << @lines.fetch(e_row)[0...e_col].to_s
  parts.join("\n")
end

#stream_commandObject



17
18
19
# File 'lib/ruvim/buffer.rb', line 17

def stream_command
  @stream&.command
end

#stream_statusObject



13
14
15
# File 'lib/ruvim/buffer.rb', line 13

def stream_status
  @stream&.status
end

#undo!Object



182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/ruvim/buffer.rb', line 182

def undo!
  close_change_group_if_open
  return false unless can_undo?

  current = snapshot
  prev = @undo_stack.pop
  with_recording_suspended do
    restore_snapshot(prev)
  end
  @redo_stack << current
  true
end

#virtual_buffer?Boolean

Returns:

  • (Boolean)


120
121
122
# File 'lib/ruvim/buffer.rb', line 120

def virtual_buffer?
  !file_buffer?
end

#write_to(path = nil) ⇒ Object



387
388
389
390
391
392
393
394
395
396
397
# File 'lib/ruvim/buffer.rb', line 387

def write_to(path = nil)
  raise RuVim::CommandError, "Buffer is readonly" if readonly?

  target = path || @path
  raise RuVim::CommandError, "No file name" if target.nil? || target.empty?

  File.binwrite(target, @lines.join("\n"))
  @path = target
  @modified = false
  target
end