Files
deb-lite-xl/data/core/docview.lua
T
Francesco Abbate 3b30bfa18b Improve left/right scrolling behavior for DocView
With the new behavior when moving right and triggering a scroll in the
right direction a subsequent movement left do not longer triggers a
scrolling to the left.
The scrolling to the left happens only when needed for the visibility of
the current position.

In other terms with the old behavior the scrolling was purely a function
of the position and was adjusted even when not strictly needed for the
visibility of the cursor.
Now the scrolling is no longer a pure function of the position but it
has a "memory" behavior. The scrolling will be adjusted only if needed
to accommodate the position of the cursor.

Reduce also the width of the margin when the scrolling will be
triggered and calculate as a function of the font character width.

With the modification now Lite behaves like the other editors. In
addition the selection of text with the mouse is much more easy when
a scrolling of the line is needed.
2020-12-12 20:26:27 +01:00

390 lines
10 KiB
Lua

local core = require "core"
local common = require "core.common"
local config = require "core.config"
local style = require "core.style"
local keymap = require "core.keymap"
local translate = require "core.doc.translate"
local View = require "core.view"
local DocView = View:extend()
local function move_to_line_offset(dv, line, col, offset)
local xo = dv.last_x_offset
if xo.line ~= line or xo.col ~= col then
xo.offset = dv:get_col_x_offset(line, col)
end
xo.line = line + offset
xo.col = dv:get_x_offset_col(line + offset, xo.offset)
return xo.line, xo.col
end
DocView.translate = {
["previous_page"] = function(doc, line, col, dv)
local min, max = dv:get_visible_line_range()
return line - (max - min), 1
end,
["next_page"] = function(doc, line, col, dv)
local min, max = dv:get_visible_line_range()
return line + (max - min), 1
end,
["previous_line"] = function(doc, line, col, dv)
if line == 1 then
return 1, 1
end
return move_to_line_offset(dv, line, col, -1)
end,
["next_line"] = function(doc, line, col, dv)
if line == #doc.lines then
return #doc.lines, math.huge
end
return move_to_line_offset(dv, line, col, 1)
end,
}
local blink_period = 0.8
function DocView:new(doc)
DocView.super.new(self)
self.cursor = "ibeam"
self.scrollable = true
self.doc = assert(doc)
self.font = "code_font"
self.last_x_offset = {}
self.blink_timer = 0
end
function DocView:try_close(do_close)
if self.doc:is_dirty()
and #core.get_views_referencing_doc(self.doc) == 1 then
core.command_view:enter("Unsaved Changes; Confirm Close", function(_, item)
if item.text:match("^[cC]") then
do_close()
elseif item.text:match("^[sS]") then
self.doc:save()
do_close()
end
end, function(text)
local items = {}
if not text:find("^[^cC]") then table.insert(items, "Close Without Saving") end
if not text:find("^[^sS]") then table.insert(items, "Save And Close") end
return items
end)
else
do_close()
end
end
function DocView:get_name()
local post = self.doc:is_dirty() and "*" or ""
local name = self.doc:get_name()
return name:match("[^/%\\]*$") .. post
end
function DocView:get_scrollable_size()
return self:get_line_height() * (#self.doc.lines - 1) + self.size.y
end
function DocView:get_font()
return style[self.font]
end
function DocView:get_line_height()
return math.floor(self:get_font():get_height() * config.line_height)
end
function DocView:get_gutter_width()
return self:get_font():get_width(#self.doc.lines) + style.padding.x * 2
end
function DocView:get_line_screen_position(idx)
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
end
function DocView:get_line_text_y_offset()
local lh = self:get_line_height()
local th = self:get_font():get_height()
return (lh - th) / 2
end
function DocView:get_visible_line_range()
local x, y, x2, y2 = self:get_content_bounds()
local lh = self:get_line_height()
local minline = math.max(1, math.floor(y / lh))
local maxline = math.min(#self.doc.lines, math.floor(y2 / lh) + 1)
return minline, maxline
end
function DocView:get_col_x_offset(line, col)
local text = self.doc.lines[line]
if not text then return 0 end
return self:get_font():get_width(text:sub(1, col - 1))
end
function DocView:get_x_offset_col(line, x)
local text = self.doc.lines[line]
local xoffset, last_i, i = 0, 1, 1
for char in common.utf8_chars(text) do
local w = self:get_font():get_width(char)
if xoffset >= x then
return (xoffset - x > w / 2) and last_i or i
end
xoffset = xoffset + w
last_i = i
i = i + #char
end
return #text
end
function DocView:resolve_screen_position(x, y)
local ox, oy = self:get_line_screen_position(1)
local line = math.floor((y - oy) / self:get_line_height()) + 1
line = common.clamp(line, 1, #self.doc.lines)
local col = self:get_x_offset_col(line, x - ox)
return line, col
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)
if instant then
self.scroll.y = self.scroll.to.y
end
end
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 gw = self:get_gutter_width()
local xoffset = self:get_col_x_offset(line, col)
local xmargin = 3 * self:get_font():get_width(' ')
local xsup = xoffset + gw + xmargin
local xinf = xoffset - xmargin
if xsup > self.scroll.x + self.size.x then
self.scroll.to.x = xsup - self.size.x
elseif xinf < self.scroll.x then
self.scroll.to.x = math.max(0, xinf)
end
end
local function mouse_selection(doc, clicks, line1, col1, line2, col2)
local swap = line2 < line1 or line2 == line1 and col2 <= col1
if swap then
line1, col1, line2, col2 = line2, col2, line1, col1
end
if clicks == 2 then
line1, col1 = translate.start_of_word(doc, line1, col1)
line2, col2 = translate.end_of_word(doc, line2, col2)
elseif clicks == 3 then
if line2 == #doc.lines and doc.lines[#doc.lines] ~= "\n" then
doc:insert(math.huge, math.huge, "\n")
end
line1, col1, line2, col2 = line1, 1, line2 + 1, 1
end
if swap then
return line2, col2, line1, col1
end
return line1, col1, line2, col2
end
function DocView:on_mouse_pressed(button, x, y, clicks)
local caught = DocView.super.on_mouse_pressed(self, button, x, y, clicks)
if caught then
return
end
if keymap.modkeys["shift"] then
if clicks == 1 then
local line1, col1 = select(3, self.doc:get_selection())
local line2, col2 = self:resolve_screen_position(x, y)
self.doc:set_selection(line2, col2, line1, col1)
end
else
local line, col = self:resolve_screen_position(x, y)
self.doc:set_selection(mouse_selection(self.doc, clicks, line, col, line, col))
self.mouse_selecting = { line, col, clicks = clicks }
end
self.blink_timer = 0
end
function DocView:on_mouse_moved(x, y, ...)
DocView.super.on_mouse_moved(self, x, y, ...)
if self:scrollbar_overlaps_point(x, y) or self.dragging_scrollbar then
self.cursor = "arrow"
else
self.cursor = "ibeam"
end
if self.mouse_selecting then
local l1, c1 = self:resolve_screen_position(x, y)
local l2, c2 = table.unpack(self.mouse_selecting)
local clicks = self.mouse_selecting.clicks
self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2))
end
end
function DocView:on_mouse_released(button)
DocView.super.on_mouse_released(self, button)
self.mouse_selecting = nil
end
function DocView:on_text_input(text)
self.doc:text_input(text)
end
function DocView:update()
-- scroll to make caret visible and reset blink timer if it moved
local line, col = self.doc:get_selection()
if (line ~= self.last_line or col ~= self.last_col) and self.size.x > 0 then
if core.active_view == self then
self:scroll_to_make_visible(line, col)
end
self.blink_timer = 0
self.last_line, self.last_col = line, col
end
-- update blink timer
if self == core.active_view and not self.mouse_selecting then
local n = blink_period / 2
local prev = self.blink_timer
self.blink_timer = (self.blink_timer + 1 / config.fps) % blink_period
if (self.blink_timer > n) ~= (prev > n) then
core.redraw = true
end
end
DocView.super.update(self)
end
function DocView:draw_line_highlight(x, y)
local lh = self:get_line_height()
renderer.draw_rect(x, y, self.size.x, lh, style.line_highlight)
end
function DocView:draw_line_text(idx, x, y)
local tx, ty = x, y + self:get_line_text_y_offset()
local font = self:get_font()
for _, type, text in self.doc.highlighter:each_token(idx) do
local color = style.syntax[type]
tx = renderer.draw_text(font, text, tx, ty, color)
end
end
function DocView:draw_line_body(idx, x, y)
local line, col = self.doc:get_selection()
-- draw selection if it overlaps this line
local line1, col1, line2, col2 = self.doc:get_selection(true)
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()
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
end
-- draw line highlight if caret is on this line
if config.highlight_current_line and not self.doc:has_selection()
and line == idx and core.active_view == self then
self:draw_line_highlight(x + self.scroll.x, y)
end
-- draw line's text
self:draw_line_text(idx, x, y)
-- draw caret if it overlaps this line
if line == idx and core.active_view == self
and self.blink_timer < blink_period / 2
and system.window_has_focus() then
local lh = self:get_line_height()
local x1 = x + self:get_col_x_offset(line, col)
renderer.draw_rect(x1, y, style.caret_width, lh, style.caret)
end
end
function DocView:draw_line_gutter(idx, x, y)
local color = style.line_number
local line1, _, line2, _ = self.doc:get_selection(true)
if idx >= line1 and idx <= line2 then
color = style.line_number2
end
local yoffset = self:get_line_text_y_offset()
x = x + style.padding.x
renderer.draw_text(self:get_font(), idx, x, y + yoffset, color)
end
function DocView:draw()
self:draw_background(style.background)
local font = self:get_font()
font:set_tab_width(font:get_width(" ") * config.indent_size)
local minline, maxline = self:get_visible_line_range()
local lh = self:get_line_height()
local _, y = self:get_line_screen_position(minline)
local x = self.position.x
for i = minline, maxline do
self:draw_line_gutter(i, x, y)
y = y + lh
end
local x, y = self:get_line_screen_position(minline)
local gw = self:get_gutter_width()
local pos = self.position
core.push_clip_rect(pos.x + gw, pos.y, self.size.x, self.size.y)
for i = minline, maxline do
self:draw_line_body(i, x, y)
y = y + lh
end
core.pop_clip_rect()
self:draw_scrollbar()
end
return DocView