Merge branch 'master' into dev

This commit is contained in:
jgmdev 2021-05-31 19:52:08 -04:00
commit ae63255f37
13 changed files with 233 additions and 155 deletions

View File

@ -1,10 +1,14 @@
Lite XL is following closely [rxi/lite](https://github.com/rxi/lite) but with some enhancements.
This files document the changes done in Lite XL for each release.
### next release
### 1.16.11
When opening directories with too many files lite-xl now keep diplaying files and directories in the treeview.
The application remains functional and the directories can be explored without using too much memory.
In this operating mode the files of the project are not indexed so the command "Core: Find File" will act as the "Core: Open File" command.
The "Project Search: Find" will work by searching all the files present in the project directory even if they are not indexed.
Implemented changing fonts per syntax group by @liquidev.
[#126](https://github.com/franko/lite-xl/issues/126): Implemented changing fonts per syntax group.
Example user module snippet that makes all comments italic:
```lua
@ -15,6 +19,12 @@ local italic = renderer.font.load("italic.ttf", 14)
style.syntax_fonts["comment"] = italic
```
Improved indentation behavior by @adamharrison.
Fix bug with close button not working in borderless window mode.
Fix problem with normalization of filename for opened documents.
### 1.16.10
Improved syntax highlight system thanks to @liquidev and @adamharrison.

View File

@ -66,6 +66,9 @@ command.add(nil, {
end,
["core:find-file"] = function()
if core.project_files_limit then
return command.perform "core:open-file"
end
local files = {}
for dir, item in core.get_project_files() do
if item.type == "file" then

View File

@ -33,33 +33,6 @@ local function doc_multiline_selection(sort)
return line1, col1, line2, col2, swap
end
local function insert_at_start_of_selected_lines(text, skip_empty)
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
for line = line1, line2 do
local line_text = doc().lines[line]
if (not skip_empty or line_text:find("%S")) then
doc():insert(line, 1, text)
end
end
doc():set_selection(line1, col1 + #text, line2, col2 + #text, swap)
end
local function remove_from_start_of_selected_lines(text, skip_empty)
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
for line = line1, line2 do
local line_text = doc().lines[line]
if line_text:sub(1, #text) == text
and (not skip_empty or line_text:find("%S"))
then
doc():remove(line, 1, line, #text + 1)
end
end
doc():set_selection(line1, col1 - #text, line2, col2 - #text, swap)
end
local function append_line_if_last_line(line)
if line >= #doc().lines then
doc():insert(line, math.huge, "\n")
@ -107,7 +80,7 @@ end
-- and remove the appropriate amount of spaces (or a tab).
local function indent_text(unindent)
local text = get_indent_string()
local line1, col1, line2, col2, swap = doc():get_selection(true)
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
local _, se = doc().lines[line1]:find("^[ \t]+")
local in_beginning_whitespace = col1 == 1 or (se and col1 <= se + 1)
if unindent or doc():has_selection() or in_beginning_whitespace then
@ -286,19 +259,31 @@ local commands = {
["doc:toggle-line-comments"] = function()
local comment = doc().syntax.comment
if not comment then return end
local indentation = get_indent_string()
local comment_text = comment .. " "
local line1, _, line2 = doc():get_selection(true)
local line1, _, line2 = doc_multiline_selection(true)
local uncomment = true
local start_offset = math.huge
for line = line1, line2 do
local text = doc().lines[line]
if text:find("%S") and text:find(comment_text, 1, true) ~= 1 then
local s = text:find("%S")
local cs, ce = text:find(comment_text, s, true)
if s and cs ~= s then
uncomment = false
start_offset = math.min(start_offset, s)
end
end
if uncomment then
remove_from_start_of_selected_lines(comment_text, true)
else
insert_at_start_of_selected_lines(comment_text, true)
for line = line1, line2 do
local text = doc().lines[line]
local s = text:find("%S")
if uncomment then
local cs, ce = text:find(comment_text, s, true)
if ce then
doc():remove(line, cs, line, ce + 1)
end
elseif s then
doc():insert(line, start_offset, comment_text)
end
end
end,
@ -346,9 +331,10 @@ local commands = {
end,
["doc:save-as"] = function()
local last_doc = core.last_active_view and core.last_active_view.doc
if doc().filename then
core.command_view:set_text(doc().filename)
elseif core.last_active_view and core.last_active_view.doc then
elseif last_doc and last_doc.filename then
local dirname, filename = core.last_active_view.doc.abs_filename:match("(.*)[/\\](.+)$")
core.command_view:set_text(core.normalize_to_project_dir(dirname) .. PATHSEP)
end

View File

@ -3,7 +3,7 @@ local config = {}
config.project_scan_rate = 5
config.fps = 60
config.max_log_items = 80
config.message_timeout = 3
config.message_timeout = 5
config.mouse_wheel_scroll = 50 * SCALE
config.file_size_limit = 10
config.ignore_files = "^%."

View File

@ -68,6 +68,7 @@ function core.set_project_dir(new_dir, change_project_fn)
core.project_directories = {}
core.add_project_directory(new_dir)
core.project_files = {}
core.project_files_limit = false
core.reschedule_project_scan()
return true
end
@ -95,6 +96,57 @@ local function strip_trailing_slash(filename)
return filename
end
local function compare_file(a, b)
return a.filename < b.filename
end
-- "root" will by an absolute path without trailing '/'
-- "path" will be a path starting with '/' and without trailing '/'
-- or the empty string.
-- It will identifies a sub-path within "root.
-- The current path location will therefore always be: root .. path.
-- When recursing "root" will always be the same, only "path" will change.
-- Returns a list of file "items". In eash item the "filename" will be the
-- complete file path relative to "root" *without* the trailing '/'.
local function get_directory_files(root, path, t, recursive, begin_hook)
if begin_hook then begin_hook() end
local size_limit = config.file_size_limit * 10e5
local all = system.list_dir(root .. path) or {}
local dirs, files = {}, {}
local entries_count = 0
local max_entries = config.max_project_files
for _, file in ipairs(all) do
if not common.match_pattern(file, config.ignore_files) then
local file = path .. PATHSEP .. file
local info = system.get_file_info(root .. file)
if info and info.size < size_limit then
info.filename = strip_leading_path(file)
table.insert(info.type == "dir" and dirs or files, info)
entries_count = entries_count + 1
if recursive and entries_count > max_entries then return nil, entries_count end
end
end
end
table.sort(dirs, compare_file)
for _, f in ipairs(dirs) do
table.insert(t, f)
if recursive and entries_count <= max_entries then
local subdir_t, subdir_count = get_directory_files(root, PATHSEP .. f.filename, t, recursive)
entries_count = entries_count + subdir_count
f.scanned = true
end
end
table.sort(files, compare_file)
for _, f in ipairs(files) do
table.insert(t, f)
end
return t, entries_count
end
local function project_scan_thread()
local function diff_files(a, b)
if #a ~= #b then return true end
@ -106,71 +158,21 @@ local function project_scan_thread()
end
end
local function compare_file(a, b)
return a.filename < b.filename
end
-- "root" will by an absolute path without trailing '/'
-- "path" will be a path starting with '/' and without trailing '/'
-- or the empty string.
-- It will identifies a sub-path within "root.
-- The current path location will therefore always be: root .. path.
-- When recursing "root" will always be the same, only "path" will change.
-- Returns a list of file "items". In eash item the "filename" will be the
-- complete file path relative to "root" *without* the trailing '/'.
local function get_files(root, path, t)
coroutine.yield()
t = t or {}
local size_limit = config.file_size_limit * 10e5
local all = system.list_dir(root .. path) or {}
local dirs, files = {}, {}
local entries_count = 0
local max_entries = config.max_project_files
for _, file in ipairs(all) do
if not common.match_pattern(file, config.ignore_files) then
local file = path .. PATHSEP .. file
local info = system.get_file_info(root .. file)
if info and info.size < size_limit then
info.filename = strip_leading_path(file)
table.insert(info.type == "dir" and dirs or files, info)
entries_count = entries_count + 1
if entries_count > max_entries then break end
end
end
end
table.sort(dirs, compare_file)
for _, f in ipairs(dirs) do
table.insert(t, f)
if entries_count <= max_entries then
local subdir_t, subdir_count = get_files(root, PATHSEP .. f.filename, t)
entries_count = entries_count + subdir_count
end
end
table.sort(files, compare_file)
for _, f in ipairs(files) do
table.insert(t, f)
end
return t, entries_count
end
while true do
-- get project files and replace previous table if the new table is
-- different
for i = 1, #core.project_directories do
local i = 1
while not core.project_files_limit and i <= #core.project_directories do
local dir = core.project_directories[i]
local t, entries_count = get_files(dir.name, "")
local t, entries_count = get_directory_files(dir.name, "", {}, true)
if diff_files(dir.files, t) then
if entries_count > config.max_project_files then
core.project_files_limit = true
core.status_view:show_message("!", style.accent,
"Too many files in project directory: stopping reading at "..
config.max_project_files.." files according to config.max_project_files. "..
"Either tweak this variable, or ignore certain files/directories by "..
"using the config.ignore_files variable in your user plugin or "..
"project config.")
"Too many files in project directory: stopped reading at "..
config.max_project_files.." files. For more information see "..
"usage.md at github.com/franko/lite-xl."
)
end
dir.files = t
core.redraw = true
@ -178,6 +180,7 @@ local function project_scan_thread()
if dir.name == core.project_dir then
core.project_files = dir.files
end
i = i + 1
end
-- wait for next scan
@ -186,6 +189,46 @@ local function project_scan_thread()
end
function core.scan_project_folder(dirname, filename)
for _, dir in ipairs(core.project_directories) do
if dir.name == dirname then
for i, file in ipairs(dir.files) do
local file = dir.files[i]
if file.filename == filename then
if file.scanned then return end
local new_files = get_directory_files(dirname, PATHSEP .. filename, {})
for j, new_file in ipairs(new_files) do
table.insert(dir.files, i + j, new_file)
end
file.scanned = true
return
end
end
end
end
end
local function find_project_files_co(root, path)
local size_limit = config.file_size_limit * 10e5
local all = system.list_dir(root .. path) or {}
for _, file in ipairs(all) do
if not common.match_pattern(file, config.ignore_files) then
local file = path .. PATHSEP .. file
local info = system.get_file_info(root .. file)
if info and info.size < size_limit then
info.filename = strip_leading_path(file)
if info.type == "file" then
coroutine.yield(root, info)
else
find_project_files_co(root, PATHSEP .. info.filename)
end
end
end
end
end
local function project_files_iter(state)
local dir = core.project_directories[state.dir_index]
state.file_index = state.file_index + 1
@ -200,17 +243,27 @@ end
function core.get_project_files()
local state = { dir_index = 1, file_index = 0 }
return project_files_iter, state
if core.project_files_limit then
return coroutine.wrap(function()
for _, dir in ipairs(core.project_directories) do
find_project_files_co(dir.name, "")
end
end)
else
local state = { dir_index = 1, file_index = 0 }
return project_files_iter, state
end
end
function core.project_files_number()
local n = 0
for i = 1, #core.project_directories do
n = n + #core.project_directories[i].files
if not core.project_files_limit then
local n = 0
for i = 1, #core.project_directories do
n = n + #core.project_directories[i].files
end
return n
end
return n
end
@ -270,7 +323,7 @@ local style = require "core.style"
------------------------------- Fonts ----------------------------------------
-- customize fonts:
-- style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Medium.ttf", 13 * SCALE)
-- style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE)
-- style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE)
--
-- font names used by lite:
@ -353,6 +406,20 @@ local function whitespace_replacements()
end
local function reload_on_user_module_save()
-- auto-realod style when user's module is saved by overriding Doc:Save()
local doc_save = Doc.save
local user_filename = system.absolute_path(USERDIR .. PATHSEP .. "init.lua")
function Doc:save(filename)
doc_save(self)
if self.abs_filename == user_filename then
core.reload_module("core.style")
core.load_user_directory()
end
end
end
function core.init()
command = require "core.command"
keymap = require "core.keymap"
@ -374,7 +441,7 @@ function core.init()
local recent_projects, window_position, window_mode = load_session()
if window_mode == "normal" then
system.set_window_size(table.unpack(window_position))
else
elseif window_mode == "maximized" then
system.set_window_mode("maximized")
end
core.recent_projects = recent_projects
@ -495,6 +562,8 @@ function core.init()
if item.text == "Exit" then os.exit(1) end
end)
end
reload_on_user_module_save()
end
@ -554,13 +623,19 @@ do
end
-- DEPRECATED function
core.doc_save_hooks = {}
function core.add_save_hook(fn)
core.error("The function core.add_save_hook is deprecated." ..
" Modules should now directly override the Doc:save function.")
core.doc_save_hooks[#core.doc_save_hooks + 1] = fn
end
-- DEPRECATED function
function core.on_doc_save(filename)
-- for backward compatibility in modules. Hooks are deprecated, the function Doc:save
-- should be directly overidded.
for _, hook in ipairs(core.doc_save_hooks) do
hook(filename)
end
@ -1042,15 +1117,4 @@ function core.on_error(err)
end
core.add_save_hook(function(filename)
local doc = core.active_view.doc
local user_filename = system.absolute_path(USERDIR .. PATHSEP .. "init.lua")
if doc and doc:is(Doc) and doc.abs_filename == user_filename then
core.reload_module("core.style")
core.load_user_directory()
end
end)
return core

View File

@ -272,6 +272,7 @@ end
function Node:get_scroll_button_index(px, py)
if #self.views == 1 then return end
for i = 1, 2 do
local x, y, w, h = self:get_scroll_button_rect(i)
if px >= x and px < x + w and py >= y and py < y + h then

View File

@ -1,6 +1,5 @@
-- this file is used by lite-xl to setup the Lua environment
-- when starting
VERSION = "1.16.10"
-- this file is used by lite-xl to setup the Lua environment when starting
VERSION = "1.16.11"
MOD_VERSION = "1"
SCALE = tonumber(os.getenv("LITE_SCALE")) or SCALE

View File

@ -21,8 +21,8 @@ style.tab_width = common.round(170 * SCALE)
--
-- On High DPI monitor or non RGB monitor you may consider using antialiasing grayscale instead.
-- The antialiasing grayscale with full hinting is interesting for crisp font rendering.
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Medium.ttf", 13 * SCALE)
style.big_font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Medium.ttf", 40 * SCALE)
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE)
style.big_font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 40 * SCALE)
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 14 * SCALE, {antialiasing="grayscale", hinting="full"})
style.icon_big_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 20 * SCALE, {antialiasing="grayscale", hinting="full"})
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE)

Binary file not shown.

View File

@ -76,7 +76,7 @@ local function get_non_empty_lines(syntax, lines)
end
local auto_detect_max_lines = 200
local auto_detect_max_lines = 100
local function detect_indent_stat(doc)
local stat = {}
@ -99,29 +99,11 @@ local function detect_indent_stat(doc)
end
local doc_on_text_change = Doc.on_text_change
local adjust_threshold = 4
local current_on_text_change = nil
local function update_cache(doc)
local type, size, score = detect_indent_stat(doc)
cache[doc] = { type = type, size = size, confirmed = (score >= adjust_threshold) }
local score_threshold = 4
cache[doc] = { type = type, size = size, confirmed = (score >= score_threshold) }
doc.indent_info = cache[doc]
if score < adjust_threshold and doc_on_text_change then
current_on_text_change = function(self, ...)
update_cache(self)
end
elseif score >= adjust_threshold and doc_on_text_change then
current_on_text_change = nil
end
end
function Doc.on_text_change(...)
if current_on_text_change then
current_on_text_change(...)
end
doc_on_text_change(...)
end
@ -129,6 +111,14 @@ local new = Doc.new
function Doc:new(...)
new(self, ...)
update_cache(self)
if not cache[self].confirmed then
core.add_thread(function ()
while not cache[self].confirmed do
update_cache(self)
coroutine.yield(1)
end
end, self)
end
end
local clean = Doc.clean

View File

@ -170,12 +170,17 @@ function ResultsView:draw()
local ox, oy = self:get_content_offset()
local x, y = ox + style.padding.x, oy + style.padding.y
local files_number = core.project_files_number()
local per = self.last_file_idx / files_number
local per = files_number and self.last_file_idx / files_number or 1
local text
if self.searching then
text = string.format("Searching %d%% (%d of %d files, %d matches) for %q...",
per * 100, self.last_file_idx, files_number,
#self.results, self.query)
if files_number then
text = string.format("Searching %d%% (%d of %d files, %d matches) for %q...",
per * 100, self.last_file_idx, files_number,
#self.results, self.query)
else
text = string.format("Searching (%d files, %d matches) for %q...",
self.last_file_idx, #self.results, self.query)
end
else
text = string.format("Found %d matches for %q",
#self.results, self.query)

View File

@ -93,6 +93,13 @@ function TreeView:get_item_height()
end
function TreeView:invalidate_cache(dirname)
for _, v in pairs(self.cache[dirname]) do
v.skip = nil
end
end
function TreeView:check_cache()
-- invalidate cache's skip values if project_files has changed
for i = 1, #core.project_directories do
@ -102,9 +109,7 @@ function TreeView:check_cache()
self.last[dir.name] = dir.files
else
if dir.files ~= last_files then
for _, v in pairs(self.cache[dir.name]) do
v.skip = nil
end
self:invalidate_cache(dir.name)
self.last[dir.name] = dir.files
end
end
@ -208,17 +213,25 @@ function TreeView:on_mouse_pressed(button, x, y, clicks)
if caught then
return
end
if not self.hovered_item then
local hovered_item = self.hovered_item
if not hovered_item then
return
elseif self.hovered_item.type == "dir" then
elseif hovered_item.type == "dir" then
if keymap.modkeys["ctrl"] and button == "left" then
create_directory_in(self.hovered_item)
create_directory_in(hovered_item)
else
self.hovered_item.expanded = not self.hovered_item.expanded
if core.project_files_limit and not hovered_item.expanded then
local filename, abs_filename = hovered_item.filename, hovered_item.abs_filename
local index = string.find(abs_filename, filename, 1, true)
local dirname = string.sub(abs_filename, 1, index - 2)
core.scan_project_folder(dirname, filename)
self:invalidate_cache(dirname)
end
hovered_item.expanded = not hovered_item.expanded
end
else
core.try(function()
local doc_filename = common.relative_path(core.project_dir, self.hovered_item.abs_filename)
local doc_filename = common.relative_path(core.project_dir, hovered_item.abs_filename)
core.root_view:open_doc(core.open_doc(doc_filename))
end)
end

View File

@ -71,6 +71,13 @@ The project module can be edited by running the `core:open-project-module`
command — if the module does not exist for the current project when the
command is run it will be created.
## Big directories
Often projects contain compiled, bundled or downloaded files which you don't want to edit. These files can be excluded from projects by configuring `config.ignore_files`. Such a configuration might look like `config.ignore_files = { "^%.", "node_modules" }`. This will exclude the `node_modules` folder and any file starting with `.`. You can add this to a user or project module.
If a project has more files than the maximum (configured with `config.max_project_files`) lite-xl will switch to a different mode where files are lazily loaded.
_Note: Because of lazy loading `core:find-file` will open `core:open-file` instead._
## Add directories to a project
In addition to the project directories it is possible to add other directories