Compare commits

...

6 Commits

Author SHA1 Message Date
Francesco Abbate 719839d299 Add a limit for very slow filesystems
When adding a directory in a project we check if the filesystem is too
slow. If it is too slow we act as if the projects was files-limited by
the number of files but we show a specific warning.

This solution is not perfect but for very low filesystem it can limit
the problem. Otherwise the application would be totally irresponsive.
2021-10-21 11:07:37 +02:00
Francesco Abbate 1794765f07 Fix problem with treeview keeping the editor busy
Fix a problem introduced when fixing the dirty pixel problem, commit
cb08c5c. The node, when determining the layout was rounding the size
of the fixed-size view. In turns this latter was calling move_towards
to the default_size it wanted. If default_size was non-integer the
value vas never archieved because it was rounded during layout and
move_towars was keeping the editor busy by setting the
core.need_redraw flag.
2021-10-14 09:13:06 +02:00
Francesco Abbate bed103b7a6 Fix error introduced with 43fc35d7 2021-10-12 22:19:24 +02:00
Francesco Abbate 80db3cb027 Fix problem with treeview x resizing
The x size of the treeview plugin cannot really change expect if explicitly
resized.

The call to move_towards for x seems to raise a state where core.redraw is
always set to true and this prevent the application to go idle.

It is seen after the introduction of the dmon directory monitoring but it
is not clear why it wasn't seen before.
2021-10-12 18:06:00 +02:00
Francesco Abbate a0f4ac93e1 Do not use normalize_path when not needed 2021-10-12 16:08:06 +02:00
Francesco Abbate 43fc35d7dc First attempt to treat correctly network volumes
On windows paths belonging to network volumes will be gives like:

\\address\share-name\path

Now the code recognize these paths and treat them correctly.
2021-10-12 14:28:28 +02:00
3 changed files with 85 additions and 29 deletions

View File

@ -280,24 +280,61 @@ local function split_on_slash(s, sep_pattern)
end
function common.normalize_path(filename)
-- The filename argument given to the function is supposed to
-- come from system.absolute_path and as such should be an
-- absolute path without . or .. elements.
-- This function exists because on Windows the drive letter returned
-- by system.absolute_path is sometimes with a lower case and sometimes
-- with an upper case to we normalize to upper case.
function common.normalize_volume(filename)
if not filename then return end
if PATHSEP == '\\' then
local drive, rem = filename:match('^([a-zA-Z]:\\)(.*)')
if drive then
return drive:upper() .. rem
end
end
return filename
end
function common.normalize_path(filename)
if not filename then return end
local volume
if PATHSEP == '\\' then
filename = filename:gsub('[/\\]', '\\')
local drive, rem = filename:match('^([a-zA-Z])(:.*)')
filename = drive and drive:upper() .. rem or filename
local drive, rem = filename:match('^([a-zA-Z]:\\)(.*)')
if drive then
volume, filename = drive:upper(), rem
else
drive, rem = filename:match('^(\\\\[^\\]+\\[^\\]+\\)(.*)')
if drive then
volume, filename = drive, rem
end
end
else
local relpath = filename:match('^/(.+)')
if relpath then
volume, filename = "/", relpath
end
end
local parts = split_on_slash(filename, PATHSEP)
local accu = {}
for _, part in ipairs(parts) do
if part == '..' and #accu > 0 and accu[#accu] ~= ".." then
table.remove(accu)
if part == '..' then
if #accu > 0 and accu[#accu] ~= ".." then
table.remove(accu)
elseif volume then
error("invalid path " .. volume .. filename)
else
table.insert(accu, part)
end
elseif part ~= '.' then
table.insert(accu, part)
end
end
local npath = table.concat(accu, PATHSEP)
return npath == "" and PATHSEP or npath
return (volume or "") .. (npath == "" and PATHSEP or npath)
end

View File

