Merge pull request #236 from jgmdev/dev-sync

Merge latest master changes to dev branch
This commit is contained in:
Jefferson González 2021-06-01 04:23:20 -04:00 committed by GitHub
commit 76f561a8b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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. 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: Example user module snippet that makes all comments italic:
```lua ```lua
@ -15,6 +19,12 @@ local italic = renderer.font.load("italic.ttf", 14)
style.syntax_fonts["comment"] = italic 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 ### 1.16.10
Improved syntax highlight system thanks to @liquidev and @adamharrison. Improved syntax highlight system thanks to @liquidev and @adamharrison.

View File

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

View File

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

View File

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

View File

@ -68,6 +68,7 @@ function core.set_project_dir(new_dir, change_project_fn)
core.project_directories = {} core.project_directories = {}
core.add_project_directory(new_dir) core.add_project_directory(new_dir)
core.project_files = {} core.project_files = {}
core.project_files_limit = false
core.reschedule_project_scan() core.reschedule_project_scan()
return true return true
end end
@ -95,6 +96,57 @@ local function strip_trailing_slash(filename)
return filename return filename
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_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 project_scan_thread()
local function diff_files(a, b) local function diff_files(a, b)
if #a ~= #b then return true end if #a ~= #b then return true end
@ -106,71 +158,21 @@ local function project_scan_thread()
end end
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 while true do
-- get project files and replace previous table if the new table is -- get project files and replace previous table if the new table is
-- different -- 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 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 diff_files(dir.files, t) then
if entries_count > config.max_project_files then if entries_count > config.max_project_files then
core.project_files_limit = true
core.status_view:show_message("!", style.accent, core.status_view:show_message("!", style.accent,
"Too many files in project directory: stopping reading at ".. "Too many files in project directory: stopped reading at "..
config.max_project_files.." files according to config.max_project_files. ".. config.max_project_files.." files. For more information see "..
"Either tweak this variable, or ignore certain files/directories by ".. "usage.md at github.com/franko/lite-xl."
"using the config.ignore_files variable in your user plugin or ".. )
"project config.")
end end
dir.files = t dir.files = t
core.redraw = true core.redraw = true
@ -178,6 +180,7 @@ local function project_scan_thread()
if dir.name == core.project_dir then if dir.name == core.project_dir then
core.project_files = dir.files core.project_files = dir.files
end end
i = i + 1
end end
-- wait for next scan -- wait for next scan
@ -186,6 +189,46 @@ local function project_scan_thread()
end 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 function project_files_iter(state)
local dir = core.project_directories[state.dir_index] local dir = core.project_directories[state.dir_index]
state.file_index = state.file_index + 1 state.file_index = state.file_index + 1
@ -200,17 +243,27 @@ end
function core.get_project_files() function core.get_project_files()
local state = { dir_index = 1, file_index = 0 } if core.project_files_limit then
return project_files_iter, state 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 end
function core.project_files_number() function core.project_files_number()
local n = 0 if not core.project_files_limit then
for i = 1, #core.project_directories do local n = 0
n = n + #core.project_directories[i].files for i = 1, #core.project_directories do
n = n + #core.project_directories[i].files
end
return n
end end
return n
end end
@ -270,7 +323,7 @@ local style = require "core.style"
------------------------------- Fonts ---------------------------------------- ------------------------------- Fonts ----------------------------------------
-- customize 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) -- style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE)
-- --
-- font names used by lite: -- font names used by lite:
@ -353,6 +406,20 @@ local function whitespace_replacements()
end 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() function core.init()
command = require "core.command" command = require "core.command"
keymap = require "core.keymap" keymap = require "core.keymap"
@ -374,7 +441,7 @@ function core.init()
local recent_projects, window_position, window_mode = load_session() local recent_projects, window_position, window_mode = load_session()
if window_mode == "normal" then if window_mode == "normal" then
system.set_window_size(table.unpack(window_position)) system.set_window_size(table.unpack(window_position))
else elseif window_mode == "maximized" then
system.set_window_mode("maximized") system.set_window_mode("maximized")
end end
core.recent_projects = recent_projects core.recent_projects = recent_projects
@ -495,6 +562,8 @@ function core.init()
if item.text == "Exit" then os.exit(1) end if item.text == "Exit" then os.exit(1) end
end) end)
end end
reload_on_user_module_save()
end end
@ -554,13 +623,19 @@ do
end end
-- DEPRECATED function
core.doc_save_hooks = {} core.doc_save_hooks = {}
function core.add_save_hook(fn) 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 core.doc_save_hooks[#core.doc_save_hooks + 1] = fn
end end
-- DEPRECATED function
function core.on_doc_save(filename) 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 for _, hook in ipairs(core.doc_save_hooks) do
hook(filename) hook(filename)
end end
@ -1042,15 +1117,4 @@ function core.on_error(err)
end 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 return core

View File

@ -272,6 +272,7 @@ end
function Node:get_scroll_button_index(px, py) function Node:get_scroll_button_index(px, py)
if #self.views == 1 then return end
for i = 1, 2 do for i = 1, 2 do
local x, y, w, h = self:get_scroll_button_rect(i) 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 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 -- this file is used by lite-xl to setup the Lua environment when starting
-- when starting VERSION = "1.16.11"
VERSION = "1.16.10"
MOD_VERSION = "1" MOD_VERSION = "1"
SCALE = tonumber(os.getenv("LITE_SCALE")) or SCALE 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. -- 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. -- 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.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE)
style.big_font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Medium.ttf", 40 * 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_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.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) 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 end
local auto_detect_max_lines = 200 local auto_detect_max_lines = 100
local function detect_indent_stat(doc) local function detect_indent_stat(doc)
local stat = {} local stat = {}
@ -99,29 +99,11 @@ local function detect_indent_stat(doc)
end 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 function update_cache(doc)
local type, size, score = detect_indent_stat(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] 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 end
@ -129,6 +111,14 @@ local new = Doc.new
function Doc:new(...) function Doc:new(...)
new(self, ...) new(self, ...)
update_cache(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 end
local clean = Doc.clean local clean = Doc.clean

View File

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

View File

@ -93,6 +93,13 @@ function TreeView:get_item_height()
end end
function TreeView:invalidate_cache(dirname)
for _, v in pairs(self.cache[dirname]) do
v.skip = nil
end
end
function TreeView:check_cache() function TreeView:check_cache()
-- invalidate cache's skip values if project_files has changed -- invalidate cache's skip values if project_files has changed
for i = 1, #core.project_directories do for i = 1, #core.project_directories do
@ -102,9 +109,7 @@ function TreeView:check_cache()
self.last[dir.name] = dir.files self.last[dir.name] = dir.files
else else
if dir.files ~= last_files then if dir.files ~= last_files then
for _, v in pairs(self.cache[dir.name]) do self:invalidate_cache(dir.name)
v.skip = nil
end
self.last[dir.name] = dir.files self.last[dir.name] = dir.files
end end
end end
@ -208,17 +213,25 @@ function TreeView:on_mouse_pressed(button, x, y, clicks)
if caught then if caught then
return return
end end
if not self.hovered_item then local hovered_item = self.hovered_item
if not hovered_item then
return return
elseif self.hovered_item.type == "dir" then elseif hovered_item.type == "dir" then
if keymap.modkeys["ctrl"] and button == "left" then if keymap.modkeys["ctrl"] and button == "left" then
create_directory_in(self.hovered_item) create_directory_in(hovered_item)
else 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 end
else else
core.try(function() 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)) core.root_view:open_doc(core.open_doc(doc_filename))
end) end)
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 — if the module does not exist for the current project when the
command is run it will be created. 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 ## Add directories to a project
In addition to the project directories it is possible to add other directories In addition to the project directories it is possible to add other directories