aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam <adamdharrison@gmail.com>2022-03-24 12:01:46 -0400
committerAdam Harrison <adamdharrison@gmail.com>2022-04-03 16:44:02 -0400
commit3479890ce503005ad531f05377dff7036d6677ea (patch)
tree819c29ffdf146c6c78ac1f3e0ac686a31449b66c
parent048d250f5e0bc4b95d5769680b04bfeb7f440476 (diff)
downloadlite-xl-3479890ce503005ad531f05377dff7036d6677ea.tar.gz
lite-xl-3479890ce503005ad531f05377dff7036d6677ea.zip
Soft Line Wrapping (#636)
Added in soft line wrapping.
-rw-r--r--data/core/commands/doc.lua42
-rw-r--r--data/core/commandview.lua5
-rw-r--r--data/core/docview.lua63
-rw-r--r--data/plugins/linewrapping.lua523
4 files changed, 581 insertions, 52 deletions
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