diff options
| author | Jackson Moore <jacksonmoore@tuta.io> | 2026-09-04 18:44:32 +0200 |
|---|---|---|
| committer | Jackson Moore <jacksonmoore@tuta.io> | 2026-09-04 18:44:32 +0200 |
| commit | c7b14ffb6d14969c9c41864e827f33f8e80fc24e (patch) | |
| tree | c7c0ac23dc93a4a8b75449d2be4d0e05c4f05f9e /lua/clever_f/text_topology.lua | |
| parent | 9013636a57144e1f57c9339e7888d588430aae6a (diff) | |
Rename plugin to clever-tee
Diffstat (limited to 'lua/clever_f/text_topology.lua')
| -rw-r--r-- | lua/clever_f/text_topology.lua | 1031 |
1 files changed, 0 insertions, 1031 deletions
diff --git a/lua/clever_f/text_topology.lua b/lua/clever_f/text_topology.lua deleted file mode 100644 index db4ea3b..0000000 --- a/lua/clever_f/text_topology.lua +++ /dev/null @@ -1,1031 +0,0 @@ -local domain = require("clever_f.domain") - -local M = {} -local TextView = {} -local MatchStartBounds = {} -M.TextView = TextView -M.MatchStartBounds = MatchStartBounds - -local view_records = setmetatable({}, { __mode = "k" }) -local bounds_records = setmetatable({}, { __mode = "k" }) - -local function fail(message, level) - error(message, (level or 1) + 1) -end - -local function is_integer(value) - return type(value) == "number" - and value > -math.huge - and value < math.huge - and value == math.floor(value) -end - -local function require_nonempty_string(value, name) - if type(value) ~= "string" or value == "" then - fail((name or "value") .. " must be a nonempty string", 2) - end - return value -end - -local function canonical_encoding(encoding) - encoding = require_nonempty_string(encoding, "effective encoding"):lower() - encoding = encoding:gsub("_", "-") - - local aliases = { - ["utf8"] = "utf-8", - ["cp-932"] = "cp932", - ["932"] = "cp932", - ["windows-31j"] = "cp932", - ["eucjp"] = "euc-jp", - ["ujis"] = "euc-jp", - ["unix-jis"] = "euc-jp", - } - return aliases[encoding] or encoding -end - -M.normalize_encoding = canonical_encoding - -local function utf8_character_length(text, offset) - local first = text:byte(offset) - if first == nil then - return nil - end - if first < 0x80 then - return 1 - end - - local length - local minimum - if first >= 0xc2 and first <= 0xdf then - length = 2 - minimum = 0x80 - elseif first >= 0xe0 and first <= 0xef then - length = 3 - minimum = 0x800 - elseif first >= 0xf0 and first <= 0xf4 then - length = 4 - minimum = 0x10000 - else - fail("text contains an invalid UTF-8 character", 3) - end - - if offset + length - 1 > #text then - fail("text contains an incomplete UTF-8 character", 3) - end - - local codepoint = first % (2 ^ (8 - length - 1)) - for index = offset + 1, offset + length - 1 do - local byte = text:byte(index) - if byte < 0x80 or byte > 0xbf then - fail("text contains an invalid UTF-8 character", 3) - end - codepoint = codepoint * 0x40 + (byte - 0x80) - end - - if codepoint < minimum - or codepoint > 0x10ffff - or (codepoint >= 0xd800 and codepoint <= 0xdfff) - then - fail("text contains an invalid UTF-8 character", 3) - end - return length -end - -local function split_utf8_codepoints(text) - local characters = {} - local offset = 1 - while offset <= #text do - local length = utf8_character_length(text, offset) - characters[#characters + 1] = text:sub(offset, offset + length - 1) - offset = offset + length - end - return characters -end - -local function nvim_split_segment(segment, result) - if segment == "" then - return - end - if segment:find("[\128-\255]") == nil then - for index = 1, #segment do - result[#result + 1] = segment:sub(index, index) - end - return - end - - local offset = 0 - while offset < #segment do - local match = vim.fn.matchstrpos(segment, "\\m.", offset) - local character = match[1] - local first = match[2] - local following = match[3] - if first ~= offset or following <= first or character == "" then - fail("Nvim could not index an editor character", 3) - end - result[#result + 1] = character - offset = following - end -end - -local function default_split_editor_characters(text) - local runtime = rawget(_G, "vim") - if type(runtime) ~= "table" - or type(runtime.fn) ~= "table" - or type(runtime.fn.strchars) ~= "function" - or type(runtime.fn.byteidx) ~= "function" - then - return split_utf8_codepoints(text) - end - - local result = {} - local offset = 1 - while offset <= #text do - local nul = text:find("\0", offset, true) - local last = nul and (nul - 1) or #text - nvim_split_segment(text:sub(offset, last), result) - if nul == nil then - break - end - result[#result + 1] = "\0" - offset = nul + 1 - end - return result -end - -local function default_encode(text, encoding) - if encoding == "utf-8" then - return text - end - - local runtime = rawget(_G, "vim") - if type(runtime) ~= "table" or type(runtime.iconv) ~= "function" then - fail("text encoding conversion requires Nvim or an encoder", 3) - end - - local ok, encoded = pcall(runtime.iconv, text, "utf-8", encoding) - if not ok or encoded == nil then - fail("text could not be converted to " .. encoding, 3) - end - return encoded -end - -local function require_character_list(characters) - if type(characters) ~= "table" then - fail("editor character splitter must return a list", 3) - end - - local result = {} - local item_count = 0 - for key, character in pairs(characters) do - if not is_integer(key) or key < 1 or key > #characters then - fail("editor character splitter must return a list", 3) - end - if type(character) ~= "string" or character == "" then - fail("editor character splitter must return nonempty strings", 3) - end - result[key] = character - item_count = item_count + 1 - end - if item_count ~= #characters then - fail("editor character splitter must return a list", 3) - end - return result -end - -function M.split_editor_characters(text) - if type(text) ~= "string" then - fail("text to split must be a string", 2) - end - return require_character_list(default_split_editor_characters(text)) -end - -local function snapshot_value(text) - if domain.TextSnapshot.is(text) then - return text - end - if type(text) == "table" and text.lines ~= nil then - text = text.lines - end - return domain.TextSnapshot.new(text) -end - -local function require_options(options) - if options == nil then - return {} - end - if type(options) == "function" then - return { encoder = options } - end - if type(options) ~= "table" then - fail("TextView options must be a table", 2) - end - return options -end - -local function selected_function(options, primary, alternate, fallback) - local value = options[primary] - if value == nil and alternate ~= nil then - value = options[alternate] - end - if value == nil then - return fallback - end - if type(value) ~= "function" then - fail("TextView " .. primary .. " must be a function", 3) - end - return value -end - -local function index_line(text, encoding, splitter, encoder) - if text:find("\n", 1, true) ~= nil then - fail("a text snapshot line must not contain a newline", 3) - end - - local characters = require_character_list(splitter(text)) - if table.concat(characters) ~= text then - fail("editor character splitter must preserve the complete line", 3) - end - - local entries = {} - local starts = {} - local by_start = {} - local encoded_parts = {} - local next_column = 1 - - for index, character in ipairs(characters) do - local encoded = encoder(character, encoding) - if type(encoded) ~= "string" or encoded == "" then - fail("TextView encoder must return a nonempty byte string", 3) - end - - local byte_length = #encoded - local entry = { - character = character, - encoded = encoded, - byte_start = next_column, - byte_end = next_column + byte_length - 1, - byte_length = byte_length, - } - entries[index] = entry - starts[index] = next_column - by_start[next_column] = index - encoded_parts[index] = encoded - next_column = next_column + byte_length - end - - return { - text = text, - encoded = table.concat(encoded_parts), - entries = entries, - starts = starts, - by_start = by_start, - byte_length = next_column - 1, - character_count = #entries, - } -end - -local text_view_metatable = { - __index = function(view, key) - local method = TextView[key] - if method ~= nil then - return method - end - - local record = view_records[view] - if key == "encoding" or key == "effective_encoding" then - return record.encoding - end - if key == "requested_encoding" then - return record.requested_encoding - end - if key == "line_count" then - return record.snapshot.line_count - end - return nil - end, - __newindex = function() - fail("TextView values are immutable", 2) - end, - __tostring = function(view) - local record = view_records[view] - return "text-view:" .. record.encoding .. ":" .. tostring(record.snapshot.line_count) - end, - __metatable = "clever_f.text_topology.TextView", -} - -function TextView.new(text, effective_encoding, options) - if TextView.is(text) and effective_encoding == nil and options == nil then - return text - end - - local snapshot = snapshot_value(text) - local requested_encoding = require_nonempty_string( - effective_encoding, - "effective encoding" - ) - local encoding = canonical_encoding(requested_encoding) - options = require_options(options) - local splitter = selected_function( - options, - "splitter", - "split_editor_characters", - default_split_editor_characters - ) - local encoder = selected_function(options, "encoder", "encode", default_encode) - - local view = setmetatable({}, text_view_metatable) - view_records[view] = { - snapshot = snapshot, - requested_encoding = requested_encoding, - encoding = encoding, - splitter = splitter, - encoder = encoder, - lines = {}, - } - return view -end - -function TextView.from_host(host, options) - if type(host) ~= "table" - or type(host.read_text) ~= "function" - or type(host.read_encoding) ~= "function" - then - fail("TextView host must provide read_text and read_encoding", 2) - end - local text = host:read_text() - local encoding = host:read_encoding() - return TextView.new(text, encoding, options) -end - -function TextView.is(value) - return type(value) == "table" and view_records[value] ~= nil -end - -local function view_record(view) - if not TextView.is(view) then - fail("value must be a TextView", 3) - end - return view_records[view] -end - -local function line_record(view, line_number) - local record = view_record(view) - if not is_integer(line_number) - or line_number < 1 - or line_number > record.snapshot.line_count - then - fail("line_number must identify a line in the TextView", 3) - end - local line = record.lines[line_number] - if line == nil then - line = index_line( - record.snapshot:line(line_number), - record.encoding, - record.splitter, - record.encoder - ) - record.lines[line_number] = line - end - return line -end - -local function require_character_index(line, character_index) - if not is_integer(character_index) - or character_index < 1 - or character_index > line.character_count - then - fail("character_index must identify an editor character", 3) - end - return character_index -end - -local function require_byte_column(byte_column) - if not is_integer(byte_column) or byte_column < 1 then - fail("byte_column must be a positive one-based integer", 3) - end - return byte_column -end - -local function position_arguments(position_or_line, byte_column, name) - if byte_column == nil then - local position = domain.Position.coerce(position_or_line) - return position.line, position.byte_column - end - if not is_integer(position_or_line) or position_or_line < 1 then - fail((name or "line_number") .. " must be a positive integer", 3) - end - return position_or_line, require_byte_column(byte_column) -end - -function TextView:text_snapshot() - return view_record(self).snapshot -end - -function TextView:line_text(line_number) - return line_record(self, line_number).text -end - -function TextView:line_encoded_text(line_number) - return line_record(self, line_number).encoded -end - -function TextView:text_suffix(position) - position = domain.Position.coerce(position) - local record = view_record(self) - local line = line_record(self, position.line) - local character_index = self:character_index_for_byte_column( - position.line, - position.byte_column - ) - local parts = {} - - for index = character_index, line.character_count do - parts[#parts + 1] = line.entries[index].character - end - for line_number = position.line + 1, record.snapshot.line_count do - parts[#parts + 1] = "\n" - parts[#parts + 1] = record.snapshot:line(line_number) - end - return table.concat(parts) -end - -function TextView:line_byte_length(line_number) - return line_record(self, line_number).byte_length -end - -function TextView:line_character_count(line_number) - return line_record(self, line_number).character_count -end - -function TextView:line_is_empty(line_number) - return self:line_character_count(line_number) == 0 -end - -function TextView:character_at_index(line_number, character_index) - local line = line_record(self, line_number) - require_character_index(line, character_index) - return line.entries[character_index].character -end - -function TextView:encoded_character_at_index(line_number, character_index) - local line = line_record(self, line_number) - require_character_index(line, character_index) - return line.entries[character_index].encoded -end - -function TextView:byte_column_for_character_index(line_number, character_index) - local line = line_record(self, line_number) - require_character_index(line, character_index) - return line.starts[character_index] -end - -function TextView:position_for_character_index(line_number, character_index) - return domain.Position.new( - line_number, - self:byte_column_for_character_index(line_number, character_index) - ) -end - -function TextView:try_character_index_for_byte_column(line_number, byte_column) - local line = line_record(self, line_number) - require_byte_column(byte_column) - return line.by_start[byte_column] -end - -function TextView:character_index_for_byte_column(line_number, byte_column) - local line = line_record(self, line_number) - require_byte_column(byte_column) - local character_index = line.by_start[byte_column] - if character_index == nil then - if byte_column <= line.byte_length then - fail("byte_column points inside an editor character", 2) - end - fail("byte_column does not identify an editor character", 2) - end - return character_index -end - -function TextView:character_index_for_position(position) - position = domain.Position.coerce(position) - return self:character_index_for_byte_column(position.line, position.byte_column) -end - -local function copy_span(line_number, character_index, entry) - local position = domain.Position.new(line_number, entry.byte_start) - return { - line = line_number, - character_index = character_index, - character = entry.character, - encoded = entry.encoded, - position = position, - byte_column = entry.byte_start, - byte_start = entry.byte_start, - byte_end = entry.byte_end, - start_byte_column = entry.byte_start, - end_byte_column = entry.byte_end, - byte_length = entry.byte_length, - } -end - -function TextView:byte_span_for_character_index(line_number, character_index) - local line = line_record(self, line_number) - require_character_index(line, character_index) - return copy_span(line_number, character_index, line.entries[character_index]) -end - -function TextView:byte_span_at(position_or_line, byte_column) - local line_number, column = position_arguments(position_or_line, byte_column) - local character_index = self:character_index_for_byte_column(line_number, column) - return self:byte_span_for_character_index(line_number, character_index) -end - -function TextView:character_at(position_or_line, byte_column) - local line_number, column = position_arguments(position_or_line, byte_column) - local character_index = self:character_index_for_byte_column(line_number, column) - return self:character_at_index(line_number, character_index) -end - -function TextView:is_character_start(position_or_line, byte_column) - local line_number, column = position_arguments(position_or_line, byte_column) - local record = view_record(self) - if line_number > record.snapshot.line_count then - return false - end - return line_record(self, line_number).by_start[column] ~= nil -end - -function TextView:is_valid_cursor_position(position_or_line, byte_column) - local line_number, column = position_arguments(position_or_line, byte_column) - local record = view_record(self) - if line_number > record.snapshot.line_count then - return false - end - local line = line_record(self, line_number) - if line.character_count == 0 then - return column == 1 - end - return line.by_start[column] ~= nil -end - -local function containing_character_index(line, byte_column) - for index = 1, line.character_count do - local entry = line.entries[index] - if byte_column >= entry.byte_start and byte_column <= entry.byte_end then - return index - end - end - return nil -end - -function TextView:normalize_endpoint(position_or_line, byte_column) - local line_number, column = position_arguments(position_or_line, byte_column) - local line = line_record(self, line_number) - if line.character_count == 0 then - return domain.Position.new(line_number, 1) - end - - if column > line.byte_length then - return self:position_for_character_index(line_number, line.character_count) - end - - local character_index = line.by_start[column] - or containing_character_index(line, column) - return self:position_for_character_index(line_number, character_index) -end - -local function first_cursor_position(view, line_number) - local line = line_record(view, line_number) - if line.character_count == 0 then - return domain.Position.new(line_number, 1) - end - return view:position_for_character_index(line_number, 1) -end - -local function last_cursor_position(view, line_number) - local line = line_record(view, line_number) - if line.character_count == 0 then - return domain.Position.new(line_number, 1) - end - return view:position_for_character_index(line_number, line.character_count) -end - -function TextView:first_cursor_position(line_number) - return first_cursor_position(self, line_number) -end - -function TextView:last_cursor_position(line_number) - return last_cursor_position(self, line_number) -end - -function TextView:predecessor(position) - position = domain.Position.coerce(position) - local line = line_record(self, position.line) - - if line.character_count > 0 then - local character_index = self:character_index_for_byte_column( - position.line, - position.byte_column - ) - if character_index > 1 then - return self:position_for_character_index(position.line, character_index - 1) - end - elseif position.byte_column ~= 1 then - fail("an empty line cursor position must use byte column one", 2) - end - - if position.line == 1 then - return nil - end - return last_cursor_position(self, position.line - 1) -end - -function TextView:successor(position) - position = domain.Position.coerce(position) - local record = view_record(self) - local line = line_record(self, position.line) - - if line.character_count > 0 then - local character_index = self:character_index_for_byte_column( - position.line, - position.byte_column - ) - if character_index < line.character_count then - return self:position_for_character_index(position.line, character_index + 1) - end - elseif position.byte_column ~= 1 then - fail("an empty line cursor position must use byte column one", 2) - end - - if position.line == record.snapshot.line_count then - return nil - end - return first_cursor_position(self, position.line + 1) -end - -local bounds_metatable = { - __index = function(bounds, key) - local method = MatchStartBounds[key] - if method ~= nil then - return method - end - return bounds_records[bounds][key] - end, - __newindex = function() - fail("MatchStartBounds values are immutable", 2) - end, - __tostring = function(bounds) - local record = bounds_records[bounds] - if record.empty then - return "match-start-bounds:empty" - end - return "match-start-bounds:" .. tostring(record.first) .. ":" .. tostring(record.last) - end, - __metatable = "clever_f.text_topology.MatchStartBounds", -} - -local function new_bounds(scope, first_line, last_line, first, last) - local bounds = setmetatable({}, bounds_metatable) - bounds_records[bounds] = { - scope = scope, - first_line = first_line, - last_line = last_line, - first = first, - last = last, - start = first, - finish = last, - empty = first == nil, - } - return bounds -end - -function MatchStartBounds.is(value) - return type(value) == "table" and bounds_records[value] ~= nil -end - -function MatchStartBounds:is_empty() - return bounds_records[self].empty -end - -function MatchStartBounds:contains(position) - position = domain.Position.coerce(position) - local record = bounds_records[self] - if record.empty then - return false - end - return domain.Position.compare(position, record.first) >= 0 - and domain.Position.compare(position, record.last) <= 0 -end - -function MatchStartBounds:to_table() - local record = bounds_records[self] - return { - scope = record.scope.value, - first_line = record.first_line, - last_line = record.last_line, - first = record.first and record.first:to_table() or nil, - last = record.last and record.last:to_table() or nil, - empty = record.empty, - } -end - -function TextView:line_match_start_bounds(line_number) - local line = line_record(self, line_number) - local first - local last - if line.character_count > 0 then - first = self:position_for_character_index(line_number, 1) - last = self:position_for_character_index(line_number, line.character_count) - end - return new_bounds( - domain.SearchScope.CURRENT_LINE, - line_number, - line_number, - first, - last - ) -end - -function TextView:buffer_match_start_bounds() - local record = view_record(self) - local first - local last - - for line_number = 1, record.snapshot.line_count do - local line = line_record(self, line_number) - if line.character_count > 0 then - first = self:position_for_character_index(line_number, 1) - break - end - end - - for line_number = record.snapshot.line_count, 1, -1 do - local line = line_record(self, line_number) - if line.character_count > 0 then - last = self:position_for_character_index(line_number, line.character_count) - break - end - end - - return new_bounds( - domain.SearchScope.BUFFER, - 1, - record.snapshot.line_count, - first, - last - ) -end - -local function scope_value(scope) - if scope == nil then - return domain.SearchScope.BUFFER - end - if scope == "line" then - return domain.SearchScope.CURRENT_LINE - end - return domain.SearchScope.from_string(scope) -end - -function TextView:match_start_bounds(scope, origin) - if domain.Position.is(scope) - or type(scope) == "number" - or (type(scope) == "table" and scope.line ~= nil) - then - scope, origin = origin, scope - end - - scope = scope_value(scope) - if scope == domain.SearchScope.BUFFER then - return self:buffer_match_start_bounds() - end - - if origin == nil then - fail("current-line match bounds require an origin line", 2) - end - local line_number = type(origin) == "number" - and origin - or domain.Position.coerce(origin).line - return self:line_match_start_bounds(line_number) -end - -local function empty_iterator() - return nil -end - -local function iteration_endpoint(view, position, name) - position = domain.Position.coerce(position) - if not view:is_character_start(position) then - fail((name or "iterator endpoint") .. " must start an editor character", 3) - end - return position -end - -local function step_character(view, position, direction) - local record = view_record(view) - local line = line_record(view, position.line) - local character_index = line.by_start[position.byte_column] - - if direction == domain.Direction.FORWARD then - if character_index < line.character_count then - return view:position_for_character_index(position.line, character_index + 1) - end - for line_number = position.line + 1, record.snapshot.line_count do - if line_record(view, line_number).character_count > 0 then - return view:position_for_character_index(line_number, 1) - end - end - return nil - end - - if character_index > 1 then - return view:position_for_character_index(position.line, character_index - 1) - end - for line_number = position.line - 1, 1, -1 do - local previous_line = line_record(view, line_number) - if previous_line.character_count > 0 then - return view:position_for_character_index( - line_number, - previous_line.character_count - ) - end - end - return nil -end - -local function position_iterator(view, direction, start_position, boundary) - if start_position == nil then - return empty_iterator - end - - start_position = iteration_endpoint(view, start_position, "iterator start") - boundary = iteration_endpoint(view, boundary, "iterator boundary") - local comparison = domain.Position.compare(start_position, boundary) - if direction == domain.Direction.FORWARD and comparison > 0 then - fail("a forward iterator start must not follow its boundary", 3) - end - if direction == domain.Direction.BACKWARD and comparison < 0 then - fail("a backward iterator start must not precede its boundary", 3) - end - - local current = start_position - local finished = false - return function() - if finished then - return nil - end - - local position = current - local character_index = view:character_index_for_position(position) - local character = view:character_at_index(position.line, character_index) - local span = view:byte_span_for_character_index(position.line, character_index) - - if position == boundary then - finished = true - else - current = step_character(view, position, direction) - if current == nil then - fail("iterator reached the text boundary before its selected boundary", 2) - end - end - return position, character, span - end -end - -local function iteration_arguments(view, direction, first, second) - if MatchStartBounds.is(first) then - local record = bounds_records[first] - if record.empty then - return nil, nil - end - if direction == domain.Direction.FORWARD then - return record.first, record.last - end - return record.last, record.first - end - - if type(first) == "number" and second == nil then - local bounds = view:line_match_start_bounds(first) - return iteration_arguments(view, direction, bounds) - end - - if first == nil then - local bounds = view:buffer_match_start_bounds() - return iteration_arguments(view, direction, bounds) - end - - first = domain.Position.coerce(first) - if second ~= nil then - return first, domain.Position.coerce(second) - end - - local bounds = view:buffer_match_start_bounds() - if bounds.empty then - return nil, nil - end - return first, direction == domain.Direction.FORWARD and bounds.last or bounds.first -end - -function TextView:iterate(direction, first, second) - direction = domain.Direction.from_string(direction) - local start_position, boundary = iteration_arguments(self, direction, first, second) - return position_iterator(self, direction, start_position, boundary) -end - -function TextView:iter_forward(first, boundary) - return self:iterate(domain.Direction.FORWARD, first, boundary) -end - -function TextView:iter_backward(first, boundary) - return self:iterate(domain.Direction.BACKWARD, first, boundary) -end - -function TextView:iter_line_forward(line_number) - return self:iter_forward(self:line_match_start_bounds(line_number)) -end - -function TextView:iter_line_backward(line_number) - return self:iter_backward(self:line_match_start_bounds(line_number)) -end - -function TextView:iter_buffer_forward() - return self:iter_forward(self:buffer_match_start_bounds()) -end - -function TextView:iter_buffer_backward() - return self:iter_backward(self:buffer_match_start_bounds()) -end - -local function strict_scope_bounds(view, origin, scope_or_bounds) - if MatchStartBounds.is(scope_or_bounds) then - return scope_or_bounds - end - local scope = scope_value(scope_or_bounds) - return view:match_start_bounds(scope, origin) -end - -function TextView:iter_strict(origin, direction, scope_or_bounds) - origin = domain.Position.coerce(origin) - line_record(self, origin.line) - if not self:is_valid_cursor_position(origin) then - fail("strict iterator origin must be a valid editor cursor position", 2) - end - - direction = domain.Direction.from_string(direction) - local bounds = strict_scope_bounds(self, origin, scope_or_bounds) - local candidates = self:iterate(direction, bounds) - - return function() - while true do - local position, character, span = candidates() - if position == nil then - return nil - end - local comparison = domain.Position.compare(position, origin) - if (direction == domain.Direction.FORWARD and comparison > 0) - or (direction == domain.Direction.BACKWARD and comparison < 0) - then - return position, character, span - end - end - end -end - -function TextView:iter_strict_forward(origin, scope_or_bounds) - return self:iter_strict(origin, domain.Direction.FORWARD, scope_or_bounds) -end - -function TextView:iter_strict_backward(origin, scope_or_bounds) - return self:iter_strict(origin, domain.Direction.BACKWARD, scope_or_bounds) -end - -TextView.character_index_to_byte_column = TextView.byte_column_for_character_index -TextView.byte_column_to_character_index = TextView.character_index_for_byte_column -TextView.character_count = TextView.line_character_count -TextView.byte_length = TextView.line_byte_length -TextView.text_from = TextView.text_suffix -TextView.suffix_from = TextView.text_suffix -TextView.character_span = TextView.byte_span_for_character_index -TextView.predecessor_endpoint = TextView.predecessor -TextView.successor_endpoint = TextView.successor -TextView.normalize_boundary_endpoint = TextView.normalize_endpoint -TextView.bounds_for_line = TextView.line_match_start_bounds -TextView.bounds_for_buffer = TextView.buffer_match_start_bounds -TextView.iterate_forward = TextView.iter_forward -TextView.iterate_backward = TextView.iter_backward -TextView.forward = TextView.iter_forward -TextView.backward = TextView.iter_backward -TextView.strict_forward = TextView.iter_strict_forward -TextView.strict_backward = TextView.iter_strict_backward - -function M.new(text, effective_encoding, options) - return TextView.new(text, effective_encoding, options) -end - -function M.from_host(host, options) - return TextView.from_host(host, options) -end - -M.build = M.new -M.build_from_host = M.from_host -M.is = TextView.is - -return M |