@ -36,7 +36,7 @@ end
local function update_recents_project(action, dir_path_abs)
local dirname = common.normalize_path(dir_path_abs)
local dirname = common.normalize_volume(dir_path_abs)
if not dirname then return end
local recents = core.recent_projects
local n = #recents
@ -56,7 +56,7 @@ function core.set_project_dir(new_dir, change_project_fn)
local chdir_ok = pcall(system.chdir, new_dir)
if chdir_ok then
if change_project_fn then change_project_fn() end
core.project_dir = common.normalize_path(new_dir)
core.project_dir = common.normalize_volume(new_dir)
core.project_directories = {}
core.add_project_directory(new_dir)
return true
@ -106,6 +106,15 @@ local function get_project_file_info(root, file)
end
-- Predicate function to inhibit directory recursion in get_directory_files
-- based on a time limit and the number of files.
local function timed_max_files_pred(dir, filename, entries_count, t_elapsed)
local n_limit = entries_count <= config.max_project_files
local t_limit = t_elapsed < 20 / config.fps
return n_limit and t_limit and core.project_subdir_is_shown(dir, filename)
end
-- "root" will by an absolute path without trailing '/'
-- "path" will be a path starting with '/' and without trailing '/'
-- or the empty string.
@ -114,12 +123,13 @@ end
-- 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(dir, root, path, t, begin_hook, max_files)
local function get_directory_files(dir, root, path, t, entries_count, recurse_pred, begin_hook)
if begin_hook then begin_hook() end
local t0 = system.get_time()
local all = system.list_dir(root .. path) or {}
local t_elapsed = system.get_time() - t0
local dirs, files = {}, {}
local entries_count = 0
for _, file in ipairs(all) do
local info = get_project_file_info(root, path .. PATHSEP .. file)
if info then
@ -128,13 +138,16 @@ local function get_directory_files(dir, root, path, t, begin_hook, max_files)
end
end
local recurse_complete = true
table.sort(dirs, compare_file)
for _, f in ipairs(dirs) do
table.insert(t, f)
if (not max_files or entries_count <= max_files) and core.project_subdir_is_shown(dir, f.filename) then
local sub_limit = max_files and max_files - entries_count
local _, n = get_directory_files(dir, root, PATHSEP .. f.filename, t, begin_hook, sub_limit)
entries_count = entries_count + n
if recurse_pred(dir, f.filename, entries_count, t_elapsed) then
local _, complete, n = get_directory_files(dir, root, PATHSEP .. f.filename, t, entries_count, recurse_pred, begin_hook)
recurse_complete = recurse_complete and complete
entries_count = n
else
recurse_complete = false
end
end
@ -143,7 +156,7 @@ local function get_directory_files(dir, root, path, t, begin_hook, max_files)
table.insert(t, f)
end
return t, entries_count
return t, recurse_complete, entries_count
end
@ -165,26 +178,28 @@ function core.project_subdir_is_shown(dir, filename)
end
local function show_max_files_warning()
core.status_view:show_message("!", style.accent,
local function show_max_files_warning(dir)
local message = dir.slow_filesystem and
"Filesystem is too slow: project files will not be indexed." or
"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."
)
core.status_view:show_message("!", style.accent, message)
end
-- Populate a project folder top directory by scanning the filesystem.
local function scan_project_folder(index)
local dir = core.project_directories[index]
local t, entries_count = get_directory_files(dir, dir.name, "", {}, nil, config.max_project_files)
if entries_count > config.max_project_files then
local t, complete, entries_count = get_directory_files(dir, dir.name, "", {}, 0, timed_max_files_pred)
if not complete then
dir.slow_filesystem = not complete and (entries_count <= config.max_project_files)
dir.files_limit = true
-- Watch non-recursively on Linux only.
-- The reason is recursively watching with dmon on linux
-- doesn't work on very large directories.
dir.watch_id = system.watch_dir(dir.name, PLATFORM ~= "Linux")
if core.status_view then -- May be not yet initialized.
show_max_files_warning()
show_max_files_warning(dir)
end
else
dir.watch_id = system.watch_dir(dir.name, true)
@ -198,7 +213,7 @@ function core.add_project_directory(path)
-- top directories has a file-like "item" but the item.filename
-- will be simply the name of the directory, without its path.
-- The field item.topdir will identify it as a top level directory.
path = common.normalize_path(path)
path = common.normalize_volume(path)
local dir = {
name = path,
item = {filename = common.basename(path), type = "dir", topdir = true},
@ -298,7 +313,7 @@ local function project_subdir_bounds(dir, filename)
end
local function rescan_project_subdir(dir, filename_rooted)
local new_files = get_directory_files(dir, dir.name, filename_rooted, {}, coroutine.yield)
local new_files = get_directory_files(dir, dir.name, filename_rooted, {}, 0, core.project_subdir_is_shown, coroutine.yield)
local index, n = 0, #dir.files
if filename_rooted ~= "" then
local filename = strip_leading_path(filename_rooted)
@ -316,7 +331,7 @@ end
function core.update_project_subdir(dir, filename, expanded)
local index, n, file = project_subdir_bounds(dir, filename)
if index then
local new_files = expanded and get_directory_files(dir, dir.name, PATHSEP .. filename, {}) or {}
local new_files = expanded and get_directory_files(dir, dir.name, PATHSEP .. filename, {}, 0, core.project_subdir_is_shown) or {}
files_list_replace(dir.files, index, n, new_files)
dir.is_dirty = true
return true
@ -572,9 +587,9 @@ function core.init()
Doc = require "core.doc"
if PATHSEP == '\\' then
USERDIR = common.normalize_path(USERDIR)
DATADIR = common.normalize_path(DATADIR)
EXEDIR = common.normalize_path(EXEDIR)
USERDIR = common.normalize_volume(USERDIR)
DATADIR = common.normalize_volume(DATADIR)
EXEDIR = common.normalize_volume(EXEDIR)
end
do
@ -673,7 +688,7 @@ function core.init()
-- We assume we have just a single project directory here. Now that StatusView
-- is there show max files warning if needed.
if core.project_directories[1].files_limit then
show_max_files_warning()
show_max_files_warning(core.project_directories[1])
end
for _, filename in ipairs(files) do

View File

@ -412,7 +412,7 @@ end
-- axis are swapped; this function lets us use the same code for both
local function calc_split_sizes(self, x, y, x1, x2, y1, y2)
local ds = ((x1 and x1 < 1) or (x2 and x2 < 1)) and 0 or style.divider_size
local n = math.floor(x1 and x1 + ds or (x2 and self.size[x] - x2 or self.size[x] * self.divider))
local n = x1 and x1 + ds or (x2 and self.size[x] - x2 or math.floor(self.size[x] * self.divider))
self.a.position[x] = self.position[x]
self.a.position[y] = self.position[y]
self.a.size[x] = n - ds
@ -675,6 +675,10 @@ end
function Node:resize(axis, value)
-- the application works fine with non-integer values but to have pixel-perfect
-- placements of view elements, like the scrollbar, we round the value to be
-- an integer.
value = math.floor(value)
if self.type == 'leaf' then
-- If it is not locked we don't accept the
-- resize operation here because for proportional panes the resize is