class Textbringer::Buffer
Constants
- DEFAULT_DETECT_ENCODING
- GAP_SIZE
- NKF_DETECT_ENCODING
- UNDO_LIMIT
- UTF8_CHAR_LEN
Attributes
current_column[R]
current_line[R]
file_encoding[R]
file_format[R]
file_name[R]
input_method[R]
keymap[RW]
marks[R]
mode[RW]
name[R]
point[R]
visible_mark[R]
Public Class Methods
[](name)
click to toggle source
# File lib/textbringer/buffer.rb, line 134 def self.[](name) @@table[name] end
add(buffer)
click to toggle source
# File lib/textbringer/buffer.rb, line 91 def self.add(buffer) @@table[buffer.name] = buffer @@list.push(buffer) end
auto_detect_encodings()
click to toggle source
# File lib/textbringer/buffer.rb, line 71 def self.auto_detect_encodings @@auto_detect_encodings end
auto_detect_encodings=(encodings)
click to toggle source
# File lib/textbringer/buffer.rb, line 75 def self.auto_detect_encodings=(encodings) @@auto_detect_encodings = encodings end
bury(buffer = @@current)
click to toggle source
# File lib/textbringer/buffer.rb, line 124 def self.bury(buffer = @@current) @@list.delete(buffer) @@list.push(buffer) @@current = @@list.first end
count()
click to toggle source
# File lib/textbringer/buffer.rb, line 130 def self.count @@table.size end
current()
click to toggle source
# File lib/textbringer/buffer.rb, line 96 def self.current @@current end
current=(buffer)
click to toggle source
# File lib/textbringer/buffer.rb, line 100 def self.current=(buffer) if buffer && buffer.name && @@table.key?(buffer.name) @@list.delete(buffer) @@list.unshift(buffer) end @@current = buffer end
detect_encoding_proc()
click to toggle source
# File lib/textbringer/buffer.rb, line 79 def self.detect_encoding_proc @@detect_encoding_proc end
detect_encoding_proc=(f)
click to toggle source
# File lib/textbringer/buffer.rb, line 83 def self.detect_encoding_proc=(f) @@detect_encoding_proc = f end
display_width(s)
click to toggle source
# File lib/textbringer/buffer.rb, line 189 def self.display_width(s) Unicode::DisplayWidth.of(s, CONFIG[:east_asian_ambiguous_width]) end
dump_unsaved_buffers(dir)
click to toggle source
# File lib/textbringer/buffer.rb, line 1311 def self.dump_unsaved_buffers(dir) FileUtils.mkdir_p(dir) @@list.each do |buffer| if /\A\*/ !~ buffer.name && buffer.modified? buffer.dump(File.expand_path(buffer.object_id.to_s, dir)) end end end
dumped_buffers_exist?(dir)
click to toggle source
# File lib/textbringer/buffer.rb, line 1320 def self.dumped_buffers_exist?(dir) !Dir.glob(File.expand_path("*.metadata", dir)).empty? end
each(&block)
click to toggle source
# File lib/textbringer/buffer.rb, line 185 def self.each(&block) @@list.each(&block) end
find_file(file_name)
click to toggle source
# File lib/textbringer/buffer.rb, line 152 def self.find_file(file_name) file_name = File.expand_path(file_name) buffer = @@table.each_value.find { |b| b.file_name == file_name } if buffer.nil? name = File.basename(file_name) begin buffer = Buffer.open(file_name, name: new_buffer_name(name)) add(buffer) rescue Errno::ENOENT buffer = new_buffer(name, file_name: file_name) end end buffer end
find_or_new(name, **opts)
click to toggle source
# File lib/textbringer/buffer.rb, line 138 def self.find_or_new(name, **opts) @@table[name] ||= new_buffer(name, **opts) end
global_mark_ring()
click to toggle source
# File lib/textbringer/buffer.rb, line 112 def self.global_mark_ring @@global_mark_ring ||= Ring.new(CONFIG[:global_mark_ring_max]) end
kill_em_all()
click to toggle source
# File lib/textbringer/buffer.rb, line 146 def self.kill_em_all @@table.clear @@list.clear @@current = nil end
last()
click to toggle source
# File lib/textbringer/buffer.rb, line 120 def self.last @@list.last end
list()
click to toggle source
# File lib/textbringer/buffer.rb, line 87 def self.list @@list.dup end
load(path)
click to toggle source
# File lib/textbringer/buffer.rb, line 1300 def self.load(path) buffer = Buffer.new(File.binread(path)) metadata = JSON.parse(File.binread(path + ".metadata")) buffer.name = metadata["name"] buffer.file_name = metadata["file_name"] if metadata["file_name"] buffer.file_encoding = Encoding.find(metadata["file_encoding"]) buffer.file_format = metadata["file_format"].intern buffer.modified = true buffer end
load_dumped_buffers(dir)
click to toggle source
# File lib/textbringer/buffer.rb, line 1324 def self.load_dumped_buffers(dir) Dir.glob(File.expand_path("*.metadata", dir)).map do |metadata_path| path = metadata_path.sub(/\.metadata\z/, "") buffer = Buffer.load(path) add(buffer) File.unlink(metadata_path) File.unlink(path) buffer end end
minibuffer()
click to toggle source
# File lib/textbringer/buffer.rb, line 108 def self.minibuffer @@minibuffer ||= Buffer.new(name: "*Minibuffer*") end
names()
click to toggle source
# File lib/textbringer/buffer.rb, line 142 def self.names @@table.keys end
new(s = +"", name: nil, file_name: nil, file_encoding: CONFIG[:default_file_encoding], file_mtime: nil, new_file: true, undo_limit: UNDO_LIMIT, read_only: false)
click to toggle source
s might not be copied.
# File lib/textbringer/buffer.rb, line 207 def initialize(s = +"", name: nil, file_name: nil, file_encoding: CONFIG[:default_file_encoding], file_mtime: nil, new_file: true, undo_limit: UNDO_LIMIT, read_only: false) set_contents(s, file_encoding) @name = name @file_name = file_name self.file_encoding = file_encoding @file_mtime = file_mtime @new_file = new_file @undo_limit = undo_limit @point = 0 @gap_start = 0 @gap_end = 0 @marks = [] @mark = nil @mark_ring = Ring.new(CONFIG[:mark_ring_max], on_delete: ->(mark) { mark.delete }) @current_line = 1 @current_column = 1 # One-based character count @goal_column = nil # Zero-based display width count @undo_stack = [] @redo_stack = [] @undoing = false @composite_edit_level = 0 @composite_edit_actions = [] @version = 0 @modified = false @mode = FundamentalMode.new(self) @keymap = nil @attributes = {} @save_point_level = 0 @match_offsets = [] @visible_mark = nil @read_only = read_only @callbacks = {} @input_method = nil end
new_buffer(name, **opts)
click to toggle source
# File lib/textbringer/buffer.rb, line 169 def self.new_buffer(name, **opts) buffer = Buffer.new(**opts.merge(name: new_buffer_name(name))) add(buffer) buffer end
new_buffer_name(name)
click to toggle source
# File lib/textbringer/buffer.rb, line 175 def self.new_buffer_name(name) if @@table.key?(name) (2..Float::INFINITY).lazy.map { |i| "#{name}<#{i}>" }.find { |i| !@@table.key?(i) } else name end end
open(file_name, name: File.basename(file_name))
click to toggle source
# File lib/textbringer/buffer.rb, line 367 def self.open(file_name, name: File.basename(file_name)) buffer = Buffer.new(name: name, file_name: file_name, new_file: false) buffer.revert buffer.read_only = !File.writable?(file_name) buffer end
other(buffer = @@current)
click to toggle source
# File lib/textbringer/buffer.rb, line 116 def self.other(buffer = @@current) @@list.find { |buf| buf != buffer } || Buffer.find_or_new("*scratch*") end
region_boundaries(s, e)
click to toggle source
# File lib/textbringer/buffer.rb, line 910 def self.region_boundaries(s, e) if s > e [e, s] else [s, e] end end
Public Instance Methods
[](name)
click to toggle source
# File lib/textbringer/buffer.rb, line 351 def [](name) if @attributes.key?(name) @attributes[name] else CONFIG[name] end end
[]=(name, value)
click to toggle source
# File lib/textbringer/buffer.rb, line 359 def []=(name, value) @attributes[name] = value end
apply_mode(mode_class)
click to toggle source
# File lib/textbringer/buffer.rb, line 1274 def apply_mode(mode_class) @keymap = nil @mode = mode_class.new(self) Utils.run_hooks(mode_class.hook_name) end
backward_char(n = 1)
click to toggle source
# File lib/textbringer/buffer.rb, line 648 def backward_char(n = 1) forward_char(-n) end
backward_delete_char(n = 1)
click to toggle source
# File lib/textbringer/buffer.rb, line 637 def backward_delete_char(n = 1) delete_char(-n) end
backward_line(n = 1)
click to toggle source
# File lib/textbringer/buffer.rb, line 696 def backward_line(n = 1) forward_line(-n) end
backward_word(n = 1, regexp: /\p{Letter}|\p{Number}/)
click to toggle source
# File lib/textbringer/buffer.rb, line 663 def backward_word(n = 1, regexp: /\p{Letter}|\p{Number}/) n.times do break if beginning_of_buffer? backward_char while !beginning_of_buffer? && regexp !~ char_after backward_char end while !beginning_of_buffer? && regexp =~ char_after backward_char end if regexp !~ char_after forward_char end end end
beginning_of_buffer()
click to toggle source
# File lib/textbringer/buffer.rb, line 721 def beginning_of_buffer if @save_point_level == 0 @current_line = 1 @current_column = 1 end @point = 0 end
beginning_of_buffer?()
click to toggle source
# File lib/textbringer/buffer.rb, line 729 def beginning_of_buffer? @point == 0 end
beginning_of_line()
click to toggle source
# File lib/textbringer/buffer.rb, line 741 def beginning_of_line while !beginning_of_line? backward_char end @point end
beginning_of_line?()
click to toggle source
# File lib/textbringer/buffer.rb, line 748 def beginning_of_line? beginning_of_buffer? || byte_before == "\n" end
binary?()
click to toggle source
# File lib/textbringer/buffer.rb, line 274 def binary? @binary end
byte_after(location = @point)
click to toggle source
# File lib/textbringer/buffer.rb, line 457 def byte_after(location = @point) if location < @gap_start @contents.byteslice(location) else @contents.byteslice(location + gap_size) end end
byte_before(location = @point)
click to toggle source
# File lib/textbringer/buffer.rb, line 465 def byte_before(location = @point) if location <= point_min || location > point_max nil else byte_after(location - 1) end end
byteindex(forward, re, pos)
click to toggle source
# File lib/textbringer/buffer.rb, line 1128 def byteindex(forward, re, pos) @match_offsets = [] method = forward ? :index : :rindex adjust_gap(0, point_max) s = @contents[0...@gap_start] if @binary offset = pos else offset = s.byteslice(0, pos).force_encoding(Encoding::UTF_8).size s.force_encoding(Encoding::UTF_8) end begin i = s.send(method, re, offset) if i m = Regexp.last_match if m.nil? # A bug of rindex @match_offsets.push([pos, pos]) pos else b = m.pre_match.bytesize e = b + m.to_s.bytesize if e <= bytesize @match_offsets.push([b, e]) match_beg = m.begin(0) match_str = m.to_s (1 .. m.size - 1).each do |j| cb, ce = m.offset(j) if cb.nil? @match_offsets.push([nil, nil]) else bb = b + match_str[0, cb - match_beg].bytesize be = b + match_str[0, ce - match_beg].bytesize @match_offsets.push([bb, be]) end end b else nil end end else nil end end end
bytesize()
click to toggle source
# File lib/textbringer/buffer.rb, line 495 def bytesize @contents.bytesize - gap_size end
Also aliased as: size
char_after(location = @point)
click to toggle source
# File lib/textbringer/buffer.rb, line 473 def char_after(location = @point) if @binary byte_after(location) else s = substring(location, location + UTF8_CHAR_LEN[byte_after(location)]) s.empty? ? nil : s end end
char_before(location = @point)
click to toggle source
# File lib/textbringer/buffer.rb, line 482 def char_before(location = @point) if @binary byte_before(location) else if beginning_of_buffer? nil else pos = get_pos(location, -1) substring(pos, location) end end end
clear()
click to toggle source
# File lib/textbringer/buffer.rb, line 966 def clear check_read_only_flag @contents = +"" @point = @gap_start = @gap_end = 0 @marks.each do |m| m.location = 0 end @current_line = 1 @current_column = 1 @goal_column = nil self.modified = true @undo_stack.clear @redo_stack.clear end
composite_edit() { || ... }
click to toggle source
# File lib/textbringer/buffer.rb, line 1254 def composite_edit @composite_edit_level += 1 begin yield ensure @composite_edit_level -= 1 if @composite_edit_level == 0 && !@composite_edit_actions.empty? action = CompositeAction.new(self, @composite_edit_actions.first.location) @composite_edit_actions.each do |i| action.add_action(i) end action.version = @composite_edit_actions.first.version push_undo(action) @composite_edit_actions.clear end end fire_callbacks(:modified) end
copy_region(s = @point, e = mark, append = false)
click to toggle source
# File lib/textbringer/buffer.rb, line 918 def copy_region(s = @point, e = mark, append = false) s, e = Buffer.region_boundaries(s, e) str = substring(s, e) if append && !KILL_RING.empty? KILL_RING.current.concat(str) else KILL_RING.push(str) end end
current?()
click to toggle source
# File lib/textbringer/buffer.rb, line 327 def current? @@current == self end
current_symbol()
click to toggle source
# File lib/textbringer/buffer.rb, line 1335 def current_symbol from = save_point { skip_re_backward(@mode.symbol_pattern); @point } to = save_point { skip_re_forward(@mode.symbol_pattern); @point } from < to ? substring(from, to) : nil end
delete_char(n = 1)
click to toggle source
# File lib/textbringer/buffer.rb, line 599 def delete_char(n = 1) check_read_only_flag adjust_gap s = @point pos = get_pos(@point, n) if n > 0 str = substring(s, pos) # fill the gap with NUL to avoid invalid byte sequence in UTF-8 @contents[@gap_end...user_to_gap(pos)] = "\0" * (pos - @point) @gap_end += pos - @point @marks.each do |m| if m.location > pos m.location -= pos - @point elsif m.location > @point m.location = @point end end push_undo(DeleteAction.new(self, s, s, str)) self.modified = true elsif n < 0 str = substring(pos, s) update_line_and_column(@point, pos) # fill the gap with NUL to avoid invalid byte sequence in UTF-8 @contents[user_to_gap(pos)...@gap_start] = "\0" * (@point - pos) @marks.each do |m| if m.location >= @point m.location -= @point - pos elsif m.location > pos m.location = pos end end @point = @gap_start = pos push_undo(DeleteAction.new(self, s, pos, str)) self.modified = true end @goal_column = nil end
delete_region(s = @point, e = mark)
click to toggle source
# File lib/textbringer/buffer.rb, line 933 def delete_region(s = @point, e = mark) check_read_only_flag old_pos = @point s, e = Buffer.region_boundaries(s, e) update_line_and_column(old_pos, s) save_point do str = substring(s, e) @point = s adjust_gap len = e - s # fill the gap with NUL to avoid invalid byte sequence in UTF-8 @contents[@gap_end, len] = "\0" * len @gap_end += len @marks.each do |m| if m.location > e m.location -= len elsif m.location > s m.location = s end end push_undo(DeleteAction.new(self, old_pos, s, str)) self.modified = true end end
delete_visible_mark()
click to toggle source
# File lib/textbringer/buffer.rb, line 903 def delete_visible_mark if @visible_mark @visible_mark.delete @visible_mark = nil end end
disable_input_method()
click to toggle source
# File lib/textbringer/buffer.rb, line 1399 def disable_input_method @input_method&.disable end
display_width(s)
click to toggle source
# File lib/textbringer/buffer.rb, line 202 def display_width(s) Buffer.display_width(expand_tab(s)) end
dump(path)
click to toggle source
# File lib/textbringer/buffer.rb, line 1289 def dump(path) File.binwrite(path, to_s) metadata = { "name" => name, "file_name" => file_name, "file_encoding" => file_encoding.name, "file_format" => file_format.to_s } File.binwrite(path + ".metadata", metadata.to_json) end
end_of_buffer()
click to toggle source
# File lib/textbringer/buffer.rb, line 733 def end_of_buffer goto_char(bytesize) end
end_of_buffer?()
click to toggle source
# File lib/textbringer/buffer.rb, line 737 def end_of_buffer? @point == bytesize end
end_of_line()
click to toggle source
# File lib/textbringer/buffer.rb, line 752 def end_of_line while !end_of_line? forward_char end @point end
end_of_line?()
click to toggle source
# File lib/textbringer/buffer.rb, line 759 def end_of_line? end_of_buffer? || byte_after == "\n" end
exchange_point_and_mark(mark = @mark)
click to toggle source
# File lib/textbringer/buffer.rb, line 789 def exchange_point_and_mark(mark = @mark) if mark.nil? raise EditorError, "The mark is not set" end update_line_and_column(@point, mark.location) @point, mark.location = mark.location, @point end
expand_tab(s)
click to toggle source
# File lib/textbringer/buffer.rb, line 193 def expand_tab(s) # TODO: Support multibyte characters tw = self[:tab_width] fmt = "A#{tw}" s.b.gsub(/([^\t]{#{tw}})|([^\t]*)\t/n) { [$+].pack(fmt) }.force_encoding(Encoding::UTF_8) end
file_encoding=(enc)
click to toggle source
# File lib/textbringer/buffer.rb, line 269 def file_encoding=(enc) @file_encoding = Encoding.find(enc) @binary = enc == Encoding::ASCII_8BIT end
file_format=(format)
click to toggle source
# File lib/textbringer/buffer.rb, line 278 def file_format=(format) case format when /\Aunix\z/i @file_format = :unix when /\Ados\z/i @file_format = :dos when /\Amac\z/i @file_format = :mac else raise ArgumentError, "Unknown file format: #{format}" end end
file_modified?()
click to toggle source
# File lib/textbringer/buffer.rb, line 435 def file_modified? !@file_mtime.nil? && File.mtime(@file_name) != @file_mtime end
file_name=(file_name)
click to toggle source
# File lib/textbringer/buffer.rb, line 261 def file_name=(file_name) @file_name = file_name basename = File.basename(file_name) if /\A#{Regexp.quote(basename)}(<\d+>)?\z/ !~ name self.name = basename end end
filter_event(event)
click to toggle source
# File lib/textbringer/buffer.rb, line 1403 def filter_event(event) if @input_method @input_method.filter_event(event) else event end end
forward_char(n = 1)
click to toggle source
# File lib/textbringer/buffer.rb, line 641 def forward_char(n = 1) pos = get_pos(@point, n) update_line_and_column(@point, pos) @point = pos @goal_column = nil end
forward_line(n = 1)
click to toggle source
# File lib/textbringer/buffer.rb, line 679 def forward_line(n = 1) if n > 0 n.times do end_of_line break if end_of_buffer? forward_char end elsif n < 0 (-n).times do beginning_of_line break if beginning_of_buffer? backward_char beginning_of_line end end end
forward_word(n = 1, regexp: /\p{Letter}|\p{Number}/)
click to toggle source
# File lib/textbringer/buffer.rb, line 652 def forward_word(n = 1, regexp: /\p{Letter}|\p{Number}/) n.times do while !end_of_buffer? && regexp !~ char_after forward_char end while !end_of_buffer? && regexp =~ char_after forward_char end end end
gap_filled_with_nul?()
click to toggle source
# File lib/textbringer/buffer.rb, line 1250 def gap_filled_with_nul? @contents[@gap_start...@gap_end]&.match?(/\A\0*\z/) end
get_line_and_column(pos)
click to toggle source
# File lib/textbringer/buffer.rb, line 508 def get_line_and_column(pos) line = 1 + @contents[0...user_to_gap(pos)].count("\n") if pos == point_min column = 1 else i = @contents.rindex("\n", user_to_gap(pos - 1)) if i i += 1 else i = 0 end column = 1 + substring(gap_to_user(i), pos).size end [line, column] end
goto_char(pos)
click to toggle source
# File lib/textbringer/buffer.rb, line 524 def goto_char(pos) if pos < 0 || pos > size raise RangeError, "Out of buffer" end if !@binary && byte_after(pos)&.match?(/[\x80-\xbf]/n) raise ArgumentError, "Position is in the middle of a character" end @goal_column = nil if @save_point_level == 0 @current_line, @current_column = get_line_and_column(pos) end @point = pos end
goto_line(n)
click to toggle source
# File lib/textbringer/buffer.rb, line 538 def goto_line(n) pos = point_min i = 1 while i < n && pos < @contents.bytesize pos = @contents.index("\n", pos) break if pos.nil? i += 1 pos += 1 end @point = gap_to_user(pos) @current_line = i @current_column = 1 @goal_column = nil end
gsub(*args, &block)
click to toggle source
# File lib/textbringer/buffer.rb, line 1353 def gsub(*args, &block) if block s = to_s.gsub(*args) { |*params| block.binding.eval('->(backref) { $~ = backref }').call($~) block.call(*params) } else s = to_s.gsub(*args) end composite_edit do delete_region(point_min, point_max) insert(s) end self end
indent_to(column)
click to toggle source
# File lib/textbringer/buffer.rb, line 1280 def indent_to(column) s = if self[:indent_tabs_mode] "\t" * (column / self[:tab_width]) + " " * (column % self[:tab_width]) else " " * column end insert(s) end
input_method_status()
click to toggle source
# File lib/textbringer/buffer.rb, line 1411 def input_method_status if @input_method&.enabled? @input_method.status else "--" end end
insert(x, merge_undo = false)
click to toggle source
# File lib/textbringer/buffer.rb, line 553 def insert(x, merge_undo = false) s = x.to_s check_read_only_flag pos = @point size = s.bytesize adjust_gap(size) @contents[@point, size] = s.b @marks.each do |m| if m.location > @point m.location += size end end @point = @gap_start += size update_line_and_column(pos, @point) unless @undoing if merge_undo && @undo_stack.last.is_a?(InsertAction) @undo_stack.last.merge(s) @redo_stack.clear else push_undo(InsertAction.new(self, pos, s)) end end self.modified = true @goal_column = nil self end
insert_final_newline()
click to toggle source
# File lib/textbringer/buffer.rb, line 1381 def insert_final_newline save_excursion do end_of_buffer if char_before != "\n" insert("\n") end end end
insert_for_yank(s)
click to toggle source
# File lib/textbringer/buffer.rb, line 1009 def insert_for_yank(s) if @mark.nil? || !point_at_mark?(@mark) push_mark end insert(s) end
inspect()
click to toggle source
# File lib/textbringer/buffer.rb, line 247 def inspect "#<Buffer:#{@name || '0x%x' % object_id}>" end
kill()
click to toggle source
# File lib/textbringer/buffer.rb, line 311 def kill @@table.delete(@name) @@list.delete(self) if @@current == self @@current = nil end @marks.each do |mark| mark.detach end fire_callbacks(:killed) end
kill_line(append = false)
click to toggle source
# File lib/textbringer/buffer.rb, line 981 def kill_line(append = false) save_point do |saved| if end_of_buffer? raise RangeError, "End of buffer" end if char_after == ?\n forward_char else end_of_line end pos = @point point_to_mark(saved) kill_region(@point, pos, append) end end
kill_region(s = @point, e = mark, append = false)
click to toggle source
# File lib/textbringer/buffer.rb, line 928 def kill_region(s = @point, e = mark, append = false) copy_region(s, e, append) delete_region(s, e) end
kill_word(append = false)
click to toggle source
# File lib/textbringer/buffer.rb, line 997 def kill_word(append = false) save_point do |saved| if end_of_buffer? raise RangeError, "End of buffer" end forward_word pos = @point point_to_mark(saved) kill_region(@point, pos, append) end end
looking_at?(re)
click to toggle source
# File lib/textbringer/buffer.rb, line 1119 def looking_at?(re) if re.is_a?(Regexp) r = /\G#{re}/ else r = "\\G(?:#{re})" end byteindex(true, r, @point) == @point end
mark()
click to toggle source
# File lib/textbringer/buffer.rb, line 831 def mark if @mark.nil? raise EditorError, "The mark is not set" end @mark.location end
mark_to_point(mark)
click to toggle source
# File lib/textbringer/buffer.rb, line 773 def mark_to_point(mark) mark.location = @point end
match_beginning(n)
click to toggle source
# File lib/textbringer/buffer.rb, line 1175 def match_beginning(n) @match_offsets[n]&.first end
match_end(n)
click to toggle source
# File lib/textbringer/buffer.rb, line 1179 def match_end(n) @match_offsets[n]&.last end
match_string(n)
click to toggle source
# File lib/textbringer/buffer.rb, line 1183 def match_string(n) b, e = @match_offsets[n] if b.nil? nil else substring(b, e) end end
modified=(modified)
click to toggle source
# File lib/textbringer/buffer.rb, line 336 def modified=(modified) @modified = modified if @composite_edit_level == 0 && modified fire_callbacks(:modified) end end
modified?()
click to toggle source
# File lib/textbringer/buffer.rb, line 343 def modified? @modified end
name=(name)
click to toggle source
# File lib/textbringer/buffer.rb, line 251 def name=(name) if @@table[@name] == self @@table.delete(@name) @name = Buffer.new_buffer_name(name) @@table[@name] = self else @name = name end end
new_file?()
click to toggle source
# File lib/textbringer/buffer.rb, line 363 def new_file? @new_file end
new_mark(location = @point)
click to toggle source
# File lib/textbringer/buffer.rb, line 763 def new_mark(location = @point) Mark.new(self, location).tap { |m| @marks << m } end
newline()
click to toggle source
# File lib/textbringer/buffer.rb, line 580 def newline indentation = save_point { |saved| if char_after&.match?(/[ \t]/) next "" end beginning_of_line s = @point while char_after&.match?(/[ \t]/) forward_char end str = substring(s, @point) if end_of_buffer? || char_after == "\n" delete_region(s, @point) end str } insert("\n" + indentation) end
next_line(n = 1)
click to toggle source
# File lib/textbringer/buffer.rb, line 700 def next_line(n = 1) column = get_goal_column n.times do end_of_line forward_char adjust_column(column) end @goal_column = column end
on(name, &callback)
click to toggle source
# File lib/textbringer/buffer.rb, line 331 def on(name, &callback) @callbacks[name] ||= [] @callbacks[name].push(callback) end
on_global_mark_ring?()
click to toggle source
# File lib/textbringer/buffer.rb, line 861 def on_global_mark_ring? mark_ring = Buffer.global_mark_ring if mark_ring.empty? return false end current = mark_ring.current if current&.buffer == self return true end next_mark = mark_ring[-1] if next_mark&.buffer == self return true end false end
on_killed(&callback)
click to toggle source
# File lib/textbringer/buffer.rb, line 323 def on_killed(&callback) add_callback(:killed, callback) end
on_modified(&callback)
click to toggle source
# File lib/textbringer/buffer.rb, line 347 def on_modified(&callback) add_callback(:modified, callback) end
point_after_mark?(mark)
click to toggle source
# File lib/textbringer/buffer.rb, line 785 def point_after_mark?(mark) @point > mark.location end
point_at_mark?(mark)
click to toggle source
# File lib/textbringer/buffer.rb, line 777 def point_at_mark?(mark) @point == mark.location end
point_before_mark?(mark)
click to toggle source
# File lib/textbringer/buffer.rb, line 781 def point_before_mark?(mark) @point < mark.location end
point_max()
click to toggle source
# File lib/textbringer/buffer.rb, line 504 def point_max bytesize end
point_min()
click to toggle source
# File lib/textbringer/buffer.rb, line 500 def point_min 0 end
point_to_mark(mark)
click to toggle source
# File lib/textbringer/buffer.rb, line 769 def point_to_mark(mark) goto_char(mark.location) end
pop_mark()
click to toggle source
# File lib/textbringer/buffer.rb, line 888 def pop_mark return if @mark_ring.empty? @mark = @mark_ring.rotate(1) end
pop_to_mark()
click to toggle source
# File lib/textbringer/buffer.rb, line 893 def pop_to_mark goto_char(mark) pop_mark end
previous_line(n = 1)
click to toggle source
# File lib/textbringer/buffer.rb, line 710 def previous_line(n = 1) column = get_goal_column n.times do beginning_of_line backward_char beginning_of_line adjust_column(column) end @goal_column = column end
push_global_mark(pos = @point, force: false)
click to toggle source
# File lib/textbringer/buffer.rb, line 877 def push_global_mark(pos = @point, force: false) if force || !on_global_mark_ring? mark = new_mark mark.location = pos Buffer.global_mark_ring.push(mark) true else false end end
push_mark(pos = @point)
click to toggle source
Set mark at pos, and push the mark on the mark ring. Unlike Emacs, the new mark is pushed on the mark ring instead of the old one.
# File lib/textbringer/buffer.rb, line 849 def push_mark(pos = @point) @mark = new_mark @mark.location = pos @mark_ring.push(@mark) if self != Buffer.minibuffer global_mark_ring = Buffer.global_mark_ring if global_mark_ring.empty? || global_mark_ring.current.buffer != self push_global_mark(pos) end end end
re_search_backward(s, raise_error: true, count: 1)
click to toggle source
# File lib/textbringer/buffer.rb, line 1089 def re_search_backward(s, raise_error: true, count: 1) if count < 0 return re_search_forward(s, raise_error: raise_error, count: -count) end re = new_regexp(s) pos = @point count.times do p = pos begin i = byteindex(false, re, p) if i.nil? if raise_error raise SearchError, "Search failed" else return nil end end p = get_pos(p, -1) rescue RangeError if raise_error raise SearchError, "Search failed" else return nil end end while match_end(0) > pos pos = match_beginning(0) end goto_char(pos) end
re_search_forward(s, raise_error: true, count: 1)
click to toggle source
# File lib/textbringer/buffer.rb, line 1069 def re_search_forward(s, raise_error: true, count: 1) if count < 0 return re_search_backward(s, raise_error: raise_error, count: -count) end re = new_regexp(s) pos = @point count.times do i = byteindex(true, re, pos) if i.nil? if raise_error raise SearchError, "Search failed" else return nil end end pos = match_end(0) end goto_char(pos) end
read_only=(value)
click to toggle source
# File lib/textbringer/buffer.rb, line 295 def read_only=(value) @read_only = value if @read_only @modified = false end end
read_only?()
click to toggle source
# File lib/textbringer/buffer.rb, line 291 def read_only? @read_only end
read_only_edit() { || ... }
click to toggle source
# File lib/textbringer/buffer.rb, line 302 def read_only_edit self.read_only = false begin yield ensure self.read_only = true end end
redo()
click to toggle source
# File lib/textbringer/buffer.rb, line 1047 def redo check_read_only_flag if @redo_stack.empty? raise EditorError, "No further redo information" end action = @redo_stack.pop @undoing = true begin was_modified = @modified action.redo if action.version == @version @modified = false action.version = nil elsif !was_modified action.version = @version end @undo_stack.push(action) ensure @undoing = false end end
replace(str, start: point_min, end: point_max)
click to toggle source
# File lib/textbringer/buffer.rb, line 958 def replace(str, start: point_min, end: point_max) composite_edit do delete_region(start, binding.local_variable_get(:end)) goto_char(start) insert(str) end end
replace_match(str)
click to toggle source
# File lib/textbringer/buffer.rb, line 1192 def replace_match(str) new_str = str.gsub(/\\(?:([0-9]+)|(&)|(\\))/) { |s| case when $1 match_string($1.to_i) when $2 match_string(0) when $3 "\\" end } b = match_beginning(0) e = match_end(0) goto_char(b) composite_edit do delete_region(b, e) insert(new_str) end end
replace_regexp_forward(regexp, to_str)
click to toggle source
# File lib/textbringer/buffer.rb, line 1212 def replace_regexp_forward(regexp, to_str) result = 0 rest = substring(point, point_max) composite_edit do delete_region(point, point_max) new_str = rest.gsub(new_regexp(regexp)) { result += 1 m = Regexp.last_match to_str.gsub(/\\(?:([0-9]+)|(&)|(\\))/) { |s| case when $1 m[$1.to_i] when $2 m.to_s when $3 "\\" end } } insert(new_str) end result end
revert(enc = nil)
click to toggle source
# File lib/textbringer/buffer.rb, line 375 def revert(enc = nil) if @file_name.nil? raise EditorError, "Buffer has no file name" end clear s, mtime = File.open(@file_name, external_encoding: Encoding::ASCII_8BIT, binmode: true) { |f| f.flock(File::LOCK_SH) [f.read, f.mtime] } enc ||= @@detect_encoding_proc.call(s) || Encoding::ASCII_8BIT s.force_encoding(enc) unless s.valid_encoding? enc = Encoding::ASCII_8BIT s.force_encoding(enc) end set_contents(s, enc) @file_mtime = mtime @modified = false end
save(file_name = @file_name)
click to toggle source
# File lib/textbringer/buffer.rb, line 397 def save(file_name = @file_name) if file_name.nil? raise EditorError, "File name is not set" end file_name = File.expand_path(file_name) config = EditorConfig.load_file(file_name) if config["trim_trailing_whitespace"] trim_trailing_whitespace end if config["insert_final_newline"] insert_final_newline end begin File.open(file_name, "w", external_encoding: @file_encoding, binmode: true) do |f| f.flock(File::LOCK_EX) write_to_file(f) f.flush f.fsync end @file_mtime = File.mtime(file_name) rescue Errno::EISDIR if @name file_name = File.expand_path(@name, file_name) retry else raise end end if file_name != @file_name self.file_name = file_name end @version += 1 @modified = false @new_file = false @read_only = false end
save_excursion() { || ... }
click to toggle source
Don't save Buffer.current
.
# File lib/textbringer/buffer.rb, line 814 def save_excursion old_point = new_mark old_mark = @mark&.dup old_column = @goal_column begin yield ensure point_to_mark(old_point) old_point.delete if old_mark @mark.location = old_mark.location old_mark.delete end @goal_column = old_column end end
save_point() { |saved| ... }
click to toggle source
The buffer should not be modified in the given block because current_line/current_column is not updated in save_point.
# File lib/textbringer/buffer.rb, line 799 def save_point saved = new_mark column = @goal_column @save_point_level += 1 begin yield(saved) ensure point_to_mark(saved) saved.delete @goal_column = column @save_point_level -= 1 end end
set_mark(pos = @point)
click to toggle source
# File lib/textbringer/buffer.rb, line 838 def set_mark(pos = @point) if @mark @mark.location = pos else push_mark(pos) end end
set_visible_mark(pos = @point)
click to toggle source
# File lib/textbringer/buffer.rb, line 898 def set_visible_mark(pos = @point) @visible_mark ||= new_mark @visible_mark.location = pos end
skip_re_backward(re)
click to toggle source
# File lib/textbringer/buffer.rb, line 1347 def skip_re_backward(re) while re =~ char_before backward_char end end
skip_re_forward(re)
click to toggle source
# File lib/textbringer/buffer.rb, line 1341 def skip_re_forward(re) while re =~ char_after forward_char end end
substring(s, e)
click to toggle source
# File lib/textbringer/buffer.rb, line 445 def substring(s, e) result = if s > @gap_start || e <= @gap_start @contents[user_to_gap(s)...user_to_gap(e)] else len = @gap_start - s @contents[user_to_gap(s), len] + @contents[@gap_end, e - s - len] end result.force_encoding(Encoding::UTF_8) unless @binary result end
to_s()
click to toggle source
# File lib/textbringer/buffer.rb, line 439 def to_s result = (@contents[0...@gap_start] + @contents[@gap_end..-1]) result.force_encoding(Encoding::UTF_8) unless @binary result end
toggle_input_method(name)
click to toggle source
# File lib/textbringer/buffer.rb, line 1390 def toggle_input_method(name) if name.nil? @input_method ||= InputMethod.find(CONFIG[:default_input_method]) else @input_method = InputMethod.find(name) end @input_method.toggle end
transpose_chars()
click to toggle source
# File lib/textbringer/buffer.rb, line 1236 def transpose_chars if end_of_line? backward_char end if beginning_of_buffer? raise RangeError, "Beginning of buffer" end backward_char c = char_after delete_char forward_char insert(c) end
trim_trailing_whitespace()
click to toggle source
# File lib/textbringer/buffer.rb, line 1370 def trim_trailing_whitespace save_excursion do beginning_of_buffer composite_edit do while re_search_forward(/[ \t]+$/, raise_error: false) replace_match("") end end end end
undo()
click to toggle source
# File lib/textbringer/buffer.rb, line 1025 def undo check_read_only_flag if @undo_stack.empty? raise EditorError, "No further undo information" end action = @undo_stack.pop @undoing = true begin was_modified = @modified action.undo if action.version == @version @modified = false action.version = nil elsif !was_modified action.version = @version end @redo_stack.push(action) ensure @undoing = false end end
yank()
click to toggle source
# File lib/textbringer/buffer.rb, line 1016 def yank insert_for_yank(KILL_RING.current) end
yank_pop()
click to toggle source
# File lib/textbringer/buffer.rb, line 1020 def yank_pop delete_region insert_for_yank(KILL_RING.rotate(1)) end
Private Instance Methods
adjust_column(column)
click to toggle source
# File lib/textbringer/buffer.rb, line 1563 def adjust_column(column) s = @point w = 0 while !end_of_line? && (w = display_width(substring(s, @point))) < column forward_char end if w > column backward_char end end
adjust_gap(min_size = 0, pos = @point)
click to toggle source
# File lib/textbringer/buffer.rb, line 1444 def adjust_gap(min_size = 0, pos = @point) if @gap_start < pos len = user_to_gap(pos) - @gap_end @contents[@gap_start, len] = @contents[@gap_end, len] @gap_start += len @gap_end += len elsif @gap_start > pos len = @gap_start - pos @contents[@gap_end - len, len] = @contents[pos, len] @gap_start -= len @gap_end -= len end # fill the gap with NUL to avoid invalid byte sequence in UTF-8 @contents[@gap_start...@gap_end] = "\0" * (@gap_end - @gap_start) if gap_size < min_size new_gap_size = GAP_SIZE + min_size extended_size = new_gap_size - gap_size @contents[@gap_end, 0] = "\0" * extended_size @gap_end += extended_size end end
check_read_only_flag()
click to toggle source
# File lib/textbringer/buffer.rb, line 1612 def check_read_only_flag if @read_only raise ReadOnlyError, "Buffer is read only: #{self.inspect}" end end
fire_callbacks(name)
click to toggle source
# File lib/textbringer/buffer.rb, line 1618 def fire_callbacks(name) @callbacks[name]&.each do |callback| callback.call(self) end end
gap_size()
click to toggle source
# File lib/textbringer/buffer.rb, line 1466 def gap_size @gap_end - @gap_start end
gap_to_user(gpos)
click to toggle source
# File lib/textbringer/buffer.rb, line 1478 def gap_to_user(gpos) if gpos <= @gap_start gpos elsif gpos >= @gap_end gpos - gap_size else raise RangeError, "Position is in gap" end end
get_goal_column()
click to toggle source
# File lib/textbringer/buffer.rb, line 1553 def get_goal_column if @goal_column @goal_column else prev_point = @point beginning_of_line display_width(substring(@point, prev_point)) end end
get_pos(pos, offset)
click to toggle source
# File lib/textbringer/buffer.rb, line 1488 def get_pos(pos, offset) if @binary result = pos + offset if result < 0 || result > bytesize raise RangeError, "Out of buffer" end return result end if offset >= 0 i = offset while i > 0 raise RangeError, "Out of buffer" if end_of_buffer? b = byte_after(pos) pos += UTF8_CHAR_LEN[b] raise RangeError, "Out of buffer" if pos > bytesize i -= 1 end else i = -offset while i > 0 pos -= 1 raise RangeError, "Out of buffer" if pos < 0 while /[\x80-\xbf]/n =~ byte_after(pos) pos -= 1 raise RangeError, "Out of buffer" if pos < 0 end i -= 1 end end pos end
new_regexp(s)
click to toggle source
# File lib/textbringer/buffer.rb, line 1604 def new_regexp(s) if s.is_a?(Regexp) s else Regexp.new(s, self[:case_fold_search] ? Regexp::IGNORECASE : 0) end end
push_undo(action)
click to toggle source
# File lib/textbringer/buffer.rb, line 1588 def push_undo(action) return if @undoing || @undo_limit == 0 if !modified? action.version = @version end if @composite_edit_level > 0 @composite_edit_actions.push(action) else if @undo_stack.size >= @undo_limit @undo_stack[0, @undo_stack.size + 1 - @undo_limit] = [] end @undo_stack.push(action) @redo_stack.clear end end
set_contents(s, enc)
click to toggle source
# File lib/textbringer/buffer.rb, line 1421 def set_contents(s, enc) case s.encoding when Encoding::UTF_8, Encoding::ASCII_8BIT @contents = s.frozen? ? s.dup : s else @contents = s.encode(Encoding::UTF_8) end @contents.force_encoding(Encoding::ASCII_8BIT) self.file_encoding = enc case @contents when /(?<!\r)\n/ @file_format = :unix when /\r(?!\n)/ @file_format = :mac @contents.gsub!(/\r/, "\n") when /\r\n/ @file_format = :dos @contents.gsub!(/\r/, "") else @file_format = CONFIG[:default_file_format] end end
update_line_and_column(pos, new_pos)
click to toggle source
# File lib/textbringer/buffer.rb, line 1520 def update_line_and_column(pos, new_pos) return if @save_point_level > 0 if pos < new_pos n = @contents[user_to_gap(pos)...user_to_gap(new_pos)].count("\n") if n == 0 @current_column += substring(pos, new_pos).size else @current_line += n i = @contents.rindex("\n", user_to_gap(new_pos - 1)) if i i += 1 else i = 0 end @current_column = 1 + substring(gap_to_user(i), new_pos).size end elsif pos > new_pos n = @contents[user_to_gap(new_pos)...user_to_gap(pos)].count("\n") if n == 0 @current_column -= substring(new_pos, pos).size else @current_line -= n i = @contents.rindex("\n", user_to_gap(new_pos - 1)) if i i += 1 else i = 0 end @current_column = 1 + substring(gap_to_user(i), new_pos).size end end end
user_to_gap(pos)
click to toggle source
# File lib/textbringer/buffer.rb, line 1470 def user_to_gap(pos) if pos <= @gap_start pos else gap_size + pos end end
write_to_file(f)
click to toggle source
# File lib/textbringer/buffer.rb, line 1575 def write_to_file(f) [@contents[0...@gap_start], @contents[@gap_end..-1]].each do |s| s.force_encoding(Encoding::UTF_8) unless @binary case @file_format when :dos s.gsub!(/\n/, "\r\n") when :mac s.gsub!(/\n/, "\r") end f.write(s) end end