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
size()
Alias for: bytesize
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