diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index ed35913a..597e3de4 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -549,29 +549,29 @@ local commands = { local translations = { - ["previous-char"] = translate.previous_char, - ["next-char"] = translate.next_char, - ["previous-word-start"] = translate.previous_word_start, - ["next-word-end"] = translate.next_word_end, - ["previous-block-start"] = translate.previous_block_start, - ["next-block-end"] = translate.next_block_end, - ["start-of-doc"] = translate.start_of_doc, - ["end-of-doc"] = translate.end_of_doc, - ["start-of-line"] = translate.start_of_line, - ["end-of-line"] = translate.end_of_line, - ["start-of-word"] = translate.start_of_word, - ["start-of-indentation"] = translate.start_of_indentation, - ["end-of-word"] = translate.end_of_word, - ["previous-line"] = DocView.translate.previous_line, - ["next-line"] = DocView.translate.next_line, - ["previous-page"] = DocView.translate.previous_page, - ["next-page"] = DocView.translate.next_page, + ["previous-char"] = translate, + ["next-char"] = translate, + ["previous-word-start"] = translate, + ["next-word-end"] = translate, + ["previous-block-start"] = translate, + ["next-block-end"] = translate, + ["start-of-doc"] = translate, + ["end-of-doc"] = translate, + ["start-of-line"] = translate, + ["end-of-line"] = translate, + ["start-of-word"] = translate, + ["start-of-indentation"] = translate, + ["end-of-word"] = translate, + ["previous-line"] = DocView.translate, + ["next-line"] = DocView.translate, + ["previous-page"] = DocView.translate, + ["next-page"] = DocView.translate, } -for name, fn in pairs(translations) do - commands["doc:move-to-" .. name] = function() doc():move_to(fn, dv()) end - commands["doc:select-to-" .. name] = function() doc():select_to(fn, dv()) end - commands["doc:delete-to-" .. name] = function() doc():delete_to(fn, dv()) end +for name, obj in pairs(translations) do + commands["doc:move-to-" .. name] = function() doc():move_to(obj[name:gsub("-", "_")], dv()) end + commands["doc:select-to-" .. name] = function() doc():select_to(obj[name:gsub("-", "_")], dv()) end + commands["doc:delete-to-" .. name] = function() doc():delete_to(obj[name:gsub("-", "_")], dv()) end end commands["doc:move-to-previous-char"] = function() diff --git a/data/core/commandview.lua b/data/core/commandview.lua index b91f1394..4396313f 100644 --- a/data/core/commandview.lua +++ b/data/core/commandview.lua @@ -56,8 +56,8 @@ function CommandView:get_name() end -function CommandView:get_line_screen_position() - local x = CommandView.super.get_line_screen_position(self, 1) +function CommandView:get_line_screen_position(line, col) + local x = CommandView.super.get_line_screen_position(self, 1, col) local _, y = self:get_content_offset() local lh = self:get_line_height() return x, y + (self.size.y - lh) / 2 @@ -243,6 +243,7 @@ function CommandView:draw_line_gutter(idx, x, y) x = x + style.padding.x renderer.draw_text(self:get_font(), self.label, x, y + yoffset, color) core.pop_clip_rect() + return self:get_line_height() end diff --git a/data/core/docview.lua b/data/core/docview.lua index 397b455e..ac3c19d9 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -121,14 +121,18 @@ function DocView:get_gutter_width() end -function DocView:get_line_screen_position(idx) +function DocView:get_line_screen_position(line, col) local x, y = self:get_content_offset() local lh = self:get_line_height() local gw = self:get_gutter_width() - return x + gw, y + (idx-1) * lh + style.padding.y + y = y + (line-1) * lh + style.padding.y + if col then + return x + gw + self:get_col_x_offset(line, col), y + else + return x + gw, y + end end - function DocView:get_line_text_y_offset() local lh = self:get_line_height() local th = self:get_font():get_height() @@ -198,8 +202,9 @@ end function DocView:scroll_to_line(line, ignore_if_visible, instant) local min, max = self:get_visible_line_range() if not (ignore_if_visible and line > min and line < max) then - local lh = self:get_line_height() - self.scroll.to.y = math.max(0, lh * (line - 1) - self.size.y / 2) + local x, y = self:get_line_screen_position(line) + local ox, oy = self:get_content_offset() + self.scroll.to.y = math.max(0, y - oy - self.size.y / 2) if instant then self.scroll.y = self.scroll.to.y end @@ -208,10 +213,10 @@ end function DocView:scroll_to_make_visible(line, col) - local min = self:get_line_height() * (line - 1) - local max = self:get_line_height() * (line + 2) - self.size.y - self.scroll.to.y = math.min(self.scroll.to.y, min) - self.scroll.to.y = math.max(self.scroll.to.y, max) + local ox, oy = self:get_content_offset() + local _, ly = self:get_line_screen_position(line, col) + local lh = self:get_line_height() + self.scroll.to.y = common.clamp(self.scroll.to.y, ly - oy - self.size.y + lh * 2, ly - oy - lh) local gw = self:get_gutter_width() local xoffset = self:get_col_x_offset(line, col) local xmargin = 3 * self:get_font():get_width(' ') @@ -314,14 +319,15 @@ function DocView:draw_line_highlight(x, y) end -function DocView:draw_line_text(idx, x, y) +function DocView:draw_line_text(line, x, y) local default_font = self:get_font() local tx, ty = x, y + self:get_line_text_y_offset() - for _, type, text in self.doc.highlighter:each_token(idx) do + for _, type, text in self.doc.highlighter:each_token(line) do local color = style.syntax[type] local font = style.syntax_fonts[type] or default_font tx = renderer.draw_text(font, text, tx, ty, color) end + return self:get_line_height() end function DocView:draw_caret(x, y) @@ -329,7 +335,7 @@ function DocView:draw_caret(x, y) renderer.draw_rect(x, y, style.caret_width, lh, style.caret) end -function DocView:draw_line_body(idx, x, y) +function DocView:draw_line_body(line, x, y) -- draw highlight if any selection ends on this line local draw_highlight = false local hcl = config.highlight_current_line @@ -352,14 +358,14 @@ function DocView:draw_line_body(idx, x, y) end -- draw selection if it overlaps this line + local lh = self:get_line_height() for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do - if idx >= line1 and idx <= line2 then - local text = self.doc.lines[idx] - if line1 ~= idx then col1 = 1 end - if line2 ~= idx then col2 = #text + 1 end - local x1 = x + self:get_col_x_offset(idx, col1) - local x2 = x + self:get_col_x_offset(idx, col2) - local lh = self:get_line_height() + if line >= line1 and line <= line2 then + local text = self.doc.lines[line] + if line1 ~= line then col1 = 1 end + if line2 ~= line then col2 = #text + 1 end + local x1 = x + self:get_col_x_offset(line, col1) + local x2 = x + self:get_col_x_offset(line, col2) if x1 ~= x2 then renderer.draw_rect(x1, y, x2 - x1, lh, style.selection) end @@ -367,20 +373,22 @@ function DocView:draw_line_body(idx, x, y) end -- draw line's text - self:draw_line_text(idx, x, y) + return self:draw_line_text(line, x, y) end -function DocView:draw_line_gutter(idx, x, y, width) +function DocView:draw_line_gutter(line, x, y, width) local color = style.line_number for _, line1, _, line2 in self.doc:get_selections(true) do - if idx >= line1 and idx <= line2 then + if line >= line1 and line <= line2 then color = style.line_number2 break end end x = x + style.padding.x - common.draw_text(self:get_font(), color, idx, "right", x, y, width, self:get_line_height()) + local lh = self:get_line_height() + common.draw_text(self:get_font(), color, line, "right", x, y + yoffset, width, lh) + return lh end @@ -394,8 +402,7 @@ function DocView:draw_overlay() and system.window_has_focus() then if config.disable_blink or (core.blink_timer - core.blink_start) % T < T / 2 then - local x, y = self:get_line_screen_position(line) - self:draw_caret(x + self:get_col_x_offset(line, col), y) + self:draw_caret(self:get_line_screen_position(line, col)) end end end @@ -413,8 +420,7 @@ function DocView:draw() local x, y = self:get_line_screen_position(minline) local gw, gpad = self:get_gutter_width() for i = minline, maxline do - self:draw_line_gutter(i, self.position.x, y, gpad and gw - gpad or gw) - y = y + lh + y = y + (self:draw_line_gutter(i, self.position.x, y, gpad and gw - gpad or gw) or lh) end local pos = self.position @@ -423,8 +429,7 @@ function DocView:draw() -- right side it is redundant with the Node's clip. core.push_clip_rect(pos.x + gw, pos.y, self.size.x - gw, self.size.y) for i = minline, maxline do - self:draw_line_body(i, x, y) - y = y + lh + y = y + (self:draw_line_body(i, x, y) or lh) end self:draw_overlay() core.pop_clip_rect() diff --git a/data/plugins/linewrapping.lua b/data/plugins/linewrapping.lua new file mode 100644 index 00000000..aecc2bab --- /dev/null +++ b/data/plugins/linewrapping.lua @@ -0,0 +1,523 @@ +-- mod-version:2 -- lite-xl 2.0 +local core = require "core" +local common = require "core.common" +local DocView = require "core.docview" +local Doc = require "core.doc" +local style = require "core.style" +local config = require "core.config" +local command = require "core.command" +local keymap = require "core.keymap" +local translate = require "core.doc.translate" + +config.plugins.linewrapping = { + -- The type of wrapping to perform. Can be "letter" or "word". + mode = "letter", + -- If nil, uses the DocView's size, otherwise, uses this exact width. + width_override = nil, + -- Whether or not to draw a guide + guide = true, + -- Whether or not we should indent ourselves like the first line of a wrapped block. + indent = true, + -- Whether or not to enable wrapping by default when opening files. + enable_by_default = true +} + +local LineWrapping = {} + +-- Computes the breaks for a given line, width and mode. Returns a list of columns +-- at which the line should be broken. +function LineWrapping.compute_line_breaks(doc, default_font, line, width, mode) + local xoffset, last_i, i, last_space, last_width, begin_width = 0, 1, 1, 1, 0, 0 + local splits = { 1 } + for idx, type, text in doc.highlighter:each_token(line) do + local font = style.syntax_fonts[type] or default_font + if idx == 1 and config.plugins.linewrapping.indent then + local _, indent_end = text:find("^%s+") + if indent_end then begin_width = font:get_width(text:sub(1, indent_end)) end + end + local w = font:get_width(text) + if xoffset + w > width then + for char in common.utf8_chars(text) do + w = font:get_width(char) + xoffset = xoffset + w + if xoffset > width then + if mode == "word" then + table.insert(splits, last_space + 1) + xoffset = xoffset - last_width + w + begin_width + else + table.insert(splits, i) + xoffset = w + begin_width + end + elseif char == ' ' then + last_space = i + last_width = xoffset + end + i = i + #char + end + else + xoffset = xoffset + w + i = i + #text + end + end + return splits, begin_width +end + +-- breaks are held in a single table that contains n*2 elements, where n is the amount of line breaks. +-- each element represents line and column of the break. line_offset will check from the specified line +-- if the first line has not changed breaks, it will stop there. +function LineWrapping.reconstruct_breaks(docview, default_font, width, line_offset) + if width ~= math.huge then + local doc = docview.doc + -- two elements per wrapped line; first maps to original line number, second to column number. + docview.wrapped_lines = { } + -- one element per actual line; maps to the first index of in wrapped_lines for this line + docview.wrapped_line_to_idx = { } + -- one element per actual line; gives the indent width for the acutal line + docview.wrapped_line_offsets = { } + docview.wrapped_settings = { ["width"] = width, ["font"] = default_font } + for i = line_offset or 1, #doc.lines do + local breaks, offset = LineWrapping.compute_line_breaks(doc, default_font, i, width, config.plugins.linewrapping.mode) + table.insert(docview.wrapped_line_offsets, offset) + for k, col in ipairs(breaks) do + table.insert(docview.wrapped_lines, i) + table.insert(docview.wrapped_lines, col) + end + end + -- list of indices for wrapped_lines, that are based on original line number + -- holds the index to the first in the wrapped_lines list. + local last_wrap = nil + for i = 1, #docview.wrapped_lines, 2 do + if not last_wrap or last_wrap ~= docview.wrapped_lines[i] then + table.insert(docview.wrapped_line_to_idx, (i + 1) / 2) + last_wrap = docview.wrapped_lines[i] + end + end + else + docview.wrapped_lines = nil + docview.wrapped_line_to_idx = nil + docview.wrapped_line_offsets = nil + docview.wrapped_settings = nil + end +end + +-- When we have an insertion or deletion, we have four sections of text. +-- 1. The unaffected section, located prior to the cursor. This is completely ignored. +-- 2. The beginning of the affected line prior to the insertion or deletion. Begins on column 1 of the selection. +-- 3. The removed/pasted lines. +-- 4. Every line after the modification, begins one line after the selection in the initial document. +function LineWrapping.update_breaks(docview, old_line1, old_line2, net_lines) + -- Step 1: Determine the index for the line for #2. + local old_idx1 = docview.wrapped_line_to_idx[old_line1] or 1 + -- Step 2: Determine the index of the line for #4. + local old_idx2 = (docview.wrapped_line_to_idx[old_line2 + 1] or ((#docview.wrapped_lines / 2) + 1)) - 1 + -- Step 3: Remove all old breaks for the old lines from the table, and all old widths from wrapped_line_offsets. + local offset = (old_idx1 - 1) * 2 + 1 + for i = old_idx1, old_idx2 do + table.remove(docview.wrapped_lines, offset) + table.remove(docview.wrapped_lines, offset) + end + for i = old_line1, old_line2 do + table.remove(docview.wrapped_line_offsets, old_line1) + end + -- Step 4: Shift the line number of wrapped_lines past #4 by the amount of inserted/deleted lines. + if net_lines ~= 0 then + for i = offset, #docview.wrapped_lines, 2 do + docview.wrapped_lines[i] = docview.wrapped_lines[i] + net_lines + end + end + -- Step 5: Compute the breaks and offsets for the lines for #2 and #3. Insert them into the table. + local new_line1 = old_line1 + local new_line2 = old_line2 + net_lines + for line = new_line1, new_line2 do + local breaks, begin_width = LineWrapping.compute_line_breaks(docview.doc, docview.wrapped_settings.font, line, docview.wrapped_settings.width, config.plugins.linewrapping.mode) + table.insert(docview.wrapped_line_offsets, line, begin_width) + for i,b in ipairs(breaks) do + table.insert(docview.wrapped_lines, offset, b) + table.insert(docview.wrapped_lines, offset, line) + offset = offset + 2 + end + end + -- Step 6: Recompute the wrapped_line_to_idx cache from #2. + local line = old_line1 + offset = (old_idx1 - 1) * 2 + 1 + while offset < #docview.wrapped_lines do + if docview.wrapped_lines[offset + 1] == 1 then + docview.wrapped_line_to_idx[line] = ((offset - 1) / 2) + 1 + line = line + 1 + end + offset = offset + 2 + end + while line <= #docview.wrapped_line_to_idx do + table.remove(docview.wrapped_line_to_idx) + end +end + +-- Draws a guide if applicable to show where wrapping is occurring. +function LineWrapping.draw_guide(docview) + if config.plugins.linewrapping.guide and docview.wrapped_settings.width ~= math.huge then + local x, y = docview:get_content_offset() + local gw = docview:get_gutter_width() + renderer.draw_rect(x + gw + docview.wrapped_settings.width, y, 1, core.root_view.size.y, style.selection) + end +end + +function LineWrapping.update_docview_breaks(docview) + local x,y,w,h = docview:get_scrollbar_rect() + local width = config.plugins.linewrapping.width_override or (docview.size.x - docview:get_gutter_width() - w) + if (not docview.wrapped_settings or docview.wrapped_settings.width == nil or width ~= docview.wrapped_settings.width) then + docview.scroll.to.x = 0 + LineWrapping.reconstruct_breaks(docview, docview:get_font(), width) + end +end + +local function get_idx_line_col(docview, idx) + local doc = docview.doc + if not docview.wrapped_settings then + if idx > #doc.lines then return #doc.lines, #doc.lines[#doc.lines] + 1 end + return idx, 1 + end + if idx < 1 then return 1, 1 end + local offset = (idx - 1) * 2 + 1 + if offset > #docview.wrapped_lines then return #doc.lines, #doc.lines[#doc.lines] + 1 end + return docview.wrapped_lines[offset], docview.wrapped_lines[offset + 1] +end + +local function get_idx_line_length(docview, idx) + local doc = docview.doc + if not docview.wrapped_settings then + if idx > #doc.lines then return #doc.lines[#doc.lines] + 1 end + return #doc.lines[idx] + end + local offset = (idx - 1) * 2 + 1 + local start = docview.wrapped_lines[offset + 1] + if docview.wrapped_lines[offset + 2] and docview.wrapped_lines[offset + 2] == docview.wrapped_lines[offset] then + return docview.wrapped_lines[offset + 3] - docview.wrapped_lines[offset + 1] + else + return #doc.lines[docview.wrapped_lines[offset]] - docview.wrapped_lines[offset + 1] + 1 + end +end + +local function get_total_wrapped_lines(docview) + if not docview.wrapped_settings then return docview.doc and #docview.doc.lines end + return #docview.wrapped_lines / 2 +end + +-- If line end, gives the end of an index line, rather than the first character of the next line. +local function get_line_idx_col_count(docview, line, col, line_end, ndoc) + local doc = docview.doc + if not docview.wrapped_settings then return common.clamp(line, 1, #doc.lines), col, 1, 1 end + if line > #doc.lines then return get_line_idx_col_count(docview, #doc.lines, #doc.lines[#doc.lines] + 1) end + line = math.max(line, 1) + local idx = docview.wrapped_line_to_idx[line] or 1 + local ncol, scol = 1, 1 + if col then + local i = idx + 1 + while line == docview.wrapped_lines[(i - 1) * 2 + 1] and col >= docview.wrapped_lines[(i - 1) * 2 + 2] do + local nscol = docview.wrapped_lines[(i - 1) * 2 + 2] + if line_end and col == nscol then + break + end + scol = nscol + i = i + 1 + idx = idx + 1 + end + ncol = (col - scol) + 1 + end + local count = (docview.wrapped_line_to_idx[line + 1] or (get_total_wrapped_lines(docview) + 1)) - (docview.wrapped_line_to_idx[line] or get_total_wrapped_lines(docview)) + return idx, ncol, count, scol +end + +local function get_line_col_from_index_and_x(docview, idx, x) + local doc = docview.doc + local line, col = get_idx_line_col(docview, idx) + if idx < 1 then return 1, 1 end + local xoffset, last_i, i = (col ~= 1 and docview.wrapped_line_offsets[line] or 0), col, 1 + if x < xoffset then return line, col end + local default_font = docview:get_font() + for _, type, text in docview.doc.highlighter:each_token(line) do + local font, w = style.syntax_fonts[type] or default_font, 0 + for char in common.utf8_chars(text) do + if i >= col then + if xoffset >= x then + return line, (xoffset - x > (w / 2) and last_i or i) + end + w = font:get_width(char) + xoffset = xoffset + w + end + last_i = i + i = i + #char + end + end + return line, #doc.lines[line] +end + + +local open_files = {} + +local old_doc_insert = Doc.raw_insert +function Doc:raw_insert(line, col, text, undo_stack, time) + local old_lines = #self.lines + old_doc_insert(self, line, col, text, undo_stack, time) + if open_files[self] then + for i,docview in ipairs(open_files[self]) do + if docview.wrapped_settings then + local lines = #self.lines - old_lines + LineWrapping.update_breaks(docview, line, line, lines) + end + end + end +end + +local old_doc_remove = Doc.raw_remove +function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time) + local old_lines = #self.lines + old_doc_remove(self, line1, col1, line2, col2, undo_stack, time) + if open_files[self] then + for i,docview in ipairs(open_files[self]) do + if docview.wrapped_settings then + local lines = #self.lines - old_lines + LineWrapping.update_breaks(docview, line1, line2, lines) + end + end + end +end + +local old_doc_update = DocView.update +function DocView:update() + old_doc_update(self) + if self.wrapped_settings and self.size.x > 0 then + LineWrapping.update_docview_breaks(self) + end +end + +function DocView:get_scrollable_size() + if not config.scroll_past_end then + return self:get_line_height() * get_total_wrapped_lines(self) + style.padding.y * 2 + end + return self:get_line_height() * (get_total_wrapped_lines(self) - 1) + self.size.y +end + +local old_new = DocView.new +function DocView:new(doc) + old_new(self, doc) + if not open_files[doc] then open_files[doc] = {} end + table.insert(open_files[doc], self) + if config.plugins.linewrapping.enable_by_default then + LineWrapping.update_docview_breaks(self) + end +end + +local old_scroll_to_make_visible = DocView.scroll_to_make_visible +function DocView:scroll_to_make_visible(line, col) + old_scroll_to_make_visible(self, line, col) + if self.wrapped_settings then self.scroll.to.x = 0 end +end + +local old_get_visible_line_range = DocView.get_visible_line_range +function DocView:get_visible_line_range() + if not self.wrapped_settings then return old_get_visible_line_range(self) end + local x, y, x2, y2 = self:get_content_bounds() + local lh = self:get_line_height() + local minline = get_idx_line_col(self, math.max(1, math.floor(y / lh))) + local maxline = get_idx_line_col(self, math.min(get_total_wrapped_lines(self), math.floor(y2 / lh) + 1)) + return minline, maxline +end + +local old_get_x_offset_col = DocView.get_x_offset_col +function DocView:get_x_offset_col(line, x) + if not self.wrapped_settings then return old_get_x_offset_col(self, line, x) end + local idx = get_line_idx_col_count(self, line) + return get_line_col_from_index_and_x(self, idx, x) +end + +-- If line end is true, returns the end of the previous line, in a multi-line break. +local old_get_col_x_offset = DocView.get_col_x_offset +function DocView:get_col_x_offset(line, col, line_end) + if not self.wrapped_settings then return old_get_col_x_offset(self, line, col) end + local idx, ncol, count, scol = get_line_idx_col_count(self, line, col, line_end) + local xoffset, i = (scol ~= 1 and self.wrapped_line_offsets[line] or 0), 1 + local default_font = self:get_font() + for _, type, text in self.doc.highlighter:each_token(line) do + if i + #text >= scol then + if i < scol then + text = text:sub(scol - i + 1) + i = scol + end + local font = style.syntax_fonts[type] or default_font + for char in common.utf8_chars(text) do + if i >= col then + return xoffset + end + xoffset = xoffset + font:get_width(char) + i = i + #char + end + else + i = i + #text + end + end + return xoffset +end + +local old_get_line_screen_position = DocView.get_line_screen_position +function DocView:get_line_screen_position(line, col) + if not self.wrapped_settings then return old_get_line_screen_position(self, line, col) end + local idx, ncol, count = get_line_idx_col_count(self, line, col) + local x, y = self:get_content_offset() + local lh = self:get_line_height() + local gw = self:get_gutter_width() + return x + gw + (col and self:get_col_x_offset(line, col) or 0), y + (idx-1) * lh + style.padding.y +end + +local old_resolve_screen_position = DocView.resolve_screen_position +function DocView:resolve_screen_position(x, y) + if not self.wrapped_settings then return old_resolve_screen_position(self, x, y) end + local ox, oy = self:get_line_screen_position(1) + local idx = common.clamp(math.floor((y - oy) / self:get_line_height()) + 1, 1, get_total_wrapped_lines(self)) + return get_line_col_from_index_and_x(self, idx, x - ox) +end + +local old_draw_line_text = DocView.draw_line_text +function DocView:draw_line_text(line, x, y) + if not self.wrapped_settings then return old_draw_line_text(self, line, x, y) end + local default_font = self:get_font() + local tx, ty, begin_width = x, y + self:get_line_text_y_offset(), self.wrapped_line_offsets[line] + local lh = self:get_line_height() + local idx, _, count = get_line_idx_col_count(self, line) + local total_offset = 1 + for _, type, text in self.doc.highlighter:each_token(line) do + local color = style.syntax[type] + local font = style.syntax_fonts[type] or default_font + local token_offset = 1 + -- Split tokens if we're at the end of the document. + while text ~= nil and token_offset <= #text do + local next_line, next_line_start_col = get_idx_line_col(self, idx + 1) + if next_line ~= line then + next_line_start_col = #self.doc.lines[line] + end + local max_length = next_line_start_col - total_offset + local rendered_text = text:sub(token_offset, token_offset + max_length - 1) + tx = renderer.draw_text(font, rendered_text, tx, ty, color) + total_offset = total_offset + #rendered_text + if total_offset ~= next_line_start_col or max_length == 0 then break end + token_offset = token_offset + #rendered_text + idx = idx + 1 + tx, ty = x + begin_width, ty + lh + end + end + return lh * count +end + +local old_draw_line_body = DocView.draw_line_body +function DocView:draw_line_body(line, x, y) + if not self.wrapped_settings then return old_draw_line_body(self, line, x, y) end + local lh = self:get_line_height() + local idx0 = get_line_idx_col_count(self, line) + for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do + if line >= line1 and line <= line2 then + if line1 ~= line then col1 = 1 end + if line2 ~= line then col2 = #self.doc.lines[line] + 1 end + if col1 ~= col2 then + local idx1, ncol1 = get_line_idx_col_count(self, line, col1) + local idx2, ncol2 = get_line_idx_col_count(self, line, col2) + for i = idx1, idx2 do + local x1, x2 = x + (idx1 == i and self:get_col_x_offset(line1, col1) or 0) + if idx2 == i then + x2 = x + self:get_col_x_offset(line, col2) + else + x2 = x + self:get_col_x_offset(line, get_idx_line_length(self, i, line) + 1, true) + end + renderer.draw_rect(x1, y + (i - idx0) * lh, x2 - x1, lh, style.selection) + end + end + end + end + local draw_highlight = nil + for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do + -- draw line highlight if caret is on this line + if draw_highlight ~= false and config.highlight_current_line + and line1 == line and core.active_view == self then + draw_highlight = (line1 == line2 and col1 == col2) + end + end + if draw_highlight then + local _, _, count = get_line_idx_col_count(self, line) + for i=1,count do + self:draw_line_highlight(x + self.scroll.x, y + lh * (i - 1)) + end + end + -- draw line's text + return self:draw_line_text(line, x, y) +end + +local old_draw = DocView.draw +function DocView:draw() + old_draw(self) + if self.wrapped_settings then + LineWrapping.draw_guide(self) + end +end + +local old_draw_line_gutter = DocView.draw_line_gutter +function DocView:draw_line_gutter(line, x, y, width) + local lh = self:get_line_height() + local _, _, count = get_line_idx_col_count(self, line) + return (old_draw_line_gutter(self, line, x, y, width) or lh) * count +end + +local old_translate_end_of_line = translate.end_of_line +function translate.end_of_line(doc, line, col) + if not core.active_view or core.active_view.doc ~= doc or not core.active_view.wrapped_settings then old_translate_end_of_line(doc, line, col) end + local idx, ncol = get_line_idx_col_count(core.active_view, line, col) + local nline, ncol2 = get_idx_line_col(core.active_view, idx + 1) + if nline ~= line then return line, math.huge end + return line, ncol2 - 1 +end + +local old_translate_start_of_line = translate.start_of_line +function translate.start_of_line(doc, line, col) + if not core.active_view or core.active_view.doc ~= doc or not core.active_view.wrapped_settings then old_translate_start_of_line(doc, line, col) end + local idx, ncol = get_line_idx_col_count(core.active_view, line, col) + local nline, ncol2 = get_idx_line_col(core.active_view, idx - 1) + if nline ~= line then return line, 1 end + return line, ncol2 + 1 +end + +local old_previous_line = DocView.translate.previous_line +function DocView.translate.previous_line(doc, line, col, dv) + if not dv.wrapped_settings then return old_previous_line(doc, line, col, dv) end + local idx, ncol = get_line_idx_col_count(dv, line, col) + return get_line_col_from_index_and_x(dv, idx - 1, dv:get_col_x_offset(line, col)) +end + +local old_next_line = DocView.translate.next_line +function DocView.translate.next_line(doc, line, col, dv) + if not dv.wrapped_settings then return old_next_line(doc, line, col, dv) end + local idx, ncol = get_line_idx_col_count(dv, line, col) + return get_line_col_from_index_and_x(dv, idx + 1, dv:get_col_x_offset(line, col)) +end + +command.add(nil, { + ["line-wrapping:enable"] = function() + if core.active_view and core.active_view.doc then + LineWrapping.update_docview_breaks(core.active_view) + end + end, + ["line-wrapping:disable"] = function() + if core.active_view and core.active_view.doc then + LineWrapping.reconstruct_breaks(core.active_view, core.active_view:get_font(), math.huge) + end + end, + ["line-wrapping:toggle"] = function() + if core.active_view and core.active_view.doc and core.active_view.wrapped_settings then + command.perform("line-wrapping:disable") + else + command.perform("line-wrapping:enable") + end + end +}) + +keymap.add { + ["f10"] = "line-wrapping:toggle", +} + +return LineWrapping