Compare commits
254 Commits
ad75b7521f
...
9cfb20bd0e
Author | SHA1 | Date |
---|---|---|
George Sokianos | 9cfb20bd0e | |
George Sokianos | 9c2abb38db | |
Jefferson González | 951f0913da | |
Jefferson González | a2d5a7a904 | |
Adam Harrison | 3e7a97737e | |
Adam Harrison | 17645ba4ec | |
jgmdev | bbac4d1560 | |
jgmdev | c3bcf68851 | |
jgmdev | f0cc973e38 | |
Jefferson González | ad25216de7 | |
Jefferson González | 331c78faac | |
Guldoman | 46f9be2960 | |
Guldoman | 699655bebf | |
Guldoman | 3765ef1d7a | |
jgmdev | b741c204db | |
jgmdev | 3ffabced62 | |
Jefferson González | b5ead3992e | |
Guldoman | 3ec0f38446 | |
Guldoman | 2d5af22dc9 | |
jgmdev | a7fc7b4408 | |
Jefferson González | 30de42f4ab | |
jgmdev | e8427ae168 | |
Jefferson González | 5a63f6dc2e | |
jgmdev | 02f6dcc07d | |
takase1121 | d5da711b6f | |
Guldoman | 000caf2e43 | |
Guldoman | 9763701cbf | |
Guldoman | fb4a5f3828 | |
Jefferson González | 0e323f4a35 | |
Jefferson González | 1f468fca24 | |
Adam Harrison | ba5289dc75 | |
Adam Harrison | 82325b6a08 | |
Jan | 120c769e7e | |
jgmdev | 5830b7d9f0 | |
Jefferson González | 20763ed7ff | |
jgmdev | dcbebef2ab | |
jgmdev | 2ce4dbc8ef | |
Jefferson González | 17017b63ae | |
Jefferson González | 1725a48ce2 | |
Jefferson González | d0d0028472 | |
jgmdev | 620b669517 | |
Jefferson González | 46795972f0 | |
jgmdev | b880aa42f9 | |
jgmdev | 4b0531cdfc | |
jgmdev | d9909cf4ea | |
jgmdev | 0aa53a0e7f | |
Jefferson González | e08353ea08 | |
jgmdev | eeb2b28a03 | |
Adam | 960b482061 | |
Jefferson González | 52a47e0d73 | |
Jefferson González | 8a31a2b169 | |
Guldoman | 5714da81f8 | |
jgmdev | 48e86bb117 | |
Jefferson González | 6386bac4e5 | |
jgmdev | 9d4e475a2c | |
jgmdev | d7d88a2037 | |
Jefferson González | 1be425d1de | |
Guldoman | 240afa7abd | |
Adam | f85612e0f0 | |
jgmdev | 6cb403b450 | |
Jefferson González | d82b668a09 | |
jgmdev | c4f7380a95 | |
jgmdev | e5ca08e13f | |
Jefferson González | 1fa1960b05 | |
jgmdev | 2d3d9a1671 | |
Jefferson González | 0e79607895 | |
Jefferson González | 2f7da44275 | |
Guldoman | caefc9112a | |
Guldoman | fbb893c6b1 | |
Jefferson González | 2736072dce | |
jgmdev | a587182982 | |
jgmdev | 1e765b5c28 | |
jgmdev | 05e355d383 | |
Guldoman | 3d879286a4 | |
Jefferson González | be5f94e913 | |
jgmdev | e9775ced78 | |
jgmdev | 4c80aa7a40 | |
jgmdev | 15a31418ca | |
jgmdev | 128e10ba47 | |
jgmdev | 131bdf9fb2 | |
jgmdev | b1ce685489 | |
jgmdev | c5648e1f02 | |
jgmdev | 3452d499e5 | |
jgmdev | 90983b22a4 | |
jgmdev | 7cb4e93d14 | |
jgmdev | feaa3b2547 | |
Adam | 61e712db12 | |
Adam | 05e1968b0b | |
Guldoman | 539f929e30 | |
jgmdev | 2079e1f707 | |
Francesco Abbate | b02aae939c | |
Adam | a6f32ca0d0 | |
Adam | 3a0af6ee04 | |
Adam | af6a06bc8b | |
Adam | b05a02cb28 | |
Adam | a4833b21e4 | |
Francesco Abbate | 85531b0d3f | |
Guldoman | 5526041da3 | |
Guldoman | 59ba759167 | |
jgmdev | bb4569da53 | |
Jefferson González | dc5888bc07 | |
Guldoman | d4b8155cbc | |
Jefferson González | 8caccbf6f0 | |
Guldoman | f23cb33f7c | |
jgmdev | aec6806d8f | |
Adam | 212d4e2729 | |
jgmdev | df0635ad35 | |
Adam | e5f17aea4b | |
Adam | d78f310a4e | |
jgmdev | 6773e85cb8 | |
Guldoman | 9a6cd2b453 | |
Adam Harrison | d2d5617774 | |
Dheisom Gomes | f5e9146b1c | |
Adam Harrison | af76f544be | |
AlexSol | efedbae663 | |
Adam | 3d40725b8f | |
Dheisom Gomes | 13adedb23a | |
Adam Harrison | 941523868e | |
Adam Harrison | 3773a812bd | |
Adam | 0a70b13a73 | |
Dheisom Gomes | 22d8f69b5c | |
Dheisom Gomes | 6331a23c6b | |
Dheisom Gomes | 8c0685d440 | |
Francesco Abbate | 3a53b05b29 | |
Francesco Abbate | 3e39da071d | |
Francesco Abbate | bc9f8a4075 | |
Adam Harrison | 456126400a | |
Adam | 7e6d9df58d | |
Jan200101 | 227ca7d0e5 | |
Francesco Abbate | f7193c4fa2 | |
Adam Harrison | f9ad83e53e | |
Adam Harrison | ed4128bc65 | |
Adam Harrison | 428c757a13 | |
Adam | 9cb25acd7b | |
takase1121 | f24ebf853d | |
Adam | 8c8bd4692c | |
Adam Harrison | b523bd5cee | |
Francesco Abbate | f6a0e12e31 | |
Francesco Abbate | cd83df1abf | |
Adam | 6025c43241 | |
Adam Harrison | cdbfecc5ce | |
Adam Harrison | 30cc205cd4 | |
Adam | d3e1636881 | |
Adam | 3b3e41c095 | |
Adam | a4e5d9f043 | |
Francesco Abbate | 2dd154edeb | |
Jan200101 | 192c577966 | |
Francesco Abbate | 7e9b2f30da | |
Francesco Abbate | 2456452f65 | |
Guldoman | e51c76c72b | |
Francesco Abbate | ae1890d29a | |
Guldoman | 51975472a9 | |
Guldoman | 7eb9908f1a | |
Francesco Abbate | 4cdd42de1a | |
Francesco Abbate | 656a89c494 | |
Francesco Abbate | 827f3f876d | |
Francesco Abbate | 648b977c1e | |
Francesco Abbate | 9be22f0b8d | |
Francesco Abbate | 19ec86d971 | |
Francesco Abbate | 44a8dc320b | |
Francesco Abbate | 6584bdfd33 | |
Francesco Abbate | f0aea5b1a4 | |
Francesco Abbate | 39366d3a09 | |
Francesco Abbate | 1520c12580 | |
Francesco Abbate | 7473fbf32c | |
Francesco Abbate | 5032e7352e | |
Francesco Abbate | a703840068 | |
Francesco Abbate | 295c65da92 | |
Francesco Abbate | 5b154c189f | |
Adam Harrison | 31d448971a | |
Adam | 93076bdc41 | |
takase1121 | fc809b3172 | |
Francesco Abbate | 143b389365 | |
takase1121 | 087314aea4 | |
Francesco Abbate | 7ded5c8199 | |
Francesco Abbate | 1e7075ca9f | |
Francesco Abbate | 1b57107352 | |
Francesco Abbate | 9929ca9c2d | |
Francesco Abbate | f3cf7ac9c7 | |
Francesco Abbate | bf578d5ee4 | |
takase1121 | df0f6fb94c | |
takase1121 | e079ddfa37 | |
Adam | 067e7cc6cd | |
Jan200101 | 73a867fc89 | |
Jan200101 | 99ddf1fb92 | |
Adam | 186248911a | |
Francesco Abbate | 445c79bb52 | |
Francesco Abbate | 03350cc14b | |
Francesco Abbate | 85d26adb62 | |
Francesco Abbate | 68aea88510 | |
Francesco Abbate | 9578359b2b | |
Francesco Abbate | fd074ff39a | |
Francesco Abbate | adaf023541 | |
Nightwing | 60322a93a8 | |
Nightwing | 46aaf57b45 | |
Adam | 1552f18a87 | |
Adam | 416a06c566 | |
Adam | 3696937fec | |
Adam | d2e02bbed3 | |
Francesco Abbate | 88ed312f6b | |
Eli Schwartz | 7a961c8c8e | |
Eli Schwartz | fcb3c41082 | |
Francesco Abbate | 8550049db8 | |
Francesco Abbate | 2cf3c6f747 | |
Francesco Abbate | 05b003eeb5 | |
Francesco Abbate | 0f1b84040d | |
Francesco Abbate | 1f0785b73f | |
Adam | 1e6046e499 | |
takase1121 | 33f7fe4fda | |
takase1121 | 69a857bbbf | |
takase1121 | 4d31b1bc40 | |
takase1121 | 16df6d8bce | |
takase1121 | 84a3906323 | |
Adam | 4be8a8b582 | |
Guldoman | d16e46dba5 | |
Guldoman | a122d7e916 | |
Guldoman | eac82e69fb | |
Francesco Abbate | 9155be7a22 | |
Guldoman | 9e7bdf49e9 | |
Lorenzo Orsatti | 66bc551488 | |
Jipok | a19baeacb1 | |
takase1121 | 00e2e281d3 | |
takase1121 | 2f65d5a26f | |
takase1121 | 8f06ef9b81 | |
Adam | 590c8bb456 | |
Adam | e0d0d17c4d | |
Adam Harrison | 978550d2a2 | |
Jipok | 773a85cd2d | |
Adam | 61379a9ab8 | |
takase1121 | 54f6579e9d | |
takase1121 | 3e175f5ad5 | |
takase1121 | 8d7867d118 | |
Jipok | 6c1c983d1c | |
Francesco Abbate | 50247fcd92 | |
Francesco Abbate | 3109263c5d | |
Jipok | c353dd6eda | |
Guldoman | 29318be9c7 | |
Francesco Abbate | 37c00c877a | |
Francesco Abbate | 405bd1c2bd | |
Adam Harrison | e512c57637 | |
Guldoman | 23f83857c5 | |
takase1121 | 695c7bf781 | |
takase1121 | 1526cd176c | |
takase1121 | becdb99222 | |
takase1121 | 31df408d93 | |
takase1121 | fd3b4334ce | |
takase1121 | b5dff196f6 | |
takase1121 | ab4ecd515b | |
Jipok | 7381a13d6f | |
Jipok | 6a135f7c06 | |
Jipok | 4a563ddea1 | |
Jipok | 4eee123eff | |
Jipok | acc7ceefd4 | |
Jipok | 93d9e61a03 |
|
@ -33,3 +33,6 @@
|
|||
|
||||
"Category: C Core":
|
||||
- src/**/*
|
||||
|
||||
"Category: Libraries":
|
||||
- lib/**/*
|
||||
|
|
|
@ -2,8 +2,7 @@ build*/
|
|||
.build*/
|
||||
lhelper/
|
||||
submodules/
|
||||
subprojects/lua/
|
||||
subprojects/reproc/
|
||||
subprojects/*/
|
||||
/appimage*
|
||||
.ccls-cache
|
||||
.lite-debug.log
|
||||
|
|
|
@ -154,6 +154,7 @@ See the [licenses] file for details on licenses used by the required dependencie
|
|||
[Get color themes]: https://github.com/lite-xl/lite-xl-colors
|
||||
[changelog]: https://github.com/lite-xl/lite-xl/blob/master/changelog.md
|
||||
[Lite XL plugins repository]: https://github.com/lite-xl/lite-xl-plugins
|
||||
[plugins repository]: https://github.com/rxi/lite-plugins
|
||||
[colors repository]: https://github.com/lite-xl/lite-xl-colors
|
||||
[LICENSE]: LICENSE
|
||||
[licenses]: licenses/licenses.md
|
||||
|
|
50
changelog.md
50
changelog.md
|
@ -1,5 +1,55 @@
|
|||
This files document the changes done in Lite XL for each release.
|
||||
|
||||
### 2.0.5
|
||||
|
||||
Revamp the project's user module so that modifications are immediately applied.
|
||||
|
||||
Add a mechanism to ignore files or directory based on their project's path.
|
||||
The new mechanism is backward compatible.*
|
||||
|
||||
Essentially there are two mechanisms:
|
||||
|
||||
- if a '/' or a '/$' appear at the end of the pattern it will match only directories
|
||||
- if a '/' appears anywhere in the pattern except at the end the pattern will be
|
||||
applied to the path
|
||||
|
||||
In the first case, when the pattern corresponds to a directory, a '/' will be
|
||||
appended to the name of each directory before checking the pattern.
|
||||
|
||||
In the second case, when the pattern corresponds to a path, the complete path of
|
||||
the file or directory will be used with an initial '/' added to the path.
|
||||
|
||||
Fix several problems with the directory monitoring library.
|
||||
Now the application should no longer assert when some related system call fails
|
||||
and we fallback to rescan when an error happens.
|
||||
On linux no longer use the recursive monitoring which was a source of problem.
|
||||
|
||||
Directory monitoring is now aware of symlinks and treat them appropriately.
|
||||
|
||||
Fix problem when encountering special files type on linux.
|
||||
|
||||
Improve directory monitoring so that the related thread actually waits without using
|
||||
any CPU time when there are no events.
|
||||
|
||||
Improve the suggestion when changing project folder or opening a new one.
|
||||
Now the previously used directory are suggested but if the path is changed the
|
||||
actual existing directories that match the pattern are suggested.
|
||||
In addition always use the text entered in the command view even if a suggested entry
|
||||
is highlighted.
|
||||
|
||||
The NagView warning window now no longer moves the document content.
|
||||
|
||||
### 2.0.4
|
||||
|
||||
Fix some bugs related to newly introduced directory monitoring using the dmon library.
|
||||
|
||||
Fix a problem with plain text search using Lua patterns by error.
|
||||
|
||||
Fix a problem with visualization of UTF-8 characters that caused garbage characters
|
||||
visualization.
|
||||
|
||||
Other fixes and improvements contributed by @Guldoman.
|
||||
|
||||
### 2.0.3
|
||||
|
||||
Replace periodic rescan of project folder with a notification based system using the
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
local bit = {}
|
||||
|
||||
local LUA_NBITS = 32
|
||||
local ALLONES = (~(((~0) << (LUA_NBITS - 1)) << 1))
|
||||
|
||||
local function trim(x)
|
||||
return (x & ALLONES)
|
||||
end
|
||||
|
||||
local function mask(n)
|
||||
return (~((ALLONES << 1) << ((n) - 1)))
|
||||
end
|
||||
|
||||
local function check_args(field, width)
|
||||
assert(field >= 0, "field cannot be negative")
|
||||
assert(width > 0, "width must be positive")
|
||||
assert(field + width < LUA_NBITS and field + width >= 0,
|
||||
"trying to access non-existent bits")
|
||||
end
|
||||
|
||||
function bit.extract(n, field, width)
|
||||
local w = width or 1
|
||||
check_args(field, w)
|
||||
local m = trim(n)
|
||||
return m >> field & mask(w)
|
||||
end
|
||||
|
||||
function bit.replace(n, v, field, width)
|
||||
local w = width or 1
|
||||
check_args(field, w)
|
||||
local m = trim(n)
|
||||
local x = v & mask(width);
|
||||
return m & ~(mask(w) << field) | (x << field)
|
||||
end
|
||||
|
||||
return bit
|
|
@ -64,7 +64,7 @@ end
|
|||
function command.add_defaults()
|
||||
local reg = {
|
||||
"core", "root", "command", "doc", "findreplace",
|
||||
"files", "drawwhitespace", "dialog"
|
||||
"files", "drawwhitespace", "dialog", "log", "statusbar"
|
||||
}
|
||||
for _, name in ipairs(reg) do
|
||||
require("core.commands." .. name)
|
||||
|
|
|
@ -10,8 +10,18 @@ local restore_title_view = false
|
|||
|
||||
local function suggest_directory(text)
|
||||
text = common.home_expand(text)
|
||||
return common.home_encode_list((text == "" or text == common.home_expand(common.dirname(core.project_dir)))
|
||||
and core.recent_projects or common.dir_path_suggest(text))
|
||||
local basedir = common.dirname(core.project_dir)
|
||||
return common.home_encode_list((basedir and text == basedir .. PATHSEP or text == "") and
|
||||
core.recent_projects or common.dir_path_suggest(text))
|
||||
end
|
||||
|
||||
local function check_directory_path(path)
|
||||
local abs_path = system.absolute_path(path)
|
||||
local info = abs_path and system.get_file_info(abs_path)
|
||||
if not info or info.type ~= 'dir' then
|
||||
return nil
|
||||
end
|
||||
return abs_path
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
|
@ -93,6 +103,15 @@ command.add(nil, {
|
|||
core.root_view:open_doc(core.open_doc())
|
||||
end,
|
||||
|
||||
["core:new-named-doc"] = function()
|
||||
core.command_view:enter(
|
||||
"File name",
|
||||
function(text)
|
||||
core.root_view:open_doc(core.open_doc(text))
|
||||
end
|
||||
)
|
||||
end,
|
||||
|
||||
["core:open-file"] = function()
|
||||
local view = core.active_view
|
||||
if view.doc and view.doc.abs_filename then
|
||||
|
@ -141,46 +160,50 @@ command.add(nil, {
|
|||
end,
|
||||
|
||||
["core:open-project-module"] = function()
|
||||
local filename = ".lite_project.lua"
|
||||
if system.get_file_info(filename) then
|
||||
core.root_view:open_doc(core.open_doc(filename))
|
||||
else
|
||||
local doc = core.open_doc()
|
||||
core.root_view:open_doc(doc)
|
||||
doc:save(filename)
|
||||
if not system.get_file_info(".lite_project.lua") then
|
||||
core.try(core.write_init_project_module, ".lite_project.lua")
|
||||
end
|
||||
local doc = core.open_doc(".lite_project.lua")
|
||||
core.root_view:open_doc(doc)
|
||||
doc:save()
|
||||
end,
|
||||
|
||||
["core:change-project-folder"] = function()
|
||||
local dirname = common.dirname(core.project_dir)
|
||||
if dirname then
|
||||
core.command_view:set_text(common.home_encode(dirname))
|
||||
core.command_view:set_text(common.home_encode(dirname) .. PATHSEP)
|
||||
end
|
||||
core.command_view:enter("Change Project Folder", function(text, item)
|
||||
text = system.absolute_path(common.home_expand(item and item.text or text))
|
||||
if text == core.project_dir then return end
|
||||
local path_stat = system.get_file_info(text)
|
||||
if not path_stat or path_stat.type ~= 'dir' then
|
||||
core.error("Cannot open folder %q", text)
|
||||
core.command_view:enter("Change Project Folder", function(text)
|
||||
local path = common.home_expand(text)
|
||||
local abs_path = check_directory_path(path)
|
||||
if not abs_path then
|
||||
core.error("Cannot open directory %q", path)
|
||||
return
|
||||
end
|
||||
core.confirm_close_docs(core.docs, core.open_folder_project, text)
|
||||
if abs_path == core.project_dir then return end
|
||||
core.confirm_close_docs(core.docs, function(dirpath)
|
||||
core.open_folder_project(dirpath)
|
||||
end, abs_path)
|
||||
end, suggest_directory)
|
||||
end,
|
||||
|
||||
["core:open-project-folder"] = function()
|
||||
local dirname = common.dirname(core.project_dir)
|
||||
if dirname then
|
||||
core.command_view:set_text(common.home_encode(dirname))
|
||||
core.command_view:set_text(common.home_encode(dirname) .. PATHSEP)
|
||||
end
|
||||
core.command_view:enter("Open Project", function(text, item)
|
||||
text = common.home_expand(item and item.text or text)
|
||||
local path_stat = system.get_file_info(text)
|
||||
if not path_stat or path_stat.type ~= 'dir' then
|
||||
core.error("Cannot open folder %q", text)
|
||||
core.command_view:enter("Open Project", function(text)
|
||||
local path = common.home_expand(text)
|
||||
local abs_path = check_directory_path(path)
|
||||
if not abs_path then
|
||||
core.error("Cannot open directory %q", path)
|
||||
return
|
||||
end
|
||||
system.exec(string.format("%q %q", EXEFILE, text))
|
||||
if abs_path == core.project_dir then
|
||||
core.error("Directory %q is currently opened", abs_path)
|
||||
return
|
||||
end
|
||||
system.exec(string.format("%q %q", EXEFILE, abs_path))
|
||||
end, suggest_directory)
|
||||
end,
|
||||
|
||||
|
|
|
@ -47,19 +47,32 @@ end
|
|||
|
||||
local function cut_or_copy(delete)
|
||||
local full_text = ""
|
||||
local text = ""
|
||||
core.cursor_clipboard = {}
|
||||
core.cursor_clipboard_whole_line = {}
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
local text = doc():get_text(line1, col1, line2, col2)
|
||||
text = doc():get_text(line1, col1, line2, col2)
|
||||
full_text = full_text == "" and text or (full_text .. " " .. text)
|
||||
core.cursor_clipboard_whole_line[idx] = false
|
||||
if delete then
|
||||
doc():delete_to_cursor(idx, 0)
|
||||
end
|
||||
full_text = full_text == "" and text or (full_text .. "\n" .. text)
|
||||
doc().cursor_clipboard[idx] = text
|
||||
else -- Cut/copy whole line
|
||||
text = doc().lines[line1]
|
||||
full_text = full_text == "" and text or (full_text .. text)
|
||||
core.cursor_clipboard_whole_line[idx] = true
|
||||
if delete then
|
||||
if line1 < #doc().lines then
|
||||
doc():remove(line1, 1, line1 + 1, 1)
|
||||
else
|
||||
doc().cursor_clipboard[idx] = ""
|
||||
doc():remove(line1 - 1, math.huge, line1, math.huge)
|
||||
end
|
||||
end
|
||||
doc().cursor_clipboard["full"] = full_text
|
||||
end
|
||||
core.cursor_clipboard[idx] = text
|
||||
end
|
||||
core.cursor_clipboard["full"] = full_text
|
||||
system.set_clipboard(full_text)
|
||||
end
|
||||
|
||||
|
@ -84,7 +97,90 @@ local function set_cursor(x, y, snap_type)
|
|||
core.blink_reset()
|
||||
end
|
||||
|
||||
local selection_commands = {
|
||||
local function line_comment(comment, line1, col1, line2, col2)
|
||||
local start_comment = (type(comment) == 'table' and comment[1] or comment) .. " "
|
||||
local end_comment = (type(comment) == 'table' and " " .. comment[2])
|
||||
local uncomment = true
|
||||
local start_offset = math.huge
|
||||
for line = line1, line2 do
|
||||
local text = doc().lines[line]
|
||||
local s = text:find("%S")
|
||||
if s then
|
||||
local cs, ce = text:find(start_comment, s, true)
|
||||
if cs ~= s then
|
||||
uncomment = false
|
||||
end
|
||||
start_offset = math.min(start_offset, s)
|
||||
end
|
||||
end
|
||||
|
||||
local end_line = col2 == #doc().lines[line2]
|
||||
for line = line1, line2 do
|
||||
local text = doc().lines[line]
|
||||
local s = text:find("%S")
|
||||
if s and uncomment then
|
||||
if end_comment and text:sub(#text - #end_comment, #text - 1) == end_comment then
|
||||
doc():remove(line, #text - #end_comment, line, #text)
|
||||
end
|
||||
local cs, ce = text:find(start_comment, s, true)
|
||||
if ce then
|
||||
doc():remove(line, cs, line, ce + 1)
|
||||
end
|
||||
elseif s then
|
||||
doc():insert(line, start_offset, start_comment)
|
||||
if end_comment then
|
||||
doc():insert(line, #doc().lines[line], " " .. comment[2])
|
||||
end
|
||||
end
|
||||
end
|
||||
col1 = col1 + (col1 > start_offset and #start_comment or 0) * (uncomment and -1 or 1)
|
||||
col2 = col2 + (col2 > start_offset and #start_comment or 0) * (uncomment and -1 or 1)
|
||||
if end_comment and end_line then
|
||||
col2 = col2 + #end_comment * (uncomment and -1 or 1)
|
||||
end
|
||||
return line1, col1, line2, col2
|
||||
end
|
||||
|
||||
local function block_comment(comment, line1, col1, line2, col2)
|
||||
-- automatically skip spaces
|
||||
local word_start = doc():get_text(line1, col1, line1, math.huge):find("%S")
|
||||
local word_end = doc():get_text(line2, 1, line2, col2):find("%s*$")
|
||||
col1 = col1 + (word_start and (word_start - 1) or 0)
|
||||
col2 = word_end and word_end or col2
|
||||
|
||||
local block_start = doc():get_text(line1, col1, line1, col1 + #comment[1])
|
||||
local block_end = doc():get_text(line2, col2 - #comment[2], line2, col2)
|
||||
|
||||
if block_start == comment[1] and block_end == comment[2] then
|
||||
-- remove up to 1 whitespace after the comment
|
||||
local start_len, stop_len = #comment[1], #comment[2]
|
||||
if doc():get_text(line1, col1 + #comment[1], line1, col1 + #comment[1] + 1):find("%s$") then
|
||||
start_len = start_len + 1
|
||||
end
|
||||
if doc():get_text(line2, col2 - #comment[2] - 1, line2, col2):find("^%s") then
|
||||
stop_len = stop_len + 1
|
||||
end
|
||||
|
||||
doc():remove(line1, col1, line1, col1 + start_len)
|
||||
col2 = col2 - (line1 == line2 and start_len or 0)
|
||||
doc():remove(line2, col2 - stop_len, line2, col2)
|
||||
|
||||
return line1, col1, line2, col2 - stop_len
|
||||
else
|
||||
doc():insert(line1, col1, comment[1] .. " ")
|
||||
col2 = col2 + (line1 == line2 and (#comment[1] + 1) or 0)
|
||||
doc():insert(line2, col2, " " .. comment[2])
|
||||
|
||||
return line1, col1, line2, col2 + #comment[2] + 1
|
||||
end
|
||||
end
|
||||
|
||||
local commands = {
|
||||
["doc:select-none"] = function()
|
||||
local line, col = doc():get_selection()
|
||||
doc():set_selection(line, col)
|
||||
end,
|
||||
|
||||
["doc:cut"] = function()
|
||||
cut_or_copy(true)
|
||||
end,
|
||||
|
@ -93,13 +189,6 @@ local selection_commands = {
|
|||
cut_or_copy(false)
|
||||
end,
|
||||
|
||||
["doc:select-none"] = function()
|
||||
local line, col = doc():get_selection()
|
||||
doc():set_selection(line, col)
|
||||
end
|
||||
}
|
||||
|
||||
local commands = {
|
||||
["doc:undo"] = function()
|
||||
doc():undo()
|
||||
end,
|
||||
|
@ -111,13 +200,29 @@ local commands = {
|
|||
["doc:paste"] = function()
|
||||
local clipboard = system.get_clipboard()
|
||||
-- If the clipboard has changed since our last look, use that instead
|
||||
if doc().cursor_clipboard["full"] ~= clipboard then
|
||||
doc().cursor_clipboard = {}
|
||||
local external_paste = core.cursor_clipboard["full"] ~= clipboard
|
||||
if external_paste then
|
||||
core.cursor_clipboard = {}
|
||||
core.cursor_clipboard_whole_line = {}
|
||||
end
|
||||
local value, whole_line
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||
local value = doc().cursor_clipboard[idx] or clipboard
|
||||
if #core.cursor_clipboard_whole_line == (#doc().selections/4) then
|
||||
value = core.cursor_clipboard[idx]
|
||||
whole_line = core.cursor_clipboard_whole_line[idx] == true
|
||||
else
|
||||
value = clipboard
|
||||
whole_line = not external_paste and clipboard:find("\n") ~= nil
|
||||
end
|
||||
if whole_line then
|
||||
doc():insert(line1, 1, value:gsub("\r", ""))
|
||||
if col1 == 1 then
|
||||
doc():move_to_cursor(idx, #value)
|
||||
end
|
||||
else
|
||||
doc():text_input(value:gsub("\r", ""), idx)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:newline"] = function()
|
||||
|
@ -171,6 +276,11 @@ local commands = {
|
|||
|
||||
["doc:select-all"] = function()
|
||||
doc():set_selection(1, 1, math.huge, math.huge)
|
||||
-- avoid triggering DocView:scroll_to_make_visible
|
||||
dv().last_line1 = 1
|
||||
dv().last_col1 = 1
|
||||
dv().last_line2 = #doc().lines
|
||||
dv().last_col2 = #doc().lines[#doc().lines]
|
||||
end,
|
||||
|
||||
["doc:select-lines"] = function()
|
||||
|
@ -263,34 +373,30 @@ local commands = {
|
|||
end
|
||||
end,
|
||||
|
||||
["doc:toggle-block-comments"] = function()
|
||||
local comment = doc().syntax.block_comment
|
||||
if not comment then
|
||||
if doc().syntax.comment then
|
||||
command.perform "doc:toggle-line-comments"
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
-- if nothing is selected, toggle the whole line
|
||||
if line1 == line2 and col1 == col2 then
|
||||
col1 = 1
|
||||
col2 = #doc().lines[line2]
|
||||
end
|
||||
doc():set_selections(idx, block_comment(comment, line1, col1, line2, col2))
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:toggle-line-comments"] = function()
|
||||
local comment = doc().syntax.comment
|
||||
if not comment then return end
|
||||
local indentation = doc():get_indent_string()
|
||||
local comment_text = comment .. " "
|
||||
for idx, line1, _, line2 in doc_multiline_selections(true) do
|
||||
local uncomment = true
|
||||
local start_offset = math.huge
|
||||
for line = line1, line2 do
|
||||
local text = doc().lines[line]
|
||||
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
|
||||
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
|
||||
local comment = doc().syntax.comment or doc().syntax.block_comment
|
||||
if comment then
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
doc():set_selections(idx, line_comment(comment, line1, col1, line2, col2))
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
@ -388,7 +494,7 @@ local commands = {
|
|||
end
|
||||
for i,docview in ipairs(core.get_views_referencing_doc(doc())) do
|
||||
local node = core.root_view.root_node:get_node_for_view(docview)
|
||||
node:close_view(core.root_view, docview)
|
||||
node:close_view(core.root_view.root_node, docview)
|
||||
end
|
||||
os.remove(filename)
|
||||
core.log("Removed \"%s\"", filename)
|
||||
|
@ -415,7 +521,18 @@ local commands = {
|
|||
|
||||
["doc:split-cursor"] = function(x, y, clicks)
|
||||
local line, col = dv():resolve_screen_position(x, y)
|
||||
local removal_target = nil
|
||||
for idx, line1, col1 in doc():get_selections(true) do
|
||||
if line1 == line and col1 == col and #doc().selections > 4 then
|
||||
removal_target = idx
|
||||
end
|
||||
end
|
||||
if removal_target then
|
||||
doc():remove_selection(removal_target)
|
||||
else
|
||||
doc():add_selection(line, col, line, col)
|
||||
end
|
||||
dv().mouse_selecting = { line, col, "set" }
|
||||
end,
|
||||
|
||||
["doc:create-cursor-previous-line"] = function()
|
||||
|
@ -461,21 +578,20 @@ commands["doc:move-to-previous-char"] = function()
|
|||
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
doc():set_selections(idx, line1, col1)
|
||||
else
|
||||
doc():move_to_cursor(idx, translate.previous_char)
|
||||
end
|
||||
end
|
||||
doc():move_to(translate.previous_char)
|
||||
end
|
||||
|
||||
commands["doc:move-to-next-char"] = function()
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
doc():set_selections(idx, line2, col2)
|
||||
else
|
||||
doc():move_to_cursor(idx, translate.next_char)
|
||||
end
|
||||
end
|
||||
doc():move_to(translate.next_char)
|
||||
end
|
||||
|
||||
command.add("core.docview", commands)
|
||||
command.add(function()
|
||||
return core.active_view:is(DocView) and core.active_view.doc:has_any_selection()
|
||||
end ,selection_commands)
|
||||
|
|
|
@ -55,7 +55,7 @@ end
|
|||
local function find(label, search_fn)
|
||||
last_view, last_sel = core.active_view,
|
||||
{ core.active_view.doc:get_selection() }
|
||||
local text = last_view.doc:get_text(unpack(last_sel))
|
||||
local text = last_view.doc:get_text(table.unpack(last_sel))
|
||||
found_expression = false
|
||||
|
||||
core.command_view:set_text(text, true)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
|
||||
|
||||
command.add(nil, {
|
||||
["log:open-as-doc"] = function()
|
||||
local doc = core.open_doc("logs.txt")
|
||||
core.root_view:open_doc(doc)
|
||||
doc:insert(1, 1, core.get_log())
|
||||
doc.new_file = false
|
||||
doc:clean()
|
||||
end,
|
||||
["log:copy-to-clipboard"] = function()
|
||||
system.set_clipboard(core.get_log())
|
||||
end
|
||||
})
|
|
@ -0,0 +1,71 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
local style = require "core.style"
|
||||
local StatusView = require "core.statusview"
|
||||
|
||||
local function status_view_item_names()
|
||||
local items = core.status_view:get_items_list()
|
||||
local names = {}
|
||||
for _, item in ipairs(items) do
|
||||
table.insert(names, item.name)
|
||||
end
|
||||
return names
|
||||
end
|
||||
|
||||
local function status_view_items_data(names)
|
||||
local data = {}
|
||||
for _, name in ipairs(names) do
|
||||
local item = core.status_view:get_item(name)
|
||||
table.insert(data, {
|
||||
text = command.prettify_name(item.name),
|
||||
info = item.alignment == StatusView.Item.LEFT and "Left" or "Right",
|
||||
name = item.name
|
||||
})
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
local function status_view_get_items(text)
|
||||
local names = status_view_item_names()
|
||||
local results = common.fuzzy_match(names, text)
|
||||
results = status_view_items_data(results)
|
||||
return results
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
["status-bar:toggle"] = function()
|
||||
core.status_view:toggle()
|
||||
end,
|
||||
["status-bar:show"] = function()
|
||||
core.status_view:show()
|
||||
end,
|
||||
["status-bar:hide"] = function()
|
||||
core.status_view:hide()
|
||||
end,
|
||||
["status-bar:disable-messages"] = function()
|
||||
core.status_view:display_messages(false)
|
||||
end,
|
||||
["status-bar:enable-messages"] = function()
|
||||
core.status_view:display_messages(true)
|
||||
end,
|
||||
["status-bar:hide-item"] = function()
|
||||
core.command_view:enter("Status bar item to hide",
|
||||
function(text, item)
|
||||
core.status_view:hide_items(item.name)
|
||||
end,
|
||||
status_view_get_items
|
||||
)
|
||||
end,
|
||||
["status-bar:show-item"] = function()
|
||||
core.command_view:enter("Status bar item to show",
|
||||
function(text, item)
|
||||
core.status_view:show_items(item.name)
|
||||
end,
|
||||
status_view_get_items
|
||||
)
|
||||
end,
|
||||
["status-bar:reset-items"] = function()
|
||||
core.status_view:show_items()
|
||||
end,
|
||||
})
|
|
@ -17,6 +17,14 @@ function common.clamp(n, lo, hi)
|
|||
end
|
||||
|
||||
|
||||
function common.merge(a, b)
|
||||
local t = {}
|
||||
for k, v in pairs(a) do t[k] = v end
|
||||
if b then for k, v in pairs(b) do t[k] = v end end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function common.round(n)
|
||||
return n >= 0 and math.floor(n + 0.5) or math.ceil(n - 0.5)
|
||||
end
|
||||
|
@ -42,7 +50,7 @@ end
|
|||
|
||||
|
||||
function common.distance(x1, y1, x2, y2)
|
||||
return math.sqrt(math.pow(x2-x1, 2)+math.pow(y2-y1, 2))
|
||||
return math.sqrt(((x2-x1) ^ 2)+((y2-y1) ^ 2))
|
||||
end
|
||||
|
||||
|
||||
|
@ -437,4 +445,5 @@ function common.rm(path, recursively)
|
|||
return true
|
||||
end
|
||||
|
||||
|
||||
return common
|
||||
|
|
|
@ -6,19 +6,19 @@ config.message_timeout = 5
|
|||
config.mouse_wheel_scroll = 50 * SCALE
|
||||
config.scroll_past_end = true
|
||||
config.file_size_limit = 10
|
||||
config.ignore_files = "^%."
|
||||
config.ignore_files = { "^%." }
|
||||
config.symbol_pattern = "[%a_][%w_]*"
|
||||
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
||||
config.undo_merge_timeout = 0.3
|
||||
config.max_undos = 10000
|
||||
config.max_tabs = 8
|
||||
config.always_show_tabs = true
|
||||
-- Possible values: false, true, "no_selection"
|
||||
config.highlight_current_line = true
|
||||
config.line_height = 1.2
|
||||
config.indent_size = 2
|
||||
config.tab_type = "soft"
|
||||
config.line_limit = 80
|
||||
config.max_symbols = 4000
|
||||
config.max_project_files = 2000
|
||||
config.transitions = true
|
||||
config.animation_rate = 1.0
|
||||
|
@ -29,10 +29,19 @@ config.borderless = false
|
|||
config.tab_close_button = true
|
||||
config.max_clicks = 3
|
||||
|
||||
-- Disable plugin loading setting to false the config entry
|
||||
-- of the same name.
|
||||
config.plugins = {}
|
||||
-- set as true to be able to test non supported plugins
|
||||
config.skip_plugins_version = false
|
||||
|
||||
config.plugins = {}
|
||||
-- Allow you to set plugin configs even if we haven't seen the plugin before.
|
||||
setmetatable(config.plugins, {
|
||||
__index = function(t, k)
|
||||
if rawget(t, k) == nil then rawset(t, k, {}) end
|
||||
return rawget(t, k)
|
||||
end
|
||||
})
|
||||
|
||||
-- Disable these plugins by default.
|
||||
config.plugins.trimwhitespace = false
|
||||
config.plugins.lineguide = false
|
||||
config.plugins.drawwhitespace = false
|
||||
|
|
|
@ -91,6 +91,7 @@ function ContextMenu:show(x, y)
|
|||
|
||||
self.position.x, self.position.y = x, y
|
||||
self.show_context_menu = true
|
||||
core.request_cursor("arrow")
|
||||
return true
|
||||
end
|
||||
return false
|
||||
|
@ -101,6 +102,7 @@ function ContextMenu:hide()
|
|||
self.items = nil
|
||||
self.selected = -1
|
||||
self.height = 0
|
||||
core.request_cursor(core.active_view.cursor)
|
||||
end
|
||||
|
||||
function ContextMenu:each_item()
|
||||
|
@ -126,9 +128,6 @@ function ContextMenu:on_mouse_moved(px, py)
|
|||
break
|
||||
end
|
||||
end
|
||||
if self.selected >= 0 then
|
||||
core.request_cursor("arrow")
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
@ -140,8 +139,38 @@ function ContextMenu:on_selected(item)
|
|||
end
|
||||
end
|
||||
|
||||
function ContextMenu:on_mouse_pressed(button, x, y, clicks)
|
||||
local selected = (self.items or {})[self.selected]
|
||||
local function change_value(value, change)
|
||||
return value + change
|
||||
end
|
||||
|
||||
function ContextMenu:focus_previous()
|
||||
self.selected = (self.selected == -1 or self.selected == 1) and #self.items or change_value(self.selected, -1)
|
||||
if self:get_item_selected() == DIVIDER then
|
||||
self.selected = change_value(self.selected, -1)
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:focus_next()
|
||||
self.selected = (self.selected == -1 or self.selected == #self.items) and 1 or change_value(self.selected, 1)
|
||||
if self:get_item_selected() == DIVIDER then
|
||||
self.selected = change_value(self.selected, 1)
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:get_item_selected()
|
||||
return (self.items or {})[self.selected]
|
||||
end
|
||||
|
||||
function ContextMenu:call_selected_item()
|
||||
local selected = self:get_item_selected()
|
||||
self:hide()
|
||||
if selected then
|
||||
self:on_selected(selected)
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:on_mouse_pressed(button, px, py, clicks)
|
||||
local selected = self:get_item_selected()
|
||||
local caught = false
|
||||
|
||||
self:hide()
|
||||
|
@ -153,7 +182,7 @@ function ContextMenu:on_mouse_pressed(button, x, y, clicks)
|
|||
end
|
||||
|
||||
if button == "right" then
|
||||
caught = self:show(x, y)
|
||||
caught = self:show(px, py)
|
||||
end
|
||||
return caught
|
||||
end
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local dirwatch = {}
|
||||
|
||||
function dirwatch:__index(idx)
|
||||
local value = rawget(self, idx)
|
||||
if value ~= nil then return value end
|
||||
return dirwatch[idx]
|
||||
end
|
||||
|
||||
function dirwatch.new()
|
||||
local t = {
|
||||
scanned = {},
|
||||
watched = {},
|
||||
reverse_watched = {},
|
||||
monitor = dirmonitor.new(),
|
||||
windows_watch_top = nil,
|
||||
windows_watch_count = 0
|
||||
}
|
||||
setmetatable(t, dirwatch)
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function dirwatch:scan(directory, bool)
|
||||
if bool == false then return self:unwatch(directory) end
|
||||
self.scanned[directory] = system.get_file_info(directory).modified
|
||||
end
|
||||
|
||||
-- Should be called on every directory in a subdirectory.
|
||||
-- In windows, this is a no-op for anything underneath a top-level directory,
|
||||
-- but code should be called anyway, so we can ensure that we have a proper
|
||||
-- experience across all platforms. Should be an absolute path.
|
||||
function dirwatch:watch(directory, bool)
|
||||
if bool == false then return self:unwatch(directory) end
|
||||
if not self.watched[directory] and not self.scanned[directory] then
|
||||
if PLATFORM == "Windows" then
|
||||
if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then
|
||||
-- Get the highest level of directory that is common to this directory, and the original.
|
||||
local target = directory
|
||||
while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do
|
||||
target = common.dirname(target)
|
||||
end
|
||||
if target ~= self.windows_watch_top then
|
||||
local value = self.monitor:watch(target)
|
||||
if value and value < 0 then
|
||||
return self:scan(directory)
|
||||
end
|
||||
self.windows_watch_top = target
|
||||
self.windows_watch_count = self.windows_watch_count + 1
|
||||
end
|
||||
end
|
||||
self.watched[directory] = true
|
||||
else
|
||||
local value = self.monitor:watch(directory)
|
||||
-- If for whatever reason, we can't watch this directory, revert back to scanning.
|
||||
-- Don't bother trying to find out why, for now.
|
||||
if value and value < 0 then
|
||||
return self:scan(directory)
|
||||
end
|
||||
self.watched[directory] = value
|
||||
self.reverse_watched[value] = directory
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- this should be an absolute path
|
||||
function dirwatch:unwatch(directory)
|
||||
if self.watched[directory] then
|
||||
if PLATFORM ~= "Windows" then
|
||||
self.monitor:unwatch(self.watched[directory])
|
||||
self.reverse_watched[directory] = nil
|
||||
else
|
||||
self.windows_watch_count = self.windows_watch_count - 1
|
||||
if self.windows_watch_count == 0 then
|
||||
self.windows_watch_top = nil
|
||||
self.monitor:unwatch(directory)
|
||||
end
|
||||
end
|
||||
self.watched[directory] = nil
|
||||
elseif self.scanned[directory] then
|
||||
self.scanned[directory] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- designed to be run inside a coroutine.
|
||||
function dirwatch:check(change_callback, scan_time, wait_time)
|
||||
self.monitor:check(function(id)
|
||||
if PLATFORM == "Windows" then
|
||||
change_callback(common.dirname(self.windows_watch_top .. PATHSEP .. id))
|
||||
elseif self.reverse_watched[id] then
|
||||
change_callback(self.reverse_watched[id])
|
||||
end
|
||||
end)
|
||||
local start_time = system.get_time()
|
||||
for directory, old_modified in pairs(self.scanned) do
|
||||
if old_modified then
|
||||
local new_modified = system.get_file_info(directory).modified
|
||||
if old_modified < new_modified then
|
||||
change_callback(directory)
|
||||
self.scanned[directory] = new_modified
|
||||
end
|
||||
end
|
||||
if system.get_time() - start_time > scan_time then
|
||||
coroutine.yield(wait_time)
|
||||
start_time = system.get_time()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- inspect config.ignore_files patterns and prepare ready to use entries.
|
||||
local function compile_ignore_files()
|
||||
local ipatterns = config.ignore_files
|
||||
local compiled = {}
|
||||
-- config.ignore_files could be a simple string...
|
||||
if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end
|
||||
for i, pattern in ipairs(ipatterns) do
|
||||
-- we ignore malformed pattern that raise an error
|
||||
if pcall(string.match, "a", pattern) then
|
||||
table.insert(compiled, {
|
||||
use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end
|
||||
-- An '/' or '/$' at the end means we want to match a directory.
|
||||
match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value
|
||||
pattern = pattern -- get the actual pattern
|
||||
})
|
||||
end
|
||||
end
|
||||
return compiled
|
||||
end
|
||||
|
||||
|
||||
local function fileinfo_pass_filter(info, ignore_compiled)
|
||||
if info.size >= config.file_size_limit * 1e6 then return false end
|
||||
local basename = common.basename(info.filename)
|
||||
-- replace '\' with '/' for Windows where PATHSEP = '\'
|
||||
local fullname = "/" .. info.filename:gsub("\\", "/")
|
||||
for _, compiled in ipairs(ignore_compiled) do
|
||||
local test = compiled.use_path and fullname or basename
|
||||
if compiled.match_dir then
|
||||
if info.type == "dir" and string.match(test .. "/", compiled.pattern) then
|
||||
return false
|
||||
end
|
||||
else
|
||||
if string.match(test, compiled.pattern) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
local function compare_file(a, b)
|
||||
return a.filename < b.filename
|
||||
end
|
||||
|
||||
|
||||
-- compute a file's info entry completed with "filename" to be used
|
||||
-- in project scan or falsy if it shouldn't appear in the list.
|
||||
local function get_project_file_info(root, file, ignore_compiled)
|
||||
local info = system.get_file_info(root .. PATHSEP .. file)
|
||||
-- info can be not nil but info.type may be nil if is neither a file neither
|
||||
-- a directory, for example for /dev/* entries on linux.
|
||||
if info and info.type then
|
||||
info.filename = file
|
||||
return fileinfo_pass_filter(info, ignore_compiled) and info
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- "root" will by an absolute path without trailing '/'
|
||||
-- "path" will be a path starting without '/' 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 each item the "filename" will be the
|
||||
-- complete file path relative to "root" *without* the trailing '/', and without the starting '/'.
|
||||
function dirwatch.get_directory_files(dir, root, path, t, entries_count, recurse_pred)
|
||||
local t0 = system.get_time()
|
||||
local t_elapsed = system.get_time() - t0
|
||||
local dirs, files = {}, {}
|
||||
local ignore_compiled = compile_ignore_files()
|
||||
|
||||
|
||||
local all = system.list_dir(root .. PATHSEP .. path)
|
||||
if not all then return nil end
|
||||
|
||||
for _, file in ipairs(all or {}) do
|
||||
local info = get_project_file_info(root, (path ~= "" and (path .. PATHSEP) or "") .. file, ignore_compiled)
|
||||
if info then
|
||||
table.insert(info.type == "dir" and dirs or files, info)
|
||||
entries_count = entries_count + 1
|
||||
end
|
||||
end
|
||||
|
||||
local recurse_complete = true
|
||||
table.sort(dirs, compare_file)
|
||||
for _, f in ipairs(dirs) do
|
||||
table.insert(t, f)
|
||||
if recurse_pred(dir, f.filename, entries_count, t_elapsed) then
|
||||
local _, complete, n = dirwatch.get_directory_files(dir, root, f.filename, t, entries_count, recurse_pred)
|
||||
recurse_complete = recurse_complete and complete
|
||||
entries_count = n
|
||||
else
|
||||
recurse_complete = false
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(files, compare_file)
|
||||
for _, f in ipairs(files) do
|
||||
table.insert(t, f)
|
||||
end
|
||||
|
||||
return t, recurse_complete, entries_count
|
||||
end
|
||||
|
||||
|
||||
return dirwatch
|
|
@ -33,7 +33,6 @@ end
|
|||
function Doc:reset()
|
||||
self.lines = { "\n" }
|
||||
self.selections = { 1, 1, 1, 1 }
|
||||
self.cursor_clipboard = {}
|
||||
self.undo_stack = { idx = 1 }
|
||||
self.redo_stack = { idx = 1 }
|
||||
self.clean_change_id = 1
|
||||
|
@ -55,6 +54,7 @@ end
|
|||
function Doc:set_filename(filename, abs_filename)
|
||||
self.filename = filename
|
||||
self.abs_filename = abs_filename
|
||||
self:reset_syntax()
|
||||
end
|
||||
|
||||
|
||||
|
@ -85,6 +85,8 @@ function Doc:save(filename, abs_filename)
|
|||
assert(self.filename, "no filename set to default to")
|
||||
filename = self.filename
|
||||
abs_filename = self.abs_filename
|
||||
else
|
||||
assert(self.filename or abs_filename, "calling save on unnamed doc without absolute path")
|
||||
end
|
||||
local fp = assert( io.open(filename, "wb") )
|
||||
for _, line in ipairs(self.lines) do
|
||||
|
@ -94,7 +96,6 @@ function Doc:save(filename, abs_filename)
|
|||
fp:close()
|
||||
self:set_filename(filename, abs_filename)
|
||||
self.new_file = false
|
||||
self:reset_syntax()
|
||||
self:clean()
|
||||
end
|
||||
|
||||
|
@ -197,8 +198,14 @@ function Doc:add_selection(line1, col1, line2, col2, swap)
|
|||
self:set_selections(target, line1, col1, line2, col2, swap, 0)
|
||||
end
|
||||
|
||||
|
||||
function Doc:remove_selection(idx)
|
||||
common.splice(self.selections, (idx - 1) * 4 + 1, 4)
|
||||
end
|
||||
|
||||
|
||||
function Doc:set_selection(line1, col1, line2, col2, swap)
|
||||
self.selections, self.cursor_clipboard = {}, {}
|
||||
self.selections = {}
|
||||
self:set_selections(1, line1, col1, line2, col2, swap)
|
||||
end
|
||||
|
||||
|
@ -208,12 +215,10 @@ function Doc:merge_cursors(idx)
|
|||
if self.selections[i] == self.selections[j] and
|
||||
self.selections[i+1] == self.selections[j+1] then
|
||||
common.splice(self.selections, i, 4)
|
||||
common.splice(self.cursor_clipboard, i, 1)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if #self.selections <= 4 then self.cursor_clipboard = {} end
|
||||
end
|
||||
|
||||
local function selection_iterator(invariant, idx)
|
||||
|
|
|
@ -224,7 +224,6 @@ function DocView:scroll_to_make_visible(line, col)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
function DocView:on_mouse_moved(x, y, ...)
|
||||
DocView.super.on_mouse_moved(self, x, y, ...)
|
||||
|
||||
|
@ -284,13 +283,15 @@ 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
|
||||
local line1, col1, line2, col2 = self.doc:get_selection()
|
||||
if (line1 ~= self.last_line1 or col1 ~= self.last_col1 or
|
||||
line2 ~= self.last_line2 or col2 ~= self.last_col2) and self.size.x > 0 then
|
||||
if core.active_view == self then
|
||||
self:scroll_to_make_visible(line, col)
|
||||
self:scroll_to_make_visible(line1, col1)
|
||||
end
|
||||
core.blink_reset()
|
||||
self.last_line, self.last_col = line, col
|
||||
self.last_line1, self.last_col1 = line1, col1
|
||||
self.last_line2, self.last_col2 = line2, col2
|
||||
end
|
||||
|
||||
-- update blink timer
|
||||
|
@ -331,13 +332,22 @@ end
|
|||
function DocView:draw_line_body(idx, x, y)
|
||||
-- draw highlight if any selection ends on this line
|
||||
local draw_highlight = false
|
||||
local hcl = config.highlight_current_line
|
||||
if hcl ~= false then
|
||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do
|
||||
if line1 == idx then
|
||||
if hcl == "no_selection" then
|
||||
if (line1 ~= line2) or (col1 ~= col2) then
|
||||
draw_highlight = false
|
||||
break
|
||||
end
|
||||
end
|
||||
draw_highlight = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if draw_highlight and config.highlight_current_line and core.active_view == self then
|
||||
end
|
||||
if draw_highlight and core.active_view == self then
|
||||
self:draw_line_highlight(x + self.scroll.x, y)
|
||||
end
|
||||
|
||||
|
@ -369,9 +379,8 @@ function DocView:draw_line_gutter(idx, x, y, width)
|
|||
break
|
||||
end
|
||||
end
|
||||
local yoffset = self:get_line_text_y_offset()
|
||||
x = x + style.padding.x
|
||||
common.draw_text(self:get_font(), color, idx, "right", x, y + yoffset, width, self:get_line_height())
|
||||
common.draw_text(self:get_font(), color, idx, "right", x, y, width, self:get_line_height())
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -8,8 +8,18 @@ local function draw_text(x, y, color)
|
|||
local th = style.big_font:get_height()
|
||||
local dh = 2 * th + style.padding.y * 2
|
||||
local x1, y1 = x, y + (dh - th) / 2
|
||||
x = renderer.draw_text(style.big_font, "Lite XL", x1, y1, color)
|
||||
renderer.draw_text(style.font, "version " .. VERSION, x1, y1 + th, color)
|
||||
local xv = x1
|
||||
local title = "Lite XL"
|
||||
local version = "version " .. VERSION
|
||||
local title_width = style.big_font:get_width(title)
|
||||
local version_width = style.font:get_width(version)
|
||||
if version_width > title_width then
|
||||
version = VERSION
|
||||
version_width = style.font:get_width(version)
|
||||
xv = x1 - (version_width - title_width)
|
||||
end
|
||||
x = renderer.draw_text(style.big_font, title, x1, y1, color)
|
||||
renderer.draw_text(style.font, version, xv, y1 + th, color)
|
||||
x = x + style.padding.x
|
||||
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
|
||||
local lines = {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -215,6 +215,7 @@ keymap.add_direct {
|
|||
["ctrl+l"] = "doc:select-lines",
|
||||
["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
||||
["ctrl+/"] = "doc:toggle-line-comments",
|
||||
["ctrl+shift+/"] = "doc:toggle-block-comments",
|
||||
["ctrl+up"] = "doc:move-lines-up",
|
||||
["ctrl+down"] = "doc:move-lines-down",
|
||||
["ctrl+shift+d"] = "doc:duplicate-lines",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
|
||||
|
@ -36,12 +37,15 @@ local LogView = View:extend()
|
|||
|
||||
LogView.context = "session"
|
||||
|
||||
|
||||
function LogView:new()
|
||||
LogView.super.new(self)
|
||||
self.last_item = core.log_items[#core.log_items]
|
||||
self.expanding = {}
|
||||
self.scrollable = true
|
||||
self.yoffset = 0
|
||||
|
||||
core.status_view:show_message("i", style.text, "ctrl+click to copy entry")
|
||||
end
|
||||
|
||||
|
||||
|
@ -77,25 +81,30 @@ function LogView:each_item()
|
|||
end
|
||||
|
||||
|
||||
function LogView:on_mouse_moved(px, py, ...)
|
||||
LogView.super.on_mouse_moved(self, px, py, ...)
|
||||
local hovered = false
|
||||
for _, item, x, y, w, h in self:each_item() do
|
||||
function LogView:on_mouse_pressed(button, px, py, clicks)
|
||||
if LogView.super.on_mouse_pressed(self, button, px, py, clicks) then
|
||||
return true
|
||||
end
|
||||
|
||||
local index, selected
|
||||
for i, item, x, y, w, h in self:each_item() do
|
||||
if px >= x and py >= y and px < x + w and py < y + h then
|
||||
hovered = true
|
||||
self.hovered_item = item
|
||||
index = i
|
||||
selected = item
|
||||
break
|
||||
end
|
||||
end
|
||||
if not hovered then self.hovered_item = nil end
|
||||
end
|
||||
|
||||
|
||||
function LogView:on_mouse_pressed(button, mx, my, clicks)
|
||||
if LogView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end
|
||||
if self.hovered_item then
|
||||
self:expand_item(self.hovered_item)
|
||||
if selected then
|
||||
if keymap.modkeys["ctrl"] then
|
||||
system.set_clipboard(core.get_log(selected))
|
||||
core.status_view:show_message("i", style.text, "copied entry #"..index.." to clipboard.")
|
||||
else
|
||||
self:expand_item(selected)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
@ -131,21 +140,37 @@ local function draw_text_multiline(font, text, x, y, color)
|
|||
return resx, y
|
||||
end
|
||||
|
||||
|
||||
-- this is just to get a date string that's consistent
|
||||
local datestr = os.date()
|
||||
function LogView:draw()
|
||||
self:draw_background(style.background)
|
||||
|
||||
local th = style.font:get_height()
|
||||
local lh = th + style.padding.y -- for one line
|
||||
for _, item, x, y, w in self:each_item() do
|
||||
local iw = math.max(
|
||||
style.icon_font:get_width(style.log.ERROR.icon),
|
||||
style.icon_font:get_width(style.log.INFO.icon)
|
||||
)
|
||||
|
||||
local tw = style.font:get_width(datestr)
|
||||
for _, item, x, y, w, h in self:each_item() do
|
||||
core.push_clip_rect(x, y, w, h)
|
||||
x = x + style.padding.x
|
||||
|
||||
x = common.draw_text(
|
||||
style.icon_font,
|
||||
style.log[item.level].color,
|
||||
style.log[item.level].icon,
|
||||
"center",
|
||||
x, y, iw, lh
|
||||
)
|
||||
x = x + style.padding.x
|
||||
|
||||
-- timestamps are always 15% of the width
|
||||
local time = os.date(nil, item.time)
|
||||
x = common.draw_text(style.font, style.dim, time, "left", x, y, w, lh)
|
||||
x = x + style.padding.x
|
||||
common.draw_text(style.font, style.dim, time, "left", x, y, tw, lh)
|
||||
x = x + tw + style.padding.x
|
||||
|
||||
x = common.draw_text(style.code_font, style.dim, is_expanded(item) and "-" or "+", "left", x, y, w, lh)
|
||||
x = x + style.padding.x
|
||||
w = w - (x - self:get_content_offset())
|
||||
|
||||
if is_expanded(item) then
|
||||
|
@ -165,6 +190,8 @@ function LogView:draw()
|
|||
end
|
||||
_, y = common.draw_text(style.font, style.text, line, "left", x, y, w, lh)
|
||||
end
|
||||
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -16,8 +16,12 @@ local NagView = View:extend()
|
|||
function NagView:new()
|
||||
NagView.super.new(self)
|
||||
self.size.y = 0
|
||||
self.show_height = 0
|
||||
self.force_focus = false
|
||||
self.queue = {}
|
||||
self.scrollable = true
|
||||
self.target_height = 0
|
||||
self.on_mouse_pressed_root = nil
|
||||
end
|
||||
|
||||
function NagView:get_title()
|
||||
|
@ -46,20 +50,20 @@ function NagView:get_target_height()
|
|||
return self.target_height + 2 * style.padding.y
|
||||
end
|
||||
|
||||
function NagView:update()
|
||||
NagView.super.update(self)
|
||||
|
||||
if core.active_view == self and self.title then
|
||||
self:move_towards(self.size, "y", self:get_target_height())
|
||||
self:move_towards(self, "underline_progress", 1)
|
||||
function NagView:get_scrollable_size()
|
||||
local w, h = system.get_window_size()
|
||||
if self.visible and self:get_target_height() > h then
|
||||
self.size.y = h
|
||||
return self:get_target_height()
|
||||
else
|
||||
self:move_towards(self.size, "y", 0)
|
||||
self.size.y = 0
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function NagView:draw_overlay()
|
||||
function NagView:dim_window_content()
|
||||
local ox, oy = self:get_content_offset()
|
||||
oy = oy + self.size.y
|
||||
oy = oy + self.show_height
|
||||
local w, h = core.root_view.size.x, core.root_view.size.y - oy
|
||||
core.root_view:defer_draw(function()
|
||||
renderer.draw_rect(ox, oy, w, h, style.nagbar_dim)
|
||||
|
@ -81,7 +85,7 @@ function NagView:each_option()
|
|||
bh = self:get_buttons_height()
|
||||
ox,oy = self:get_content_offset()
|
||||
ox = ox + self.size.x
|
||||
oy = oy + self.size.y - bh - style.padding.y
|
||||
oy = oy + self.show_height - bh - style.padding.y
|
||||
|
||||
for i = #self.options, 1, -1 do
|
||||
opt = self.options[i]
|
||||
|
@ -94,6 +98,8 @@ function NagView:each_option()
|
|||
end
|
||||
|
||||
function NagView:on_mouse_moved(mx, my, ...)
|
||||
if not self.visible then return end
|
||||
core.set_active_view(self)
|
||||
NagView.super.on_mouse_moved(self, mx, my, ...)
|
||||
for i, _, x,y,w,h in self:each_option() do
|
||||
if mx >= x and my >= y and mx < x + w and my < y + h then
|
||||
|
@ -103,18 +109,55 @@ function NagView:on_mouse_moved(mx, my, ...)
|
|||
end
|
||||
end
|
||||
|
||||
local function register_mouse_pressed(self)
|
||||
if self.on_mouse_pressed_root then return end
|
||||
-- RootView is loaded locally to avoid NagView and RootView being
|
||||
-- mutually recursive
|
||||
local RootView = require "core.rootview"
|
||||
self.on_mouse_pressed_root = RootView.on_mouse_pressed
|
||||
local this = self
|
||||
function RootView:on_mouse_pressed(button, x, y, clicks)
|
||||
if
|
||||
not this:on_mouse_pressed(button, x, y, clicks)
|
||||
then
|
||||
return this.on_mouse_pressed_root(self, button, x, y, clicks)
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
self.new_on_mouse_pressed_root = RootView.on_mouse_pressed
|
||||
end
|
||||
|
||||
local function unregister_mouse_pressed(self)
|
||||
local RootView = require "core.rootview"
|
||||
if
|
||||
self.on_mouse_pressed_root
|
||||
and
|
||||
-- just in case prevent overwriting what something else may
|
||||
-- have overwrote after us, but after testing with various
|
||||
-- plugins this doesn't seems to happen, but just in case
|
||||
self.new_on_mouse_pressed_root == RootView.on_mouse_pressed
|
||||
then
|
||||
RootView.on_mouse_pressed = self.on_mouse_pressed_root
|
||||
self.on_mouse_pressed_root = nil
|
||||
self.new_on_mouse_pressed_root = nil
|
||||
end
|
||||
end
|
||||
|
||||
function NagView:on_mouse_pressed(button, mx, my, clicks)
|
||||
if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end
|
||||
if not self.visible then return false end
|
||||
if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return true end
|
||||
for i, _, x,y,w,h in self:each_option() do
|
||||
if mx >= x and my >= y and mx < x + w and my < y + h then
|
||||
self:change_hovered(i)
|
||||
command.perform "dialog:select"
|
||||
break
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function NagView:on_text_input(text)
|
||||
if not self.visible then return end
|
||||
if text:lower() == "y" then
|
||||
command.perform "dialog:select-yes"
|
||||
elseif text:lower() == "n" then
|
||||
|
@ -122,20 +165,39 @@ function NagView:on_text_input(text)
|
|||
end
|
||||
end
|
||||
|
||||
function NagView:update()
|
||||
if not self.visible and self.show_height <= 0 then return end
|
||||
NagView.super.update(self)
|
||||
|
||||
function NagView:draw()
|
||||
if self.size.y <= 0 or not self.title then return end
|
||||
if self.visible and core.active_view == self and self.title then
|
||||
self:move_towards(self, "show_height", self:get_target_height())
|
||||
self:move_towards(self, "underline_progress", 1)
|
||||
else
|
||||
self:move_towards(self, "show_height", 0)
|
||||
if self.show_height <= 0 then
|
||||
self.title = nil
|
||||
self.message = nil
|
||||
self.options = nil
|
||||
self.on_selected = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:draw_overlay()
|
||||
self:draw_background(style.nagbar)
|
||||
local function draw_nagview_message(self)
|
||||
self:dim_window_content()
|
||||
|
||||
-- draw message's background
|
||||
local ox, oy = self:get_content_offset()
|
||||
renderer.draw_rect(ox, oy, self.size.x, self.show_height, style.nagbar)
|
||||
|
||||
ox = ox + style.padding.x
|
||||
|
||||
core.push_clip_rect(ox, oy, self.size.x, self.show_height)
|
||||
|
||||
-- if there are other items, show it
|
||||
if #self.queue > 0 then
|
||||
local str = string.format("[%d]", #self.queue)
|
||||
ox = common.draw_text(style.font, style.nagbar_text, str, "left", ox, oy, self.size.x, self.size.y)
|
||||
ox = common.draw_text(style.font, style.nagbar_text, str, "left", ox, oy, self.size.x, self.show_height)
|
||||
ox = ox + style.padding.x
|
||||
end
|
||||
|
||||
|
@ -168,6 +230,17 @@ function NagView:draw()
|
|||
|
||||
common.draw_text(opt.font, style.nagbar_text, opt.text, "center", fx,fy,fw,fh)
|
||||
end
|
||||
|
||||
self:draw_scrollbar()
|
||||
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
||||
function NagView:draw()
|
||||
if (not self.visible and self.show_height <= 0) or not self.title then
|
||||
return
|
||||
end
|
||||
core.root_view:defer_draw(draw_nagview_message, self)
|
||||
end
|
||||
|
||||
function NagView:get_message_height()
|
||||
|
@ -178,23 +251,31 @@ function NagView:get_message_height()
|
|||
return h
|
||||
end
|
||||
|
||||
|
||||
function NagView:next()
|
||||
local opts = table.remove(self.queue, 1) or {}
|
||||
if opts.title and opts.message and opts.options then
|
||||
self.visible = true
|
||||
self.title = opts.title
|
||||
self.message = opts.message and opts.message .. "\n"
|
||||
self.options = opts.options
|
||||
self.on_selected = opts.on_selected
|
||||
if self.message and self.options then
|
||||
|
||||
local message_height = self:get_message_height()
|
||||
-- self.target_height is the nagview height needed to display the message and
|
||||
-- the buttons, excluding the top and bottom padding space.
|
||||
self.target_height = math.max(message_height, self:get_buttons_height())
|
||||
self:change_hovered(common.find_index(self.options, "default_yes"))
|
||||
|
||||
self.force_focus = true
|
||||
core.set_active_view(self)
|
||||
-- We add a hook to manage all the mouse_pressed events.
|
||||
register_mouse_pressed(self)
|
||||
else
|
||||
self.force_focus = false
|
||||
core.set_active_view(core.next_active_view or core.last_active_view)
|
||||
self.visible = false
|
||||
unregister_mouse_pressed(self)
|
||||
end
|
||||
self.force_focus = self.message ~= nil
|
||||
core.set_active_view(self.message ~= nil and self or
|
||||
core.next_active_view or core.last_active_view)
|
||||
end
|
||||
|
||||
function NagView:show(title, message, options, on_select)
|
||||
|
@ -204,7 +285,7 @@ function NagView:show(title, message, options, on_select)
|
|||
opts.options = assert(options, "No options")
|
||||
opts.on_selected = on_select or noop
|
||||
table.insert(self.queue, opts)
|
||||
if #self.queue > 0 and not self.title then self:next() end
|
||||
self:next()
|
||||
end
|
||||
|
||||
return NagView
|
||||
|
|
|
@ -260,8 +260,8 @@ end
|
|||
|
||||
local function close_button_location(x, w)
|
||||
local cw = style.icon_font:get_width("C")
|
||||
local pad = style.padding.y
|
||||
return x + w - pad - cw, cw, pad
|
||||
local pad = style.padding.x / 2
|
||||
return x + w - cw - pad, cw, pad
|
||||
end
|
||||
|
||||
|
||||
|
@ -476,51 +476,59 @@ function Node:update()
|
|||
end
|
||||
end
|
||||
|
||||
function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
|
||||
function Node:draw_tab_title(view, font, is_active, is_hovered, x, y, w, h)
|
||||
local text = view and view:get_name() or ""
|
||||
local dots_width = font:get_width("…")
|
||||
local align = "center"
|
||||
if font:get_width(text) > w then
|
||||
align = "left"
|
||||
for i = 1, #text do
|
||||
local reduced_text = text:sub(1, #text - i)
|
||||
if font:get_width(reduced_text) + dots_width <= w then
|
||||
text = reduced_text .. "…"
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
local color = style.dim
|
||||
if is_active then color = style.text end
|
||||
if is_hovered then color = style.text end
|
||||
common.draw_text(font, color, text, align, x, y, w, h)
|
||||
end
|
||||
|
||||
function Node:draw_tab_borders(view, is_active, is_hovered, x, y, w, h, standalone)
|
||||
-- Tabs deviders
|
||||
local ds = style.divider_size
|
||||
local dots_width = style.font:get_width("…")
|
||||
local color = style.dim
|
||||
local padding_y = style.padding.y
|
||||
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
|
||||
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y*2, style.dim)
|
||||
if standalone then
|
||||
renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2)
|
||||
end
|
||||
-- Full border
|
||||
if is_active then
|
||||
color = style.text
|
||||
renderer.draw_rect(x, y, w, h, style.background)
|
||||
renderer.draw_rect(x + w, y, ds, h, style.divider)
|
||||
renderer.draw_rect(x - ds, y, ds, h, style.divider)
|
||||
end
|
||||
local cx, cw, cspace = close_button_location(x, w)
|
||||
return x + ds, y, w - ds*2, h
|
||||
end
|
||||
|
||||
function Node:draw_tab(view, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
|
||||
x, y, w, h = self:draw_tab_borders(view, is_active, is_hovered, x, y, w, h, standalone)
|
||||
-- Close button
|
||||
local cx, cw, cpad = close_button_location(x, w)
|
||||
local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button)
|
||||
if show_close_button then
|
||||
local close_style = is_close_hovered and style.text or style.dim
|
||||
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
|
||||
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, cw, h)
|
||||
end
|
||||
if is_hovered then
|
||||
color = style.text
|
||||
end
|
||||
local padx = style.padding.x
|
||||
-- Normally we should substract "cspace" from text_avail_width and from the
|
||||
-- clipping width. It is the padding space we give to the left and right of the
|
||||
-- close button. However, since we are using dots to terminate filenames, we
|
||||
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
|
||||
-- close button.
|
||||
local text_avail_width = cx - x - padx
|
||||
core.push_clip_rect(x, y, cx - x, h)
|
||||
x, w = x + padx, w - padx * 2
|
||||
local align = "center"
|
||||
if style.font:get_width(text) > text_avail_width then
|
||||
align = "left"
|
||||
for i = 1, #text do
|
||||
local reduced_text = text:sub(1, #text - i)
|
||||
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
|
||||
text = reduced_text .. "…"
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
common.draw_text(style.font, color, text, align, x, y, w, h)
|
||||
-- Title
|
||||
x = x + cpad
|
||||
w = cx - x
|
||||
core.push_clip_rect(x, y, w, h)
|
||||
self:draw_tab_title(view, style.font, is_active, is_hovered, x, y, w, h)
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
||||
|
@ -547,7 +555,7 @@ function Node:draw_tabs()
|
|||
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
|
||||
local view = self.views[i]
|
||||
local x, y, w, h = self:get_tab_rect(i)
|
||||
self:draw_tab(view:get_name(), view == self.active_view,
|
||||
self:draw_tab(view, view == self.active_view,
|
||||
i == self.hovered_tab, i == self.hovered_close,
|
||||
x, y, w, h)
|
||||
end
|
||||
|
|
|
@ -30,7 +30,9 @@ end
|
|||
|
||||
|
||||
function RootView:get_active_node()
|
||||
return self.root_node:get_node_for_view(core.active_view)
|
||||
local node = self.root_node:get_node_for_view(core.active_view)
|
||||
if not node then node = self:get_primary_node() end
|
||||
return node
|
||||
end
|
||||
|
||||
|
||||
|
@ -46,6 +48,7 @@ end
|
|||
|
||||
function RootView:get_active_node_default()
|
||||
local node = self.root_node:get_node_for_view(core.active_view)
|
||||
if not node then node = self:get_primary_node() end
|
||||
if node.locked then
|
||||
local default_view = self:get_primary_node().views[1]
|
||||
assert(default_view, "internal error: cannot find original document node.")
|
||||
|
@ -269,6 +272,12 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
|||
end
|
||||
|
||||
|
||||
function RootView:on_file_dropped(filename, x, y)
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
return node and node.active_view:on_file_dropped(filename, x, y)
|
||||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_wheel(...)
|
||||
local x, y = self.mouse.x, self.mouse.y
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
|
@ -381,8 +390,8 @@ function RootView:draw_grabbed_tab()
|
|||
local _,_, w, h = dn.node:get_tab_rect(dn.idx)
|
||||
local x = self.mouse.x - w / 2
|
||||
local y = self.mouse.y - h / 2
|
||||
local text = dn.node.views[dn.idx] and dn.node.views[dn.idx]:get_name() or ""
|
||||
self.root_node:draw_tab(text, true, true, false, x, y, w, h, true)
|
||||
local view = dn.node.views[dn.idx]
|
||||
self.root_node:draw_tab(view, true, true, false, x, y, w, h, true)
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -12,8 +12,9 @@ else
|
|||
local prefix = EXEDIR:match("^(.+)[/\\]bin$")
|
||||
DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data')
|
||||
end
|
||||
USERDIR = (os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. "/lite-xl")
|
||||
or (HOME and (HOME .. '/.config/lite-xl') or (EXEDIR .. '/user'))
|
||||
USERDIR = (system.get_file_info(EXEDIR .. '/user') and (EXEDIR .. '/user'))
|
||||
or ((os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. "/lite-xl"))
|
||||
or (HOME and (HOME .. '/.config/lite-xl'))
|
||||
|
||||
package.path = DATADIR .. '/?.lua;' .. package.path
|
||||
package.path = DATADIR .. '/?/init.lua;' .. package.path
|
||||
|
@ -31,3 +32,5 @@ end }
|
|||
|
||||
table.pack = table.pack or pack or function(...) return {...} end
|
||||
table.unpack = table.unpack or unpack
|
||||
|
||||
bit32 = bit32 or require "core.bit"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -72,4 +72,9 @@ style.syntax["function"] = { common.color "#93DDFA" }
|
|||
style.syntax_fonts = {}
|
||||
-- style.syntax_fonts["comment"] = renderer.font.load(path_to_font, size_of_font, rendering_options)
|
||||
|
||||
style.log = {
|
||||
INFO = { icon = "i", color = style.text },
|
||||
ERROR = { icon = "!", color = style.error }
|
||||
}
|
||||
|
||||
return style
|
||||
|
|
|
@ -7,6 +7,11 @@ local plain_text_syntax = { name = "Plain Text", patterns = {}, symbols = {} }
|
|||
|
||||
|
||||
function syntax.add(t)
|
||||
-- this rule gives us a performance gain for the tokenizer in lines with
|
||||
-- long amounts of consecutive spaces without affecting other patterns
|
||||
if t.patterns then
|
||||
table.insert(t.patterns, 1, { pattern = "%s+", type = "normal" })
|
||||
end
|
||||
table.insert(syntax.items, t)
|
||||
end
|
||||
|
||||
|
|
|
@ -136,20 +136,42 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
|||
end
|
||||
|
||||
local function find_text(text, p, offset, at_start, close)
|
||||
local target, res = p.pattern or p.regex, { 1, offset - 1 }, p.regex
|
||||
local code = type(target) == "table" and target[close and 2 or 1] or target
|
||||
local target, res = p.pattern or p.regex, { 1, offset - 1 }
|
||||
local p_idx = close and 2 or 1
|
||||
local code = type(target) == "table" and target[p_idx] or target
|
||||
|
||||
if p.whole_line == nil then p.whole_line = { } end
|
||||
if p.whole_line[p_idx] == nil then
|
||||
-- Match patterns that start with '^'
|
||||
p.whole_line[p_idx] = code:match("^%^") and true or false
|
||||
if p.whole_line[p_idx] then
|
||||
-- Remove '^' from the beginning of the pattern
|
||||
if type(target) == "table" then
|
||||
target[p_idx] = code:sub(2)
|
||||
else
|
||||
p.pattern = p.pattern and code:sub(2)
|
||||
p.regex = p.regex and code:sub(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if p.regex and type(p.regex) ~= "table" then
|
||||
p._regex = p._regex or regex.compile(p.regex)
|
||||
code = p._regex
|
||||
end
|
||||
|
||||
repeat
|
||||
local next = res[2] + 1
|
||||
-- If the pattern contained '^', allow matching only the whole line
|
||||
if p.whole_line[p_idx] and next > 1 then
|
||||
return
|
||||
end
|
||||
-- go to the start of the next utf-8 character
|
||||
while text:byte(next) and common.is_utf8_cont(text, next) do
|
||||
next = next + 1
|
||||
end
|
||||
res = p.pattern and { text:find(at_start and "^" .. code or code, next) }
|
||||
or { regex.match(code, text, next, at_start and regex.ANCHORED or 0) }
|
||||
res = p.pattern and { text:find((at_start or p.whole_line[p_idx]) and "^" .. code or code, next) }
|
||||
or { regex.match(code, text, next, (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) }
|
||||
if res[1] and close and target[3] then
|
||||
local count = 0
|
||||
for i = res[1] - 1, 1, -1 do
|
||||
|
|
|
@ -25,7 +25,8 @@ function View:move_towards(t, k, dest, rate)
|
|||
return self:move_towards(self, t, k, dest, rate)
|
||||
end
|
||||
local val = t[k]
|
||||
if not config.transitions or math.abs(val - dest) < 0.5 then
|
||||
local diff = math.abs(val - dest)
|
||||
if not config.transitions or diff < 0.5 then
|
||||
t[k] = dest
|
||||
else
|
||||
rate = rate or 0.5
|
||||
|
@ -35,7 +36,7 @@ function View:move_towards(t, k, dest, rate)
|
|||
end
|
||||
t[k] = common.lerp(val, dest, rate)
|
||||
end
|
||||
if val ~= dest then
|
||||
if diff > 1e-8 then
|
||||
core.redraw = true
|
||||
end
|
||||
end
|
||||
|
@ -98,6 +99,11 @@ function View:on_mouse_moved(x, y, dx, dy)
|
|||
end
|
||||
|
||||
|
||||
function View:on_file_dropped(filename, x, y)
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function View:on_text_input(text)
|
||||
-- no-op
|
||||
end
|
||||
|
|
|
@ -10,14 +10,18 @@ local RootView = require "core.rootview"
|
|||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
config.plugins.autocomplete = {
|
||||
config.plugins.autocomplete = common.merge({
|
||||
-- Amount of characters that need to be written for autocomplete
|
||||
min_len = 3,
|
||||
-- The max amount of visible items
|
||||
max_height = 6,
|
||||
-- The max amount of scrollable items
|
||||
max_suggestions = 100,
|
||||
}
|
||||
-- Maximum amount of symbols to cache per document
|
||||
max_symbols = 4000,
|
||||
-- Font size of the description box
|
||||
desc_font_size = 12
|
||||
}, config.plugins.autocomplete)
|
||||
|
||||
local autocomplete = {}
|
||||
|
||||
|
@ -33,7 +37,7 @@ local triggered_manually = false
|
|||
|
||||
local mt = { __tostring = function(t) return t.text end }
|
||||
|
||||
function autocomplete.add(t, triggered_manually)
|
||||
function autocomplete.add(t, manually_triggered)
|
||||
local items = {}
|
||||
for text, info in pairs(t.items) do
|
||||
if type(info) == "table" then
|
||||
|
@ -44,7 +48,8 @@ function autocomplete.add(t, triggered_manually)
|
|||
text = text,
|
||||
info = info.info,
|
||||
desc = info.desc, -- Description shown on item selected
|
||||
cb = info.cb, -- A callback called once when item is selected
|
||||
onhover = info.onhover, -- A callback called once when item is hovered
|
||||
onselect = info.onselect, -- A callback called when item is selected
|
||||
data = info.data -- Optional data that can be used on cb
|
||||
},
|
||||
mt
|
||||
|
@ -56,7 +61,7 @@ function autocomplete.add(t, triggered_manually)
|
|||
end
|
||||
end
|
||||
|
||||
if not triggered_manually then
|
||||
if not manually_triggered then
|
||||
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
|
||||
else
|
||||
autocomplete.map_manually[t.name] = { files = t.files or ".*", items = items }
|
||||
|
@ -66,7 +71,7 @@ end
|
|||
--
|
||||
-- Thread that scans open document symbols and cache them
|
||||
--
|
||||
local max_symbols = config.max_symbols
|
||||
local max_symbols = config.plugins.autocomplete.max_symbols
|
||||
|
||||
core.add_thread(function()
|
||||
local cache = setmetatable({}, { __mode = "k" })
|
||||
|
@ -85,7 +90,9 @@ core.add_thread(function()
|
|||
doc.disable_symbols = true
|
||||
core.status_view:show_message("!", style.accent,
|
||||
"Too many symbols in document "..doc.filename..
|
||||
": stopping auto-complete for this document according to config.max_symbols.")
|
||||
": stopping auto-complete for this document according to "..
|
||||
"config.plugins.autocomplete.max_symbols."
|
||||
)
|
||||
collectgarbage('collect')
|
||||
return {}
|
||||
end
|
||||
|
@ -159,16 +166,6 @@ local function reset_suggestions()
|
|||
end
|
||||
end
|
||||
|
||||
local function in_table(value, table_array)
|
||||
for i, element in pairs(table_array) do
|
||||
if element == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function update_suggestions()
|
||||
local doc = core.active_view.doc
|
||||
local filename = doc and doc.filename or ""
|
||||
|
@ -199,6 +196,7 @@ local function update_suggestions()
|
|||
j = j + 1
|
||||
end
|
||||
end
|
||||
suggestions_idx = 1
|
||||
end
|
||||
|
||||
local function get_partial_symbol()
|
||||
|
@ -249,6 +247,11 @@ local function get_suggestions_rect(av)
|
|||
max_width = 150
|
||||
end
|
||||
|
||||
-- if portion not visiable to right, reposition to DocView right margin
|
||||
if (x - av.position.x) + max_width > av.size.x then
|
||||
x = (av.size.x + av.position.x) - max_width - (style.padding.x * 2)
|
||||
end
|
||||
|
||||
return
|
||||
x - style.padding.x,
|
||||
y - style.padding.y,
|
||||
|
@ -256,20 +259,99 @@ local function get_suggestions_rect(av)
|
|||
max_items * (th + style.padding.y) + style.padding.y
|
||||
end
|
||||
|
||||
local function wrap_line(line, max_chars)
|
||||
if #line > max_chars then
|
||||
local lines = {}
|
||||
local line_len = #line
|
||||
local new_line = ""
|
||||
local prev_char = ""
|
||||
local position = 0
|
||||
local indent = line:match("^%s+")
|
||||
for char in line:gmatch(".") do
|
||||
position = position + 1
|
||||
if #new_line < max_chars then
|
||||
new_line = new_line .. char
|
||||
prev_char = char
|
||||
if position >= line_len then
|
||||
table.insert(lines, new_line)
|
||||
end
|
||||
else
|
||||
if
|
||||
not prev_char:match("%s")
|
||||
and
|
||||
not string.sub(line, position+1, 1):match("%s")
|
||||
and
|
||||
position < line_len
|
||||
then
|
||||
new_line = new_line .. "-"
|
||||
end
|
||||
table.insert(lines, new_line)
|
||||
if indent then
|
||||
new_line = indent .. char
|
||||
else
|
||||
new_line = char
|
||||
end
|
||||
end
|
||||
end
|
||||
return lines
|
||||
end
|
||||
return line
|
||||
end
|
||||
|
||||
local previous_scale = SCALE
|
||||
local desc_font = style.code_font:copy(
|
||||
config.plugins.autocomplete.desc_font_size * SCALE
|
||||
)
|
||||
local function draw_description_box(text, av, sx, sy, sw, sh)
|
||||
if previous_scale ~= SCALE then
|
||||
desc_font = style.code_font:copy(
|
||||
config.plugins.autocomplete.desc_font_size * SCALE
|
||||
)
|
||||
previous_scale = SCALE
|
||||
end
|
||||
|
||||
local font = desc_font
|
||||
local lh = font:get_height()
|
||||
local y = sy + style.padding.y
|
||||
local x = sx + sw + style.padding.x / 4
|
||||
local width = 0
|
||||
local char_width = font:get_width(" ")
|
||||
local draw_left = false;
|
||||
|
||||
local max_chars = 0
|
||||
if sx - av.position.x < av.size.x - (sx - av.position.x) - sw then
|
||||
max_chars = (((av.size.x+av.position.x) - x) / char_width) - 5
|
||||
else
|
||||
draw_left = true;
|
||||
max_chars = (
|
||||
(sx - av.position.x - (style.padding.x / 4) - style.scrollbar_size)
|
||||
/ char_width
|
||||
) - 5
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
for line in string.gmatch(text.."\n", "(.-)\n") do
|
||||
width = math.max(width, style.font:get_width(line))
|
||||
local wrapper_lines = wrap_line(line, max_chars)
|
||||
if type(wrapper_lines) == "table" then
|
||||
for _, wrapped_line in pairs(wrapper_lines) do
|
||||
width = math.max(width, font:get_width(wrapped_line))
|
||||
table.insert(lines, wrapped_line)
|
||||
end
|
||||
else
|
||||
width = math.max(width, font:get_width(line))
|
||||
table.insert(lines, line)
|
||||
end
|
||||
end
|
||||
|
||||
local height = #lines * style.font:get_height()
|
||||
if draw_left then
|
||||
x = sx - (style.padding.x / 4) - width - (style.padding.x * 2)
|
||||
end
|
||||
|
||||
local height = #lines * font:get_height()
|
||||
|
||||
-- draw background rect
|
||||
renderer.draw_rect(
|
||||
sx + sw + style.padding.x / 4,
|
||||
x,
|
||||
sy,
|
||||
width + style.padding.x * 2,
|
||||
height + style.padding.y * 2,
|
||||
|
@ -277,13 +359,10 @@ local function draw_description_box(text, av, sx, sy, sw, sh)
|
|||
)
|
||||
|
||||
-- draw text
|
||||
local lh = style.font:get_height()
|
||||
local y = sy + style.padding.y
|
||||
local x = sx + sw + style.padding.x / 4
|
||||
|
||||
for _, line in pairs(lines) do
|
||||
common.draw_text(
|
||||
style.font, style.text, line, "left", x + style.padding.x, y, width, lh
|
||||
font, style.text, line, "left",
|
||||
x + style.padding.x, y, width, lh
|
||||
)
|
||||
y = y + lh
|
||||
end
|
||||
|
@ -320,10 +399,9 @@ local function draw_suggestions_box(av)
|
|||
end
|
||||
y = y + lh
|
||||
if suggestions_idx == i then
|
||||
if s.cb then
|
||||
s.cb(suggestions_idx, s)
|
||||
s.cb = nil
|
||||
s.data = nil
|
||||
if s.onhover then
|
||||
s.onhover(suggestions_idx, s)
|
||||
s.onhover = nil
|
||||
end
|
||||
if s.desc and #s.desc > 0 then
|
||||
draw_description_box(s.desc, av, rx, ry, rw, rh)
|
||||
|
@ -487,10 +565,17 @@ command.add(predicate, {
|
|||
["autocomplete:complete"] = function()
|
||||
local doc = core.active_view.doc
|
||||
local line, col = doc:get_selection()
|
||||
local text = suggestions[suggestions_idx].text
|
||||
local item = suggestions[suggestions_idx]
|
||||
local text = item.text
|
||||
local inserted = false
|
||||
if item.onselect then
|
||||
inserted = item.onselect(suggestions_idx, item)
|
||||
end
|
||||
if not inserted then
|
||||
doc:insert(line, col, text)
|
||||
doc:remove(line, col, line, col - #partial)
|
||||
doc:set_selection(line, col + #text - #partial)
|
||||
end
|
||||
reset_suggestions()
|
||||
end,
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ local core = require "core"
|
|||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
local ContextMenu = require "core.contextmenu"
|
||||
local DocView = require "core.docview"
|
||||
local RootView = require "core.rootview"
|
||||
|
||||
local menu = ContextMenu()
|
||||
|
@ -32,7 +33,7 @@ function RootView:draw(...)
|
|||
menu:draw()
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
command.add(function() return getmetatable(core.active_view) == DocView end, {
|
||||
["context:show"] = function()
|
||||
menu:show(core.active_view.position.x, core.active_view.position.y)
|
||||
end
|
||||
|
@ -42,23 +43,24 @@ keymap.add {
|
|||
["menu"] = "context:show"
|
||||
}
|
||||
|
||||
local function copy_log()
|
||||
local item = core.active_view.hovered_item
|
||||
if item then
|
||||
system.set_clipboard(core.get_log(item))
|
||||
end
|
||||
end
|
||||
|
||||
local function open_as_doc()
|
||||
local doc = core.open_doc("logs.txt")
|
||||
core.root_view:open_doc(doc)
|
||||
doc:insert(1, 1, core.get_log())
|
||||
end
|
||||
|
||||
menu:register("core.logview", {
|
||||
{ text = "Copy entry", command = copy_log },
|
||||
{ text = "Open as file", command = open_as_doc }
|
||||
command.add(function() return menu.show_context_menu == true end, {
|
||||
["context:focus-previous"] = function()
|
||||
menu:focus_previous()
|
||||
end,
|
||||
["context:focus-next"] = function()
|
||||
menu:focus_next()
|
||||
end,
|
||||
["context:hide"] = function()
|
||||
menu:hide()
|
||||
end,
|
||||
["context:on-selected"] = function()
|
||||
menu:call_selected_item()
|
||||
end,
|
||||
})
|
||||
keymap.add { ["return"] = "context:on-selected" }
|
||||
keymap.add { ["up"] = "context:focus-previous" }
|
||||
keymap.add { ["down"] = "context:focus-next" }
|
||||
keymap.add { ["escape"] = "context:hide" }
|
||||
|
||||
if require("plugins.scale") then
|
||||
menu:register("core.docview", {
|
||||
|
|
|
@ -3,93 +3,254 @@ local core = require "core"
|
|||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local core_syntax = require "core.syntax"
|
||||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
local tokenizer = require "core.tokenizer"
|
||||
|
||||
local cache = setmetatable({}, { __mode = "k" })
|
||||
local comments_cache = {}
|
||||
local auto_detect_max_lines = 150
|
||||
|
||||
|
||||
local function add_to_stat(stat, val)
|
||||
for i = 1, #stat do
|
||||
if val == stat[i][1] then
|
||||
stat[i][2] = stat[i][2] + 1
|
||||
return
|
||||
local function indent_occurrences_more_than_once(stat, idx)
|
||||
if stat[idx-1] and stat[idx-1] == stat[idx] then
|
||||
return true
|
||||
elseif stat[idx+1] and stat[idx+1] == stat[idx] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
stat[#stat + 1] = {val, 1}
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local function optimal_indent_from_stat(stat)
|
||||
if #stat == 0 then return nil, 0 end
|
||||
local bins = {}
|
||||
for k = 1, #stat do
|
||||
local indent = stat[k][1]
|
||||
table.sort(stat, function(a, b) return a > b end)
|
||||
local best_indent = 0
|
||||
local best_score = 0
|
||||
local count = #stat
|
||||
for x=1, count do
|
||||
local indent = stat[x]
|
||||
local score = 0
|
||||
local mult_prev, lines_prev
|
||||
for i = k, #stat do
|
||||
if stat[i][1] % indent == 0 then
|
||||
local mult = stat[i][1] / indent
|
||||
if not mult_prev or (mult_prev + 1 == mult and lines_prev / stat[i][2] > 0.1) then
|
||||
-- we add the number of lines to the score only if the previous
|
||||
-- multiple of "indent" was populated with enough lines.
|
||||
score = score + stat[i][2]
|
||||
end
|
||||
mult_prev, lines_prev = mult, stat[i][2]
|
||||
for y=1, count do
|
||||
if y ~= x and stat[y] % indent == 0 then
|
||||
score = score + 1
|
||||
elseif
|
||||
indent > stat[y]
|
||||
and
|
||||
indent_occurrences_more_than_once(stat, y)
|
||||
then
|
||||
score = 0
|
||||
break
|
||||
end
|
||||
end
|
||||
bins[#bins + 1] = {indent, score}
|
||||
if score > best_score then
|
||||
best_indent = indent
|
||||
best_score = score
|
||||
end
|
||||
table.sort(bins, function(a, b) return a[2] > b[2] end)
|
||||
return bins[1][1], bins[1][2]
|
||||
if score > 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
return best_score > 0 and best_indent or nil, best_score
|
||||
end
|
||||
|
||||
|
||||
-- return nil if it is a comment or blank line or the initial part of the
|
||||
-- line otherwise.
|
||||
-- we don't need to have the whole line to detect indentation.
|
||||
local function get_first_line_part(tokens)
|
||||
local i, n = 1, #tokens
|
||||
while i + 1 <= n do
|
||||
local ttype, ttext = tokens[i], tokens[i + 1]
|
||||
if ttype ~= "comment" and ttext:gsub("%s+", "") ~= "" then
|
||||
return ttext
|
||||
local function escape_comment_tokens(token)
|
||||
local special_chars = "*-%[].()+?^$"
|
||||
local escaped = ""
|
||||
for x=1, token:len() do
|
||||
local found = false
|
||||
for y=1, special_chars:len() do
|
||||
if token:sub(x, x) == special_chars:sub(y, y) then
|
||||
escaped = escaped .. "%" .. token:sub(x, x)
|
||||
found = true
|
||||
break
|
||||
end
|
||||
i = i + 2
|
||||
end
|
||||
if not found then
|
||||
escaped = escaped .. token:sub(x, x)
|
||||
end
|
||||
end
|
||||
return escaped
|
||||
end
|
||||
|
||||
|
||||
local function get_comment_patterns(syntax)
|
||||
if comments_cache[syntax] then
|
||||
if #comments_cache[syntax] > 0 then
|
||||
return comments_cache[syntax]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
local comments = {}
|
||||
for idx=1, #syntax.patterns do
|
||||
local pattern = syntax.patterns[idx]
|
||||
local startp = ""
|
||||
if
|
||||
type(pattern.type) == "string"
|
||||
and
|
||||
(pattern.type == "comment" or pattern.type == "string")
|
||||
then
|
||||
local not_is_string = pattern.type ~= "string"
|
||||
if pattern.pattern then
|
||||
startp = type(pattern.pattern) == "table"
|
||||
and pattern.pattern[1] or pattern.pattern
|
||||
if not_is_string and startp:sub(1, 1) ~= "^" then
|
||||
startp = "^%s*" .. startp
|
||||
elseif not_is_string then
|
||||
startp = "^%s*" .. startp:sub(2, startp:len())
|
||||
end
|
||||
if type(pattern.pattern) == "table" then
|
||||
table.insert(comments, {"p", startp, pattern.pattern[2]})
|
||||
elseif not_is_string then
|
||||
table.insert(comments, {"p", startp})
|
||||
end
|
||||
elseif pattern.regex then
|
||||
startp = type(pattern.regex) == "table"
|
||||
and pattern.regex[1] or pattern.regex
|
||||
if not_is_string and startp:sub(1, 1) ~= "^" then
|
||||
startp = "^\\s*" .. startp
|
||||
elseif not_is_string then
|
||||
startp = "^\\s*" .. startp:sub(2, startp:len())
|
||||
end
|
||||
if type(pattern.regex) == "table" then
|
||||
table.insert(comments, {
|
||||
"r", regex.compile(startp), regex.compile(pattern.regex[2])
|
||||
})
|
||||
elseif not_is_string then
|
||||
table.insert(comments, {"r", regex.compile(startp)})
|
||||
end
|
||||
end
|
||||
elseif pattern.syntax then
|
||||
local subsyntax = type(pattern.syntax) == 'table' and pattern.syntax
|
||||
or core_syntax.get("file"..pattern.syntax, "")
|
||||
local sub_comments = get_comment_patterns(subsyntax)
|
||||
if sub_comments then
|
||||
for s=1, #sub_comments do
|
||||
table.insert(comments, sub_comments[s])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if #comments == 0 then
|
||||
local single_line_comment = syntax.comment
|
||||
and escape_comment_tokens(syntax.comment) or nil
|
||||
local block_comment = nil
|
||||
if syntax.block_comment then
|
||||
block_comment = {
|
||||
escape_comment_tokens(syntax.block_comment[1]),
|
||||
escape_comment_tokens(syntax.block_comment[2])
|
||||
}
|
||||
end
|
||||
if single_line_comment then
|
||||
table.insert(comments, {"p", "^%s*" .. single_line_comment})
|
||||
end
|
||||
if block_comment then
|
||||
table.insert(comments, {"p", "^%s*" .. block_comment[1], block_comment[2]})
|
||||
end
|
||||
end
|
||||
comments_cache[syntax] = comments
|
||||
if #comments > 0 then
|
||||
return comments
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
local function get_non_empty_lines(syntax, lines)
|
||||
return coroutine.wrap(function()
|
||||
local tokens, state
|
||||
local comments = get_comment_patterns(syntax)
|
||||
|
||||
local i = 0
|
||||
local end_regex = nil
|
||||
local end_pattern = nil
|
||||
local inside_comment = false
|
||||
for _, line in ipairs(lines) do
|
||||
tokens, state = tokenizer.tokenize(syntax, line, state)
|
||||
local line_start = get_first_line_part(tokens)
|
||||
if line_start then
|
||||
if line:gsub("^%s+", "") ~= "" then
|
||||
local is_comment = false
|
||||
if comments then
|
||||
if not inside_comment then
|
||||
for c=1, #comments do
|
||||
local comment = comments[c]
|
||||
if comment[1] == "p" then
|
||||
if comment[3] then
|
||||
local start, ending = line:find(comment[2])
|
||||
if start then
|
||||
if not line:find(comment[3], ending+1) then
|
||||
is_comment = true
|
||||
inside_comment = true
|
||||
end_pattern = comment[3]
|
||||
end
|
||||
break
|
||||
end
|
||||
elseif line:find(comment[2]) then
|
||||
is_comment = true
|
||||
break
|
||||
end
|
||||
else
|
||||
if comment[3] then
|
||||
local start, ending = regex.match(
|
||||
comment[2], line, 1, regex.ANCHORED
|
||||
)
|
||||
if start then
|
||||
if not regex.match(
|
||||
comment[3], line, ending+1, regex.ANCHORED
|
||||
)
|
||||
then
|
||||
is_comment = true
|
||||
inside_comment = true
|
||||
end_regex = comment[3]
|
||||
end
|
||||
break
|
||||
end
|
||||
elseif regex.match(comment[2], line, 1, regex.ANCHORED) then
|
||||
is_comment = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif end_pattern and line:find(end_pattern) then
|
||||
is_comment = true
|
||||
inside_comment = false
|
||||
end_pattern = nil
|
||||
elseif end_regex and regex.match(end_regex, line) then
|
||||
is_comment = true
|
||||
inside_comment = false
|
||||
end_regex = nil
|
||||
end
|
||||
end
|
||||
if
|
||||
not is_comment
|
||||
and
|
||||
not inside_comment
|
||||
then
|
||||
i = i + 1
|
||||
coroutine.yield(i, line_start)
|
||||
coroutine.yield(i, line)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local auto_detect_max_lines = 100
|
||||
|
||||
local function detect_indent_stat(doc)
|
||||
local stat = {}
|
||||
local tab_count = 0
|
||||
local runs = 1
|
||||
local max_lines = auto_detect_max_lines
|
||||
for i, text in get_non_empty_lines(doc.syntax, doc.lines) do
|
||||
local str = text:match("^ %s+%S")
|
||||
if str then add_to_stat(stat, #str - 1) end
|
||||
local str = text:match("^\t+")
|
||||
if str then tab_count = tab_count + 1 end
|
||||
local spaces = text:match("^ +")
|
||||
if spaces then table.insert(stat, spaces:len()) end
|
||||
local tabs = text:match("^\t+")
|
||||
if tabs then tab_count = tab_count + 1 end
|
||||
-- if nothing found for first lines try at least 4 more times
|
||||
if i == max_lines and runs < 5 and #stat == 0 and tab_count == 0 then
|
||||
max_lines = max_lines + auto_detect_max_lines
|
||||
runs = runs + 1
|
||||
-- Stop parsing when files is very long. Not needed for euristic determination.
|
||||
if i > auto_detect_max_lines then break end
|
||||
elseif i > max_lines then break end
|
||||
end
|
||||
table.sort(stat, function(a, b) return a[1] < b[1] end)
|
||||
local indent, score = optimal_indent_from_stat(stat)
|
||||
if tab_count > score then
|
||||
return "hard", config.indent_size, tab_count
|
||||
|
@ -101,7 +262,7 @@ end
|
|||
|
||||
local function update_cache(doc)
|
||||
local type, size, score = detect_indent_stat(doc)
|
||||
local score_threshold = 4
|
||||
local score_threshold = 2
|
||||
if score < score_threshold then
|
||||
-- use default values
|
||||
type = config.tab_type
|
||||
|
@ -130,9 +291,11 @@ end
|
|||
|
||||
local function set_indent_type(doc, type)
|
||||
local _, indent_size = doc:get_indent_info()
|
||||
cache[doc] = {type = type,
|
||||
cache[doc] = {
|
||||
type = type,
|
||||
size = indent_size,
|
||||
confirmed = true}
|
||||
confirmed = true
|
||||
}
|
||||
doc.indent_info = cache[doc]
|
||||
end
|
||||
|
||||
|
@ -158,9 +321,11 @@ end
|
|||
|
||||
local function set_indent_size(doc, size)
|
||||
local indent_type = doc:get_indent_info()
|
||||
cache[doc] = {type = indent_type,
|
||||
cache[doc] = {
|
||||
type = indent_type,
|
||||
size = size,
|
||||
confirmed = true}
|
||||
confirmed = true
|
||||
}
|
||||
doc.indent_info = cache[doc]
|
||||
end
|
||||
|
||||
|
@ -168,14 +333,14 @@ local function set_indent_size_command()
|
|||
core.command_view:enter(
|
||||
"Specify indent size for current file",
|
||||
function(value) -- submit
|
||||
local value = math.floor(tonumber(value))
|
||||
value = math.floor(tonumber(value))
|
||||
local doc = core.active_view.doc
|
||||
set_indent_size(doc, value)
|
||||
end,
|
||||
nil, -- suggest
|
||||
nil, -- cancel
|
||||
function(value) -- validate
|
||||
local value = tonumber(value)
|
||||
value = tonumber(value)
|
||||
return value ~= nil and value >= 1
|
||||
end
|
||||
)
|
||||
|
@ -187,20 +352,24 @@ command.add("core.docview", {
|
|||
["indent:set-file-indent-size"] = set_indent_size_command
|
||||
})
|
||||
|
||||
|
||||
command.add(function()
|
||||
command.add(
|
||||
function()
|
||||
return core.active_view:is(DocView)
|
||||
and cache[core.active_view.doc]
|
||||
and cache[core.active_view.doc].type == "soft"
|
||||
end, {
|
||||
["indent:switch-file-to-tabs-indentation"] = function() set_indent_type(core.active_view.doc, "hard") end
|
||||
["indent:switch-file-to-tabs-indentation"] = function()
|
||||
set_indent_type(core.active_view.doc, "hard")
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
command.add(function()
|
||||
command.add(
|
||||
function()
|
||||
return core.active_view:is(DocView)
|
||||
and cache[core.active_view.doc]
|
||||
and cache[core.active_view.doc].type == "hard"
|
||||
end, {
|
||||
["indent:switch-file-to-spaces-indentation"] = function() set_indent_type(core.active_view.doc, "soft") end
|
||||
["indent:switch-file-to-spaces-indentation"] = function()
|
||||
set_indent_type(core.active_view.doc, "soft")
|
||||
end
|
||||
})
|
||||
|
|
|
@ -3,8 +3,9 @@ local syntax = require "core.syntax"
|
|||
|
||||
syntax.add {
|
||||
name = "C",
|
||||
files = { "%.c$", "%.h$", "%.inl$" },
|
||||
files = { "%.c$" },
|
||||
comment = "//",
|
||||
block_comment = { "/*", "*/" },
|
||||
patterns = {
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
|
@ -16,10 +17,21 @@ syntax.add {
|
|||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{
|
||||
pattern = "^%s*#define%s+()[%a_][%a%d_]*",
|
||||
type = { "keyword", "symbol" }
|
||||
},
|
||||
-- Uppercase constants of at least 2 chars in len
|
||||
{
|
||||
pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%(%)%?%^%%=/<>~|&;:,!]",
|
||||
type = "number"
|
||||
},
|
||||
-- Magic constants
|
||||
{ pattern = "__[%u%l]+__", type = "number" },
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ pattern = "#include%s()<.->", type = {"keyword", "string"} },
|
||||
{ pattern = "#[%a_][%w_]*", type = "keyword" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
},
|
||||
symbols = {
|
||||
["if"] = "keyword",
|
||||
|
@ -44,6 +56,8 @@ syntax.add {
|
|||
["case"] = "keyword",
|
||||
["default"] = "keyword",
|
||||
["auto"] = "keyword",
|
||||
["struct"] = "keyword",
|
||||
["union"] = "keyword",
|
||||
["void"] = "keyword2",
|
||||
["int"] = "keyword2",
|
||||
["short"] = "keyword2",
|
||||
|
@ -60,6 +74,7 @@ syntax.add {
|
|||
["#if"] = "keyword",
|
||||
["#ifdef"] = "keyword",
|
||||
["#ifndef"] = "keyword",
|
||||
["#elif"] = "keyword",
|
||||
["#else"] = "keyword",
|
||||
["#elseif"] = "keyword",
|
||||
["#endif"] = "keyword",
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
-- mod-version:2 -- lite-xl 2.0
|
||||
pcall(require, "plugins.language_c")
|
||||
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
|
@ -10,6 +8,7 @@ syntax.add {
|
|||
"%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$"
|
||||
},
|
||||
comment = "//",
|
||||
block_comment = { "/*", "*/" },
|
||||
patterns = {
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
|
@ -18,20 +17,45 @@ syntax.add {
|
|||
{ pattern = "0x%x+", type = "number" },
|
||||
{ pattern = "%d+[%d%.eE]*f?", type = "number" },
|
||||
{ pattern = "%.?%d+f?", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{
|
||||
pattern = "^%s*#define%s+()[%a_][%a%d_]*",
|
||||
type = { "keyword", "symbol" }
|
||||
},
|
||||
{
|
||||
pattern = "#include%s+()<.->",
|
||||
type = { "keyword", "string" }
|
||||
},
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|:&]", type = "operator" },
|
||||
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "class%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "namespace%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "[%a_][%w_]*::", type = "symbol" },
|
||||
{ pattern = "::", type = "symbol" },
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
-- Match scope operator element access
|
||||
{ pattern = "[%a_][%w_]*[%s]*%f[:]", type = "literal" },
|
||||
-- Uppercase constants of at least 2 chars in len
|
||||
{
|
||||
pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%(%)%?%^%%=/<>~|&;:,!]",
|
||||
type = "number"
|
||||
},
|
||||
-- Magic constants
|
||||
{ pattern = "__[%u%l]+__", type = "number" },
|
||||
-- Somehow makes macros properly work
|
||||
{ pattern = "#[%a_][%w_]*", type = "normal" },
|
||||
-- Everything else to make the tokenizer work properly
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ pattern = "#include%s()<.->", type = {"keyword", "string"} },
|
||||
{ pattern = "#[%a_][%w_]*", type = "keyword" },
|
||||
},
|
||||
symbols = {
|
||||
["alignof"] = "keyword",
|
||||
["alignas"] = "keyword",
|
||||
["and"] = "keyword",
|
||||
["and_eq"] = "keyword",
|
||||
["not"] = "keyword",
|
||||
["not_eq"] = "keyword",
|
||||
["or"] = "keyword",
|
||||
["or_eq"] = "keyword",
|
||||
["xor"] = "keyword",
|
||||
["xor_eq"] = "keyword",
|
||||
["private"] = "keyword",
|
||||
["protected"] = "keyword",
|
||||
["public"] = "keyword",
|
||||
|
@ -39,9 +63,12 @@ syntax.add {
|
|||
["nullptr"] = "keyword",
|
||||
["operator"] = "keyword",
|
||||
["asm"] = "keyword",
|
||||
["bitand"] = "keyword",
|
||||
["bitor"] = "keyword",
|
||||
["catch"] = "keyword",
|
||||
["throw"] = "keyword",
|
||||
["try"] = "keyword",
|
||||
["class"] = "keyword",
|
||||
["compl"] = "keyword",
|
||||
["explicit"] = "keyword",
|
||||
["export"] = "keyword",
|
||||
|
@ -63,7 +90,6 @@ syntax.add {
|
|||
["co_yield"] = "keyword",
|
||||
["decltype"] = "keyword",
|
||||
["delete"] = "keyword",
|
||||
["export"] = "keyword",
|
||||
["friend"] = "keyword",
|
||||
["typeid"] = "keyword",
|
||||
["typename"] = "keyword",
|
||||
|
@ -71,6 +97,7 @@ syntax.add {
|
|||
["override"] = "keyword",
|
||||
["virtual"] = "keyword",
|
||||
["using"] = "keyword",
|
||||
["namespace"] = "keyword",
|
||||
["new"] = "keyword",
|
||||
["noexcept"] = "keyword",
|
||||
["if"] = "keyword",
|
||||
|
@ -84,6 +111,8 @@ syntax.add {
|
|||
["continue"] = "keyword",
|
||||
["return"] = "keyword",
|
||||
["goto"] = "keyword",
|
||||
["struct"] = "keyword",
|
||||
["union"] = "keyword",
|
||||
["typedef"] = "keyword",
|
||||
["enum"] = "keyword",
|
||||
["extern"] = "keyword",
|
||||
|
@ -95,7 +124,6 @@ syntax.add {
|
|||
["case"] = "keyword",
|
||||
["default"] = "keyword",
|
||||
["auto"] = "keyword",
|
||||
["const"] = "keyword",
|
||||
["void"] = "keyword2",
|
||||
["int"] = "keyword2",
|
||||
["short"] = "keyword2",
|
||||
|
@ -105,12 +133,18 @@ syntax.add {
|
|||
["char"] = "keyword2",
|
||||
["unsigned"] = "keyword2",
|
||||
["bool"] = "keyword2",
|
||||
["true"] = "keyword2",
|
||||
["false"] = "keyword2",
|
||||
["true"] = "literal",
|
||||
["false"] = "literal",
|
||||
["NULL"] = "literal",
|
||||
["wchar_t"] = "keyword2",
|
||||
["char8_t"] = "keyword2",
|
||||
["char16_t"] = "keyword2",
|
||||
["char32_t"] = "keyword2",
|
||||
["#include"] = "keyword",
|
||||
["#if"] = "keyword",
|
||||
["#ifdef"] = "keyword",
|
||||
["#ifndef"] = "keyword",
|
||||
["#elif"] = "keyword",
|
||||
["#else"] = "keyword",
|
||||
["#elseif"] = "keyword",
|
||||
["#endif"] = "keyword",
|
||||
|
@ -120,4 +154,3 @@ syntax.add {
|
|||
["#pragma"] = "keyword",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ local syntax = require "core.syntax"
|
|||
syntax.add {
|
||||
name = "CSS",
|
||||
files = { "%.css$" },
|
||||
block_comment = { "/*", "*/" },
|
||||
patterns = {
|
||||
{ pattern = "\\.", type = "normal" },
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
|
|
|
@ -4,6 +4,7 @@ local syntax = require "core.syntax"
|
|||
syntax.add {
|
||||
name = "HTML",
|
||||
files = { "%.html?$" },
|
||||
block_comment = { "<!--", "-->" },
|
||||
patterns = {
|
||||
{
|
||||
pattern = {
|
||||
|
|
|
@ -5,6 +5,7 @@ syntax.add {
|
|||
name = "JavaScript",
|
||||
files = { "%.js$", "%.json$", "%.cson$" },
|
||||
comment = "//",
|
||||
block_comment = { "/*", "*/" },
|
||||
patterns = {
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
|
|
|
@ -6,6 +6,7 @@ syntax.add {
|
|||
files = "%.lua$",
|
||||
headers = "^#!.*[ /]lua",
|
||||
comment = "--",
|
||||
block_comment = { "--[[", "]]" },
|
||||
patterns = {
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
|
|
|
@ -1,25 +1,104 @@
|
|||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
local style = require "core.style"
|
||||
local core = require "core"
|
||||
|
||||
local initial_color = style.syntax["keyword2"]
|
||||
|
||||
-- Add 3 type of font styles for use on markdown files
|
||||
for _, attr in pairs({"bold", "italic", "bold_italic"}) do
|
||||
local attributes = {}
|
||||
if attr ~= "bold_italic" then
|
||||
attributes[attr] = true
|
||||
else
|
||||
attributes["bold"] = true
|
||||
attributes["italic"] = true
|
||||
end
|
||||
-- no way to copy user custom font with additional attributes :(
|
||||
style.syntax_fonts["markdown_"..attr] = renderer.font.load(
|
||||
DATADIR .. "/fonts/JetBrainsMono-Regular.ttf",
|
||||
style.code_font:get_size(),
|
||||
attributes
|
||||
)
|
||||
-- also add a color for it
|
||||
style.syntax["markdown_"..attr] = style.syntax["keyword2"]
|
||||
end
|
||||
|
||||
local in_squares_match = "^%[%]"
|
||||
local in_parenthesis_match = "^%(%)"
|
||||
|
||||
syntax.add {
|
||||
name = "Markdown",
|
||||
files = { "%.md$", "%.markdown$" },
|
||||
block_comment = { "<!--", "-->" },
|
||||
patterns = {
|
||||
{ pattern = "\\.", type = "normal" },
|
||||
---- HTML rules imported and adapted from language_html
|
||||
---- to not conflict with markdown rules
|
||||
-- Inline JS and CSS
|
||||
{
|
||||
pattern = {
|
||||
"<%s*[sS][cC][rR][iI][pP][tT]%s+[tT][yY][pP][eE]%s*=%s*" ..
|
||||
"['\"]%a+/[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]['\"]%s*>",
|
||||
"<%s*/[sS][cC][rR][iI][pP][tT]>"
|
||||
},
|
||||
syntax = ".js",
|
||||
type = "function"
|
||||
},
|
||||
{
|
||||
pattern = {
|
||||
"<%s*[sS][cC][rR][iI][pP][tT]%s*>",
|
||||
"<%s*/%s*[sS][cC][rR][iI][pP][tT]>"
|
||||
},
|
||||
syntax = ".js",
|
||||
type = "function"
|
||||
},
|
||||
{
|
||||
pattern = {
|
||||
"<%s*[sS][tT][yY][lL][eE][^>]*>",
|
||||
"<%s*/%s*[sS][tT][yY][lL][eE]%s*>"
|
||||
},
|
||||
syntax = ".css",
|
||||
type = "function"
|
||||
},
|
||||
-- Comments
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
-- Tags
|
||||
{ pattern = "%f[^<]![%a_][%w_]*", type = "keyword2" },
|
||||
{ pattern = "%f[^<][%a_][%w_]*", type = "function" },
|
||||
{ pattern = "%f[^<]/[%a_][%w_]*", type = "function" },
|
||||
-- Attributes
|
||||
{
|
||||
pattern = "[a-z%-]+%s*()=%s*()\".-\"",
|
||||
type = { "keyword", "operator", "string" }
|
||||
},
|
||||
{
|
||||
pattern = "[a-z%-]+%s*()=%s*()'.-'",
|
||||
type = { "keyword", "operator", "string" }
|
||||
},
|
||||
{
|
||||
pattern = "[a-z%-]+%s*()=%s*()%-?%d[%d%.]*",
|
||||
type = { "keyword", "operator", "number" }
|
||||
},
|
||||
-- Entities
|
||||
{ pattern = "&#?[a-zA-Z0-9]+;", type = "keyword2" },
|
||||
|
||||
---- Markdown rules
|
||||
-- code blocks
|
||||
{ pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" },
|
||||
{ pattern = { "```cpp", "```" }, type = "string", syntax = ".cpp" },
|
||||
{ pattern = { "```python", "```" }, type = "string", syntax = ".py" },
|
||||
{ pattern = { "```ruby", "```" }, type = "string", syntax = ".rb" },
|
||||
{ pattern = { "```perl", "```" }, type = "string", syntax = ".pl" },
|
||||
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
|
||||
{ pattern = { "```javascript", "```" }, type = "string", syntax = ".js" },
|
||||
{ pattern = { "```json", "```" }, type = "string", syntax = ".js" },
|
||||
{ pattern = { "```html", "```" }, type = "string", syntax = ".html" },
|
||||
{ pattern = { "```ini", "```" }, type = "string", syntax = ".ini" },
|
||||
{ pattern = { "```xml", "```" }, type = "string", syntax = ".xml" },
|
||||
{ pattern = { "```css", "```" }, type = "string", syntax = ".css" },
|
||||
{ pattern = { "```lua", "```" }, type = "string", syntax = ".lua" },
|
||||
{ pattern = { "```bash", "```" }, type = "string", syntax = ".sh" },
|
||||
{ pattern = { "```sh", "```" }, type = "string", syntax = ".sh" },
|
||||
{ pattern = { "```java", "```" }, type = "string", syntax = ".java" },
|
||||
{ pattern = { "```c#", "```" }, type = "string", syntax = ".cs" },
|
||||
{ pattern = { "```cmake", "```" }, type = "string", syntax = ".cmake" },
|
||||
|
@ -32,7 +111,6 @@ syntax.add {
|
|||
{ pattern = { "```v", "```" }, type = "string", syntax = ".v" },
|
||||
{ pattern = { "```toml", "```" }, type = "string", syntax = ".toml" },
|
||||
{ pattern = { "```yaml", "```" }, type = "string", syntax = ".yaml" },
|
||||
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
|
||||
{ pattern = { "```nim", "```" }, type = "string", syntax = ".nim" },
|
||||
{ pattern = { "```typescript", "```" }, type = "string", syntax = ".ts" },
|
||||
{ pattern = { "```rescript", "```" }, type = "string", syntax = ".res" },
|
||||
|
@ -41,16 +119,105 @@ syntax.add {
|
|||
{ pattern = { "```lobster", "```" }, type = "string", syntax = ".lobster" },
|
||||
{ pattern = { "```liquid", "```" }, type = "string", syntax = ".liquid" },
|
||||
{ pattern = { "```", "```" }, type = "string" },
|
||||
{ pattern = { "``", "``", "\\" }, type = "string" },
|
||||
{ pattern = { "`", "`", "\\" }, type = "string" },
|
||||
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
|
||||
{ pattern = "%-%-%-+", type = "comment" },
|
||||
{ pattern = "%*%s+", type = "operator" },
|
||||
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
|
||||
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
|
||||
{ pattern = "#.-\n", type = "keyword" },
|
||||
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
|
||||
{ pattern = "https?://%S+", type = "function" },
|
||||
{ pattern = { "``", "``" }, type = "string" },
|
||||
{ pattern = { "%f[\\`]%`[%S]", "`" }, type = "string" },
|
||||
-- strike
|
||||
{ pattern = { "~~", "~~" }, type = "keyword2" },
|
||||
-- highlight
|
||||
{ pattern = { "==", "==" }, type = "literal" },
|
||||
-- lines
|
||||
{ pattern = "^%-%-%-+\n", type = "comment" },
|
||||
{ pattern = "^%*%*%*+\n", type = "comment" },
|
||||
{ pattern = "^___+\n", type = "comment" },
|
||||
-- bullets
|
||||
{ pattern = "^%s*%*%s", type = "number" },
|
||||
{ pattern = "^%s*%-%s", type = "number" },
|
||||
{ pattern = "^%s*%+%s", type = "number" },
|
||||
-- numbered bullet
|
||||
{ pattern = "^%s*[0-9]+%.%s", type = "number" },
|
||||
-- blockquote
|
||||
{ pattern = "^%s*>+%s", type = "string" },
|
||||
-- bold and italic
|
||||
{ pattern = { "%*%*%*%S", "%*%*%*" }, type = "markdown_bold_italic" },
|
||||
{ pattern = { "%*%*%S", "%*%*" }, type = "markdown_bold" },
|
||||
-- handle edge case where asterisk can be at end of line and not close
|
||||
{
|
||||
pattern = { "%f[\\%*]%*[%S]", "%*%f[^%*]" },
|
||||
type = "markdown_italic"
|
||||
},
|
||||
-- alternative bold italic formats
|
||||
{ pattern = "^___[%s%p%w]+___%s" , type = "markdown_bold_italic" },
|
||||
{ pattern = "^__[%s%p%w]+__%s" , type = "markdown_bold" },
|
||||
{ pattern = "^_[%s%p%w]+_%s" , type = "markdown_italic" },
|
||||
{ pattern = { "%s___", "___%f[%s]" }, type = "markdown_bold_italic" },
|
||||
{ pattern = { "%s__", "__%f[%s]" }, type = "markdown_bold" },
|
||||
{ pattern = { "%s_[%S]", "_%f[%s]" }, type = "markdown_italic" },
|
||||
-- heading with custom id
|
||||
{
|
||||
pattern = "^#+%s[%w%s%p]+(){()#[%w%-]+()}",
|
||||
type = { "keyword", "function", "string", "function" }
|
||||
},
|
||||
-- headings
|
||||
{ pattern = "^#+%s.+\n", type = "keyword" },
|
||||
-- superscript and subscript
|
||||
{
|
||||
pattern = "%^()%d+()%^",
|
||||
type = { "function", "number", "function" }
|
||||
},
|
||||
{
|
||||
pattern = "%~()%d+()%~",
|
||||
type = { "function", "number", "function" }
|
||||
},
|
||||
-- definitions
|
||||
{ pattern = "^:%s.+", type = "function" },
|
||||
-- emoji
|
||||
{ pattern = ":[a-zA-Z0-9_%-]+:", type = "literal" },
|
||||
-- images and link
|
||||
{
|
||||
pattern = "!?%[!?%[()["..in_squares_match.."]+()%]%(()["..in_parenthesis_match.."]+()%)%]%(()["..in_parenthesis_match.."]+()%)",
|
||||
type = { "function", "string", "function", "number", "function", "number", "function" }
|
||||
},
|
||||
{
|
||||
pattern = "!?%[!?%[?()["..in_squares_match.."]+()%]?%]%(()["..in_parenthesis_match.."]+()%)",
|
||||
type = { "function", "string", "function", "number", "function" }
|
||||
},
|
||||
-- reference links
|
||||
{
|
||||
pattern = "%[()["..in_squares_match.."]+()%] *()%[()["..in_squares_match.."]+()%]",
|
||||
type = { "function", "string", "function", "function", "number", "function" }
|
||||
},
|
||||
{
|
||||
pattern = "^%s*%[%^()["..in_squares_match.."]+()%]: ",
|
||||
type = { "function", "number", "function" }
|
||||
},
|
||||
{
|
||||
pattern = "^%s*%[%^?()["..in_squares_match.."]+()%]:%s+.+\n",
|
||||
type = { "function", "number", "function" }
|
||||
},
|
||||
{
|
||||
pattern = "!?%[%^?()["..in_squares_match.."]+()%]",
|
||||
type = { "function", "number", "function" }
|
||||
},
|
||||
-- url's and email
|
||||
{
|
||||
pattern = "<[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+%.[a-zA-Z0-9-.]+>",
|
||||
type = "function"
|
||||
},
|
||||
{ pattern = "<https?://%S+>", type = "function" },
|
||||
{ pattern = "https?://%S+", type = "function" }
|
||||
},
|
||||
symbols = { },
|
||||
}
|
||||
|
||||
-- Adjust the color on theme changes
|
||||
core.add_thread(function()
|
||||
while true do
|
||||
if initial_color ~= style.syntax["keyword2"] then
|
||||
for _, attr in pairs({"bold", "italic", "bold_italic"}) do
|
||||
style.syntax["markdown_"..attr] = style.syntax["keyword2"]
|
||||
end
|
||||
initial_color = style.syntax["keyword2"]
|
||||
end
|
||||
coroutine.yield(1)
|
||||
end
|
||||
end)
|
||||
|
|
|
@ -5,6 +5,7 @@ syntax.add {
|
|||
name = "XML",
|
||||
files = { "%.xml$" },
|
||||
headers = "<%?xml",
|
||||
block_comment = { "<!--", "-->" },
|
||||
patterns = {
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
{ pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" },
|
||||
|
|
|
@ -176,7 +176,7 @@ function ResultsView:draw()
|
|||
local text
|
||||
if self.searching then
|
||||
if files_number then
|
||||
text = string.format("Searching %d%% (%d of %d files, %d matches) for %q...",
|
||||
text = string.format("Searching %.f%% (%d of %d files, %d matches) for %q...",
|
||||
per * 100, self.last_file_idx, files_number,
|
||||
#self.results, self.query)
|
||||
else
|
||||
|
@ -229,8 +229,21 @@ local function begin_search(text, fn)
|
|||
end
|
||||
|
||||
|
||||
local function set_command_view_text()
|
||||
local view = core.active_view
|
||||
local doc = (view and view.doc) and view.doc or nil
|
||||
if doc then
|
||||
core.command_view:set_text(
|
||||
doc:get_text(table.unpack({ doc:get_selection() })),
|
||||
true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
command.add(nil, {
|
||||
["project-search:find"] = function()
|
||||
set_command_view_text()
|
||||
core.command_view:enter("Find Text In Project", function(text)
|
||||
text = text:lower()
|
||||
begin_search(text, function(line_text)
|
||||
|
@ -249,6 +262,7 @@ command.add(nil, {
|
|||
end,
|
||||
|
||||
["project-search:fuzzy-find"] = function()
|
||||
set_command_view_text()
|
||||
core.command_view:enter("Fuzzy Find Text In Project", function(text)
|
||||
begin_search(text, function(line_text)
|
||||
return common.fuzzy_match(line_text, text) and 1
|
||||
|
|
|
@ -8,10 +8,10 @@ local style = require "core.style"
|
|||
local RootView = require "core.rootview"
|
||||
local CommandView = require "core.commandview"
|
||||
|
||||
config.plugins.scale = {
|
||||
config.plugins.scale = common.merge({
|
||||
mode = "code",
|
||||
use_mousewheel = true
|
||||
}
|
||||
}, config.plugins.scale)
|
||||
|
||||
local scale_steps = 0.05
|
||||
|
||||
|
@ -50,12 +50,8 @@ local function set_scale(scale)
|
|||
style.code_font = renderer.font.copy(style.code_font, s * style.code_font:get_size())
|
||||
end
|
||||
|
||||
for _, font in pairs(style.syntax_fonts) do
|
||||
renderer.font.set_size(font, s * font:get_size())
|
||||
end
|
||||
|
||||
for _, font in pairs(style.syntax_fonts) do
|
||||
renderer.font.set_size(font, s * font:get_size())
|
||||
for name, font in pairs(style.syntax_fonts) do
|
||||
style.syntax_fonts[name] = renderer.font.copy(font, s * font:get_size())
|
||||
end
|
||||
|
||||
-- restore scroll positions
|
||||
|
|
|
@ -84,11 +84,12 @@ end
|
|||
|
||||
function ToolbarView:on_mouse_pressed(button, x, y, clicks)
|
||||
local caught = ToolbarView.super.on_mouse_pressed(self, button, x, y, clicks)
|
||||
if caught then return end
|
||||
if caught then return caught end
|
||||
core.set_active_view(core.last_active_view)
|
||||
if self.hovered_item then
|
||||
command.perform(self.hovered_item.command)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -8,9 +8,15 @@ local style = require "core.style"
|
|||
local View = require "core.view"
|
||||
local ContextMenu = require "core.contextmenu"
|
||||
local RootView = require "core.rootview"
|
||||
local CommandView = require "core.commandview"
|
||||
|
||||
config.plugins.treeview = common.merge({
|
||||
-- Amount of clicks to open a file
|
||||
clicks_to_open = 2,
|
||||
-- Default treeview width
|
||||
size = 200 * SCALE
|
||||
}, config.plugins.treeview)
|
||||
|
||||
local default_treeview_size = 200 * SCALE
|
||||
local tooltip_offset = style.font:get_height()
|
||||
local tooltip_border = 1
|
||||
local tooltip_delay = 0.5
|
||||
|
@ -39,19 +45,26 @@ function TreeView:new()
|
|||
self.scrollable = true
|
||||
self.visible = true
|
||||
self.init_size = true
|
||||
self.target_size = default_treeview_size
|
||||
self.target_size = config.plugins.treeview.size
|
||||
self.cache = {}
|
||||
self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 }
|
||||
self.cursor_pos = { x = 0, y = 0 }
|
||||
|
||||
self.item_icon_width = 0
|
||||
self.item_text_spacing = 0
|
||||
|
||||
local on_dirmonitor_modify = core.on_dirmonitor_modify
|
||||
function core.on_dirmonitor_modify(dir, filepath)
|
||||
if self.cache[dir.name] then
|
||||
self.cache[dir.name][filepath] = nil
|
||||
end
|
||||
on_dirmonitor_modify(dir, filepath)
|
||||
self:add_core_hooks()
|
||||
end
|
||||
|
||||
|
||||
function TreeView:add_core_hooks()
|
||||
-- When a file or directory is deleted we delete the corresponding cache entry
|
||||
-- because if the entry is recreated we may use wrong information from cache.
|
||||
local on_delete = core.on_dirmonitor_delete
|
||||
core.on_dirmonitor_delete = function(dir, filepath)
|
||||
local cache = self.cache[dir.name]
|
||||
if cache then cache[filepath] = nil end
|
||||
on_delete(dir, filepath)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -90,7 +103,7 @@ function TreeView:get_cached(dir, item, dirname)
|
|||
end
|
||||
t.name = basename
|
||||
t.type = item.type
|
||||
t.dir = dir -- points to top level "dir" item
|
||||
t.dir_name = dir.name -- points to top level "dir" item
|
||||
dir_cache[cache_name] = t
|
||||
end
|
||||
return t
|
||||
|
@ -170,6 +183,21 @@ function TreeView:each_item()
|
|||
end
|
||||
|
||||
|
||||
function TreeView:set_selection(selection, selection_y)
|
||||
self.selected_item = selection
|
||||
if selection and selection_y
|
||||
and (selection_y <= 0 or selection_y >= self.size.y) then
|
||||
|
||||
local lh = self:get_item_height()
|
||||
if selection_y >= self.size.y - lh then
|
||||
selection_y = selection_y - self.size.y + lh
|
||||
end
|
||||
local _, y = self:get_content_offset()
|
||||
self.scroll.to.y = selection and (selection_y - y)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function TreeView:get_text_bounding_box(item, x, y, w, h)
|
||||
local icon_width = style.icon_font:get_width("D")
|
||||
local xoffset = item.depth * style.padding.x + style.padding.x + icon_width
|
||||
|
@ -180,8 +208,14 @@ end
|
|||
|
||||
|
||||
function TreeView:on_mouse_moved(px, py, ...)
|
||||
if not self.visible then return end
|
||||
TreeView.super.on_mouse_moved(self, px, py, ...)
|
||||
if self.dragging_scrollbar then return end
|
||||
self.cursor_pos.x = px
|
||||
self.cursor_pos.y = py
|
||||
if self.dragging_scrollbar then
|
||||
self.hovered_item = nil
|
||||
return
|
||||
end
|
||||
|
||||
local item_changed, tooltip_changed
|
||||
for item, x,y,w,h in self:each_item() do
|
||||
|
@ -217,32 +251,28 @@ end
|
|||
|
||||
|
||||
function TreeView:on_mouse_pressed(button, x, y, clicks)
|
||||
if not self.visible then return end
|
||||
local caught = TreeView.super.on_mouse_pressed(self, button, x, y, clicks)
|
||||
if caught or button ~= "left" then
|
||||
return true
|
||||
end
|
||||
local hovered_item = self.hovered_item
|
||||
if not hovered_item then
|
||||
return false
|
||||
elseif hovered_item.type == "dir" then
|
||||
|
||||
if self.hovered_item then
|
||||
self:set_selection(self.hovered_item)
|
||||
|
||||
if keymap.modkeys["ctrl"] and button == "left" then
|
||||
create_directory_in(hovered_item)
|
||||
else
|
||||
hovered_item.expanded = not hovered_item.expanded
|
||||
if hovered_item.dir.files_limit then
|
||||
core.update_project_subdir(hovered_item.dir, hovered_item.filename, hovered_item.expanded)
|
||||
core.project_subdir_set_show(hovered_item.dir, hovered_item.filename, hovered_item.expanded)
|
||||
end
|
||||
create_directory_in(self.selected_item)
|
||||
elseif self.selected_item.type == "dir"
|
||||
or (self.selected_item.type == "file"
|
||||
and clicks == config.plugins.treeview.clicks_to_open
|
||||
)
|
||||
then
|
||||
command.perform "treeview:open"
|
||||
end
|
||||
else
|
||||
core.try(function()
|
||||
if core.last_active_view and core.active_view == self then
|
||||
core.set_active_view(core.last_active_view)
|
||||
end
|
||||
local doc_filename = core.normalize_to_project_dir(hovered_item.abs_filename)
|
||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
||||
end)
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
@ -257,6 +287,8 @@ function TreeView:update()
|
|||
self:move_towards(self.size, "x", dest)
|
||||
end
|
||||
|
||||
if not self.visible then return end
|
||||
|
||||
local duration = system.get_time() - self.tooltip.begin
|
||||
if self.hovered_item and self.tooltip.x and duration > tooltip_delay then
|
||||
self:move_towards(self.tooltip, "alpha", tooltip_alpha, tooltip_alpha_rate)
|
||||
|
@ -267,6 +299,13 @@ function TreeView:update()
|
|||
self.item_icon_width = style.icon_font:get_width("D")
|
||||
self.item_text_spacing = style.icon_font:get_width("f") / 2
|
||||
|
||||
-- this will make sure hovered_item is updated
|
||||
-- we don't want events when the thing is scrolling fast
|
||||
local dy = math.abs(self.scroll.to.y - self.scroll.y)
|
||||
if self.scroll.to.y ~= 0 and dy < self:get_item_height() then
|
||||
self:on_mouse_moved(self.cursor_pos.x, self.cursor_pos.y, 0, 0)
|
||||
end
|
||||
|
||||
TreeView.super.update(self)
|
||||
end
|
||||
|
||||
|
@ -350,6 +389,10 @@ end
|
|||
|
||||
function TreeView:draw_item_background(item, active, hovered, x, y, w, h)
|
||||
if hovered then
|
||||
local hover_color = { table.unpack(style.line_highlight) }
|
||||
hover_color[4] = 160
|
||||
renderer.draw_rect(x, y, w, h, hover_color)
|
||||
elseif active then
|
||||
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
||||
end
|
||||
end
|
||||
|
@ -366,6 +409,7 @@ end
|
|||
|
||||
|
||||
function TreeView:draw()
|
||||
if not self.visible then return end
|
||||
self:draw_background(style.background2)
|
||||
local _y, _h = self.position.y, self.size.y
|
||||
|
||||
|
@ -375,18 +419,46 @@ function TreeView:draw()
|
|||
for item, x,y,w,h in self:each_item() do
|
||||
if y + h >= _y and y < _y + _h then
|
||||
self:draw_item(item,
|
||||
item.abs_filename == active_filename,
|
||||
item == self.selected_item,
|
||||
item == self.hovered_item,
|
||||
x, y, w, h)
|
||||
end
|
||||
end
|
||||
|
||||
self:draw_scrollbar()
|
||||
if self.hovered_item and self.tooltip.alpha > 0 then
|
||||
if self.hovered_item and self.tooltip.x and self.tooltip.alpha > 0 then
|
||||
core.root_view:defer_draw(self.draw_tooltip, self)
|
||||
end
|
||||
end
|
||||
|
||||
function TreeView:get_parent(item)
|
||||
local parent_path = common.dirname(item.abs_filename)
|
||||
if not parent_path then return end
|
||||
for it, _, y in self:each_item() do
|
||||
if it.abs_filename == parent_path then
|
||||
return it, y
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function TreeView:toggle_expand(toggle)
|
||||
local item = self.selected_item
|
||||
|
||||
if not item then return end
|
||||
|
||||
if item.type == "dir" then
|
||||
if type(toggle) == "boolean" then
|
||||
item.expanded = toggle
|
||||
else
|
||||
item.expanded = not item.expanded
|
||||
end
|
||||
local hovered_dir = core.project_dir_by_name(item.dir_name)
|
||||
if hovered_dir and hovered_dir.files_limit then
|
||||
core.update_project_subdir(hovered_dir, item.filename, item.expanded)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- init
|
||||
local view = TreeView()
|
||||
|
@ -405,7 +477,7 @@ if config.plugins.toolbarview ~= false and toolbar_plugin then
|
|||
toolbar_view = ToolbarView()
|
||||
treeview_node:split("down", toolbar_view, {y = true})
|
||||
local min_toolbar_width = toolbar_view:get_min_width()
|
||||
view:set_target_size("x", math.max(default_treeview_size, min_toolbar_width))
|
||||
view:set_target_size("x", math.max(config.plugins.treeview.size, min_toolbar_width))
|
||||
command.add(nil, {
|
||||
["toolbar:toggle"] = function()
|
||||
toolbar_view:toggle_visible()
|
||||
|
@ -446,6 +518,12 @@ function RootView:draw(...)
|
|||
menu:draw()
|
||||
end
|
||||
|
||||
local on_quit_project = core.on_quit_project
|
||||
function core.on_quit_project()
|
||||
view.cache = {}
|
||||
on_quit_project()
|
||||
end
|
||||
|
||||
local function is_project_folder(path)
|
||||
return core.project_dir == path
|
||||
end
|
||||
|
@ -476,65 +554,131 @@ menu:register(
|
|||
}
|
||||
)
|
||||
|
||||
local previous_view = nil
|
||||
|
||||
-- Register the TreeView commands and keymap
|
||||
command.add(nil, {
|
||||
["treeview:toggle"] = function()
|
||||
view.visible = not view.visible
|
||||
end})
|
||||
end,
|
||||
|
||||
|
||||
command.add(function() return view.hovered_item ~= nil end, {
|
||||
["treeview:rename"] = function()
|
||||
local old_filename = view.hovered_item.filename
|
||||
local old_abs_filename = view.hovered_item.abs_filename
|
||||
core.command_view:set_text(old_filename)
|
||||
core.command_view:enter("Rename", function(filename)
|
||||
filename = core.normalize_to_project_dir(filename)
|
||||
local abs_filename = core.project_absolute_path(filename)
|
||||
local res, err = os.rename(old_abs_filename, abs_filename)
|
||||
if res then -- successfully renamed
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if doc.abs_filename and old_abs_filename == doc.abs_filename then
|
||||
doc:set_filename(filename, abs_filename) -- make doc point to the new filename
|
||||
doc:reset_syntax()
|
||||
break -- only first needed
|
||||
end
|
||||
end
|
||||
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
||||
["treeview:toggle-focus"] = function()
|
||||
if not core.active_view:is(TreeView) then
|
||||
if core.active_view:is(CommandView) then
|
||||
previous_view = core.last_active_view
|
||||
else
|
||||
core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err)
|
||||
previous_view = core.active_view
|
||||
end
|
||||
end, common.path_suggest)
|
||||
if not previous_view then
|
||||
previous_view = core.root_view:get_primary_node().active_view
|
||||
end
|
||||
core.set_active_view(view)
|
||||
if not view.selected_item then
|
||||
for it, _, y in view:each_item() do
|
||||
view:set_selection(it, y)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
core.set_active_view(
|
||||
previous_view or core.root_view:get_primary_node().active_view
|
||||
)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
command.add(TreeView, {
|
||||
["treeview:next"] = function()
|
||||
local item = view.selected_item
|
||||
local item_y
|
||||
local stop = false
|
||||
for it, _, y in view:each_item() do
|
||||
if item == it then
|
||||
stop = true
|
||||
elseif stop then
|
||||
item = it
|
||||
item_y = y
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
view:set_selection(item, item_y)
|
||||
end,
|
||||
|
||||
["treeview:new-file"] = function()
|
||||
if not is_project_folder(view.hovered_item.abs_filename) then
|
||||
core.command_view:set_text(view.hovered_item.filename .. "/")
|
||||
["treeview:previous"] = function()
|
||||
local last_item
|
||||
local last_item_y
|
||||
for it, _, y in view:each_item() do
|
||||
if it == view.selected_item then
|
||||
if not last_item then
|
||||
last_item = it
|
||||
last_item_y = y
|
||||
end
|
||||
core.command_view:enter("Filename", function(filename)
|
||||
local doc_filename = core.project_dir .. PATHSEP .. filename
|
||||
local file = io.open(doc_filename, "a+")
|
||||
file:write("")
|
||||
file:close()
|
||||
break
|
||||
end
|
||||
last_item = it
|
||||
last_item_y = y
|
||||
end
|
||||
|
||||
view:set_selection(last_item, last_item_y)
|
||||
end,
|
||||
|
||||
["treeview:open"] = function()
|
||||
local item = view.selected_item
|
||||
if not item then return end
|
||||
if item.type == "dir" then
|
||||
view:toggle_expand()
|
||||
else
|
||||
core.try(function()
|
||||
if core.last_active_view and core.active_view == view then
|
||||
core.set_active_view(core.last_active_view)
|
||||
end
|
||||
local doc_filename = core.normalize_to_project_dir(item.abs_filename)
|
||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
||||
core.log("Created %s", doc_filename)
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
["treeview:new-folder"] = function()
|
||||
if not is_project_folder(view.hovered_item.abs_filename) then
|
||||
core.command_view:set_text(view.hovered_item.filename .. "/")
|
||||
end)
|
||||
end
|
||||
core.command_view:enter("Folder Name", function(filename)
|
||||
local dir_path = core.project_dir .. PATHSEP .. filename
|
||||
common.mkdirp(dir_path)
|
||||
core.log("Created %s", dir_path)
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
["treeview:deselect"] = function()
|
||||
view.selected_item = nil
|
||||
end,
|
||||
|
||||
["treeview:collapse"] = function()
|
||||
if view.selected_item then
|
||||
if view.selected_item.type == "dir" and view.selected_item.expanded then
|
||||
view:toggle_expand(false)
|
||||
else
|
||||
local parent_item, y = view:get_parent(view.selected_item)
|
||||
if parent_item then
|
||||
view:set_selection(parent_item, y)
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["treeview:expand"] = function()
|
||||
view:toggle_expand(true)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
local function treeitem() return view.hovered_item or view.selected_item end
|
||||
|
||||
|
||||
command.add(
|
||||
function()
|
||||
return treeitem() ~= nil
|
||||
and (
|
||||
core.active_view == view or core.active_view == menu
|
||||
or (view.toolbar and core.active_view == view.toolbar)
|
||||
-- sometimes the context menu is shown on top of statusbar
|
||||
or core.active_view == core.status_view
|
||||
)
|
||||
end, {
|
||||
["treeview:delete"] = function()
|
||||
local filename = view.hovered_item.abs_filename
|
||||
local relfilename = view.hovered_item.filename
|
||||
local filename = treeitem().abs_filename
|
||||
local relfilename = treeitem().filename
|
||||
local file_info = system.get_file_info(filename)
|
||||
local file_type = file_info.type == "dir" and "Directory" or "File"
|
||||
-- Ask before deleting
|
||||
|
@ -568,22 +712,83 @@ command.add(function() return view.hovered_item ~= nil end, {
|
|||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
command.add(function() return treeitem() ~= nil end, {
|
||||
["treeview:rename"] = function()
|
||||
local old_filename = treeitem().filename
|
||||
local old_abs_filename = treeitem().abs_filename
|
||||
core.command_view:set_text(old_filename)
|
||||
core.command_view:enter("Rename", function(filename)
|
||||
filename = core.normalize_to_project_dir(filename)
|
||||
local abs_filename = core.project_absolute_path(filename)
|
||||
local res, err = os.rename(old_abs_filename, abs_filename)
|
||||
if res then -- successfully renamed
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if doc.abs_filename and old_abs_filename == doc.abs_filename then
|
||||
doc:set_filename(filename, abs_filename) -- make doc point to the new filename
|
||||
doc:reset_syntax()
|
||||
break -- only first needed
|
||||
end
|
||||
end
|
||||
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
||||
else
|
||||
core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err)
|
||||
end
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
["treeview:new-file"] = function()
|
||||
if not is_project_folder(treeitem().abs_filename) then
|
||||
core.command_view:set_text(treeitem().filename .. "/")
|
||||
end
|
||||
core.command_view:enter("Filename", function(filename)
|
||||
local doc_filename = core.project_dir .. PATHSEP .. filename
|
||||
local file = io.open(doc_filename, "a+")
|
||||
file:write("")
|
||||
file:close()
|
||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
||||
core.log("Created %s", doc_filename)
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
["treeview:new-folder"] = function()
|
||||
if not is_project_folder(treeitem().abs_filename) then
|
||||
core.command_view:set_text(treeitem().filename .. "/")
|
||||
end
|
||||
core.command_view:enter("Folder Name", function(filename)
|
||||
local dir_path = core.project_dir .. PATHSEP .. filename
|
||||
common.mkdirp(dir_path)
|
||||
core.log("Created %s", dir_path)
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
["treeview:open-in-system"] = function()
|
||||
local hovered_item = view.hovered_item
|
||||
local hovered_item = treeitem()
|
||||
|
||||
if PLATFORM == "Windows" then
|
||||
system.exec(string.format("start \"\" %q", hovered_item.abs_filename))
|
||||
elseif string.find(PLATFORM, "Mac") then
|
||||
system.exec(string.format("open %q", hovered_item.abs_filename))
|
||||
elseif PLATFORM == "Linux" then
|
||||
elseif PLATFORM == "Linux" or string.find(PLATFORM, "BSD") then
|
||||
system.exec(string.format("xdg-open %q", hovered_item.abs_filename))
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
keymap.add { ["ctrl+\\"] = "treeview:toggle" }
|
||||
keymap.add {
|
||||
["ctrl+\\"] = "treeview:toggle",
|
||||
["up"] = "treeview:previous",
|
||||
["down"] = "treeview:next",
|
||||
["left"] = "treeview:collapse",
|
||||
["right"] = "treeview:expand",
|
||||
["return"] = "treeview:open",
|
||||
["escape"] = "treeview:deselect",
|
||||
["delete"] = "treeview:delete",
|
||||
["ctrl+return"] = "treeview:new-folder"
|
||||
}
|
||||
|
||||
-- Return the treeview with toolbar and contextmenu to allow
|
||||
-- user or plugin modifications
|
||||
|
|
|
@ -124,7 +124,7 @@ process.options = {}
|
|||
---@return process | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:start(command_and_params, options) end
|
||||
function process.start(command_and_params, options) end
|
||||
|
||||
---
|
||||
---Translates an error code into a useful text message
|
||||
|
|
|
@ -192,6 +192,12 @@ function system.get_clipboard() end
|
|||
---@param text string
|
||||
function system.set_clipboard(text) end
|
||||
|
||||
---
|
||||
---Get the process id of lite-xl it self.
|
||||
---
|
||||
---@return integer
|
||||
function system.get_process_id() end
|
||||
|
||||
---
|
||||
---Get amount of iterations since the application was launched
|
||||
---also known as SDL_GetPerformanceCounter() / SDL_GetPerformanceFrequency()
|
||||
|
|
1591
lib/dmon/dmon.h
1591
lib/dmon/dmon.h
File diff suppressed because it is too large
Load Diff
|
@ -1,162 +0,0 @@
|
|||
#ifndef __DMON_EXTRA_H__
|
||||
#define __DMON_EXTRA_H__
|
||||
|
||||
//
|
||||
// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved.
|
||||
// License: https://github.com/septag/dmon#license-bsd-2-clause
|
||||
//
|
||||
// Extra header functionality for dmon.h for the backend based on inotify
|
||||
//
|
||||
// Add/Remove directory functions:
|
||||
// dmon_watch_add: Adds a sub-directory to already valid watch_id. sub-directories are assumed to be relative to watch root_dir
|
||||
// dmon_watch_add: Removes a sub-directory from already valid watch_id. sub-directories are assumed to be relative to watch root_dir
|
||||
// Reason: The inotify backend does not work well when watching in recursive mode a root directory of a large tree, it may take
|
||||
// quite a while until all inotify watches are established, and events will not be received in this time. Also, since one
|
||||
// inotify watch will be established per subdirectory, it is possible that the maximum amount of inotify watches per user
|
||||
// will be reached. The default maximum is 8192.
|
||||
// When using inotify backend users may turn off the DMON_WATCHFLAGS_RECURSIVE flag and add/remove selectively the
|
||||
// sub-directories to be watched based on application-specific logic about which sub-directory actually needs to be watched.
|
||||
// The function dmon_watch_add and dmon_watch_rm are used to this purpose.
|
||||
//
|
||||
|
||||
#ifndef __DMON_H__
|
||||
#error "Include 'dmon.h' before including this file"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir);
|
||||
DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef DMON_IMPL
|
||||
#if DMON_OS_LINUX
|
||||
DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir)
|
||||
{
|
||||
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
|
||||
|
||||
bool skip_lock = pthread_self() == _dmon.thread_handle;
|
||||
|
||||
if (!skip_lock)
|
||||
pthread_mutex_lock(&_dmon.mutex);
|
||||
|
||||
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
|
||||
|
||||
// check if the directory exists
|
||||
// if watchdir contains absolute/root-included path, try to strip the rootdir from it
|
||||
// else, we assume that watchdir is correct, so save it as it is
|
||||
struct stat st;
|
||||
dmon__watch_subdir subdir;
|
||||
if (stat(watchdir, &st) == 0 && (st.st_mode & S_IFDIR)) {
|
||||
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
|
||||
if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) {
|
||||
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir));
|
||||
}
|
||||
} else {
|
||||
char fullpath[DMON_MAX_PATH];
|
||||
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
|
||||
dmon__strcat(fullpath, sizeof(fullpath), watchdir);
|
||||
if (stat(fullpath, &st) != 0 || (st.st_mode & S_IFDIR) == 0) {
|
||||
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
}
|
||||
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
|
||||
}
|
||||
|
||||
int dirlen = (int)strlen(subdir.rootdir);
|
||||
if (subdir.rootdir[dirlen - 1] != '/') {
|
||||
subdir.rootdir[dirlen] = '/';
|
||||
subdir.rootdir[dirlen + 1] = '\0';
|
||||
}
|
||||
|
||||
// check that the directory is not already added
|
||||
for (int i = 0, c = stb_sb_count(watch->subdirs); i < c; i++) {
|
||||
if (strcmp(subdir.rootdir, watch->subdirs[i].rootdir) == 0) {
|
||||
_DMON_LOG_ERRORF("Error watching directory '%s', because it is already added.", watchdir);
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY;
|
||||
char fullpath[DMON_MAX_PATH];
|
||||
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
|
||||
dmon__strcat(fullpath, sizeof(fullpath), subdir.rootdir);
|
||||
int wd = inotify_add_watch(watch->fd, fullpath, inotify_mask);
|
||||
if (wd == -1) {
|
||||
_DMON_LOG_ERRORF("Error watching directory '%s'. (inotify_add_watch:err=%d)", watchdir, errno);
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
stb_sb_push(watch->subdirs, subdir);
|
||||
stb_sb_push(watch->wds, wd);
|
||||
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir)
|
||||
{
|
||||
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
|
||||
|
||||
bool skip_lock = pthread_self() == _dmon.thread_handle;
|
||||
|
||||
if (!skip_lock)
|
||||
pthread_mutex_lock(&_dmon.mutex);
|
||||
|
||||
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
|
||||
|
||||
char subdir[DMON_MAX_PATH];
|
||||
dmon__strcpy(subdir, sizeof(subdir), watchdir);
|
||||
if (strstr(subdir, watch->rootdir) == subdir) {
|
||||
dmon__strcpy(subdir, sizeof(subdir), watchdir + strlen(watch->rootdir));
|
||||
}
|
||||
|
||||
int dirlen = (int)strlen(subdir);
|
||||
if (subdir[dirlen - 1] != '/') {
|
||||
subdir[dirlen] = '/';
|
||||
subdir[dirlen + 1] = '\0';
|
||||
}
|
||||
|
||||
int i, c = stb_sb_count(watch->subdirs);
|
||||
for (i = 0; i < c; i++) {
|
||||
if (strcmp(watch->subdirs[i].rootdir, subdir) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= c) {
|
||||
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
}
|
||||
inotify_rm_watch(watch->fd, watch->wds[i]);
|
||||
|
||||
/* Remove entry from subdirs and wds by swapping position with the last entry */
|
||||
watch->subdirs[i] = stb_sb_last(watch->subdirs);
|
||||
stb_sb_pop(watch->subdirs);
|
||||
|
||||
watch->wds[i] = stb_sb_last(watch->wds);
|
||||
stb_sb_pop(watch->wds);
|
||||
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return true;
|
||||
}
|
||||
#endif // DMON_OS_LINUX
|
||||
#endif // DMON_IMPL
|
||||
|
||||
#endif // __DMON_EXTRA_H__
|
||||
|
|
@ -1 +0,0 @@
|
|||
lite_includes += include_directories('.')
|
|
@ -22,33 +22,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
## septag/dmon
|
||||
|
||||
Copyright 2019 Sepehr Taghdisian. All rights reserved.
|
||||
|
||||
https://github.com/septag/dmon
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
## Fira Sans
|
||||
|
||||
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
|
||||
|
|
73
meson.build
73
meson.build
|
@ -1,18 +1,41 @@
|
|||
project('lite-xl',
|
||||
['c'],
|
||||
version : '2.0.3',
|
||||
version : '2.0.5',
|
||||
license : 'MIT',
|
||||
meson_version : '>= 0.54',
|
||||
default_options : ['c_std=gnu11']
|
||||
meson_version : '>= 0.47',
|
||||
default_options : [
|
||||
'c_std=gnu11',
|
||||
'wrap_mode=nofallback'
|
||||
]
|
||||
)
|
||||
|
||||
#===============================================================================
|
||||
# Project version including git commit if possible
|
||||
#===============================================================================
|
||||
version = meson.project_version()
|
||||
|
||||
if get_option('buildtype') != 'release'
|
||||
git_command = find_program('git', required : false)
|
||||
|
||||
if git_command.found()
|
||||
git_commit = run_command(
|
||||
[git_command, 'rev-parse', 'HEAD'],
|
||||
check : false
|
||||
).stdout().strip()
|
||||
|
||||
if git_commit != ''
|
||||
version += ' (git-' + git_commit.substring(0, 8) + ')'
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
#===============================================================================
|
||||
# Configuration
|
||||
#===============================================================================
|
||||
conf_data = configuration_data()
|
||||
conf_data.set('PROJECT_BUILD_DIR', meson.current_build_dir())
|
||||
conf_data.set('PROJECT_SOURCE_DIR', meson.current_source_dir())
|
||||
conf_data.set('PROJECT_VERSION', meson.project_version())
|
||||
conf_data.set('PROJECT_VERSION', version)
|
||||
|
||||
#===============================================================================
|
||||
# Compiler Settings
|
||||
|
@ -24,7 +47,7 @@ endif
|
|||
cc = meson.get_compiler('c')
|
||||
|
||||
lite_includes = []
|
||||
lite_cargs = []
|
||||
lite_cargs = ['-DSDL_MAIN_HANDLED', '-DPCRE2_STATIC']
|
||||
# On macos we need to use the SDL renderer to support retina displays
|
||||
if get_option('renderer') or host_machine.system() == 'darwin'
|
||||
lite_cargs += '-DLITE_USE_SDL_RENDERER'
|
||||
|
@ -46,14 +69,32 @@ endif
|
|||
if not get_option('source-only')
|
||||
libm = cc.find_library('m', required : false)
|
||||
libdl = cc.find_library('dl', required : false)
|
||||
threads_dep = dependency('threads')
|
||||
lua_dep = dependency('lua5.2', fallback: ['lua', 'lua_dep'],
|
||||
default_options: ['shared=false', 'use_readline=false', 'app=false']
|
||||
lua_fallback = ['lua', 'lua_dep']
|
||||
lua_quick_fallback = []
|
||||
if get_option('wrap_mode') == 'forcefallback'
|
||||
lua_quick_fallback = lua_fallback
|
||||
endif
|
||||
|
||||
lua_dep = dependency('lua5.4', fallback: lua_quick_fallback, required : false)
|
||||
if not lua_dep.found()
|
||||
lua_dep = dependency('lua', fallback: ['lua', 'lua_dep'],
|
||||
default_options: ['default_library=static', 'line_editing=false', 'interpreter=false']
|
||||
)
|
||||
pcre2_dep = dependency('libpcre2-8')
|
||||
freetype_dep = dependency('freetype2')
|
||||
sdl_dep = dependency('sdl2', method: 'config-tool')
|
||||
lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl, threads_dep]
|
||||
endif
|
||||
|
||||
pcre2_dep = dependency('libpcre2-8', fallback: ['pcre2', 'libpcre2_8'],
|
||||
default_options: ['default_library=static', 'grep=false', 'test=false']
|
||||
)
|
||||
|
||||
freetype_dep = dependency('freetype2', fallback: ['freetype2', 'freetype_dep'],
|
||||
default_options: ['default_library=static', 'zlib=disabled', 'bzip2=disabled', 'png=disabled', 'harfbuzz=disabled', 'brotli=disabled']
|
||||
)
|
||||
|
||||
sdl_dep = dependency('sdl2', fallback: ['sdl2', 'sdl2_dep'],
|
||||
default_options: ['default_library=static']
|
||||
)
|
||||
|
||||
lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl]
|
||||
endif
|
||||
#===============================================================================
|
||||
# Install Configuration
|
||||
|
@ -94,21 +135,19 @@ endif
|
|||
|
||||
install_data('licenses/licenses.md', install_dir : lite_docdir)
|
||||
|
||||
install_subdir('data' / 'core' , install_dir : lite_datadir, exclude_files : 'start.lua')
|
||||
install_subdir('data/core' , install_dir : lite_datadir, exclude_files : 'start.lua')
|
||||
foreach data_module : ['fonts', 'plugins', 'colors']
|
||||
install_subdir('data' / data_module , install_dir : lite_datadir)
|
||||
install_subdir(join_paths('data', data_module), install_dir : lite_datadir)
|
||||
endforeach
|
||||
|
||||
configure_file(
|
||||
input : 'data/core/start.lua',
|
||||
output : 'start.lua',
|
||||
configuration : conf_data,
|
||||
install : true,
|
||||
install_dir : lite_datadir / 'core',
|
||||
install_dir : join_paths(lite_datadir, 'core'),
|
||||
)
|
||||
|
||||
if not get_option('source-only')
|
||||
subdir('lib/dmon')
|
||||
subdir('src')
|
||||
subdir('scripts')
|
||||
endif
|
||||
|
|
|
@ -2,3 +2,4 @@ option('bundle', type : 'boolean', value : false, description: 'Build a macOS bu
|
|||
option('source-only', type : 'boolean', value : false, description: 'Configure source files only, doesn\'t checks for dependencies')
|
||||
option('portable', type : 'boolean', value : false, description: 'Portable install')
|
||||
option('renderer', type : 'boolean', value : false, description: 'Use SDL renderer')
|
||||
option('dirmonitor_backend', type : 'combo', value : '', choices : ['', 'inotify', 'kqueue', 'win32', 'dummy'], description: 'define what dirmonitor backend to use')
|
|
@ -17,11 +17,11 @@
|
|||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>The editor window</caption>
|
||||
<image>https://lite-xl.github.io/assets/img/screenshots/editor.png</image>
|
||||
<image>https://lite-xl.com/assets/img/editor.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
<url type="homepage">https://lite-xl.github.io</url>
|
||||
<url type="homepage">https://lite-xl.com</url>
|
||||
|
||||
<provides>
|
||||
<binary>lite-xl</binary>
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
<string>lite-xl</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>icon.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.lite-xl</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Lite XL</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
|
||||
`core.set_project_dir`:
|
||||
Reset project directories and set its directory.
|
||||
It chdir into the directory, empty the `core.project_directories` and add
|
||||
the given directory.
|
||||
`core.add_project_directory`:
|
||||
Add a new top-level directory to the project.
|
||||
Also called from modules and commands outside core.init.
|
||||
local function `scan_project_folder`:
|
||||
Scan all files for a given top-level project directory.
|
||||
Can emit a warning about file limit.
|
||||
Called only from within core.init module.
|
||||
|
||||
`core.scan_project_subdir`: (before was named `core.scan_project_folder`)
|
||||
scan a single folder, without recursion. Used when too many files.
|
||||
|
||||
Local function `scan_project_folder`:
|
||||
Populate the project folder top directory. Done only once when the directory
|
||||
is added to the project.
|
||||
|
||||
`core.add_project_directory`:
|
||||
Add a new top-level folder to the project.
|
||||
|
||||
`core.set_project_dir`:
|
||||
Set the initial project directory.
|
||||
|
||||
`core.dir_rescan_add_job`:
|
||||
Add a job to rescan after an elapsed time a project's subdirectory to fix for any
|
||||
changes.
|
||||
|
||||
Local function `rescan_project_subdir`:
|
||||
Rescan a project's subdirectory, compare to the current version and patch the list if
|
||||
a difference is found.
|
||||
|
||||
|
||||
`core.project_scan_thread`:
|
||||
Should disappear now that we use dmon.
|
||||
|
||||
|
||||
`core.project_scan_topdir`:
|
||||
New function to scan a top level project folder.
|
||||
|
||||
|
||||
`config.project_scan_rate`:
|
||||
`core.project_scan_thread_id`:
|
||||
`core.reschedule_project_scan`:
|
||||
`core.project_files_limit`:
|
||||
A eliminer.
|
||||
|
||||
`core.get_project_files`:
|
||||
To be fixed. Use `find_project_files_co` for a single directory
|
||||
|
||||
In TreeView remove usage of self.last to detect new scan that changed the files list.
|
||||
|
|
@ -63,10 +63,10 @@ main() {
|
|||
elif [[ "$OSTYPE" == "msys" ]]; then
|
||||
if [[ $lhelper == true ]]; then
|
||||
pacman --noconfirm -S \
|
||||
${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config} unzip
|
||||
${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config,mesa} unzip
|
||||
else
|
||||
pacman --noconfirm -S \
|
||||
${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config,freetype,pcre2,SDL2} unzip
|
||||
${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config,mesa,freetype,pcre2,SDL2} unzip
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
|
|
@ -72,4 +72,4 @@ main() {
|
|||
fi
|
||||
}
|
||||
|
||||
main
|
||||
main "$@"
|
||||
|
|
|
@ -216,11 +216,11 @@ main() {
|
|||
rm -rf "Lite XL.app"; mv "${dest_dir}" "Lite XL.app"
|
||||
dest_dir="Lite XL.app"
|
||||
exe_file="$(pwd)/${dest_dir}/Contents/MacOS/lite-xl"
|
||||
data_dir="$(pwd)/${dest_dir}/Contents/Resources"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $bundle == false && $portable == false ]]; then
|
||||
echo "Creating a compressed archive..."
|
||||
data_dir="$(pwd)/${dest_dir}/$prefix/share/lite-xl"
|
||||
exe_file="$(pwd)/${dest_dir}/$prefix/bin/lite-xl"
|
||||
fi
|
||||
|
@ -240,6 +240,7 @@ main() {
|
|||
|
||||
$stripcmd "${exe_file}"
|
||||
|
||||
echo "Creating a compressed archive ${package_name}"
|
||||
if [[ $binary == true ]]; then
|
||||
rm -f "${package_name}".tar.gz
|
||||
rm -f "${package_name}".zip
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
#include "api.h"
|
||||
|
||||
|
||||
int luaopen_system(lua_State *L);
|
||||
int luaopen_renderer(lua_State *L);
|
||||
int luaopen_regex(lua_State *L);
|
||||
int luaopen_process(lua_State *L);
|
||||
int luaopen_dirmonitor(lua_State* L);
|
||||
|
||||
static const luaL_Reg libs[] = {
|
||||
{ "system", luaopen_system },
|
||||
{ "renderer", luaopen_renderer },
|
||||
{ "regex", luaopen_regex },
|
||||
{ "process", luaopen_process },
|
||||
{ "dirmonitor", luaopen_dirmonitor },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
|
||||
void api_load_libs(lua_State *L) {
|
||||
for (int i = 0; libs[i].name; i++)
|
||||
luaL_requiref(L, libs[i].name, libs[i].func, 1);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#define API_TYPE_FONT "Font"
|
||||
#define API_TYPE_PROCESS "Process"
|
||||
#define API_TYPE_DIRMONITOR "Dirmonitor"
|
||||
|
||||
#define API_CONSTANT_DEFINE(L, idx, key, n) (lua_pushnumber(L, n), lua_setfield(L, idx - 1, key))
|
||||
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
#include "api.h"
|
||||
#include <stdlib.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#elif __linux__
|
||||
#include <sys/inotify.h>
|
||||
#include <limits.h>
|
||||
#else
|
||||
#include <sys/event.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef DIRMONITOR_BACKEND
|
||||
#error No dirmonitor backend defined
|
||||
#endif
|
||||
|
||||
#define GLUE_HELPER(x, y) x##y
|
||||
#define GLUE(x, y) GLUE_HELPER(x, y)
|
||||
|
||||
#define init_dirmonitor GLUE(init_dirmonitor_, DIRMONITOR_BACKEND)
|
||||
#define deinit_dirmonitor GLUE(deinit_dirmonitor_, DIRMONITOR_BACKEND)
|
||||
#define check_dirmonitor GLUE(check_dirmonitor_, DIRMONITOR_BACKEND)
|
||||
#define add_dirmonitor GLUE(add_dirmonitor_, DIRMONITOR_BACKEND)
|
||||
#define remove_dirmonitor GLUE(remove_dirmonitor_, DIRMONITOR_BACKEND)
|
||||
|
||||
struct dirmonitor {}; // dirmonitor struct is defined in each backend
|
||||
|
||||
// define functions so we know their signature
|
||||
struct dirmonitor* init_dirmonitor();
|
||||
void deinit_dirmonitor(struct dirmonitor*);
|
||||
int check_dirmonitor(struct dirmonitor*, int (*)(int, const char*, void*), void*);
|
||||
int add_dirmonitor(struct dirmonitor*, const char*);
|
||||
void remove_dirmonitor(struct dirmonitor*, int);
|
||||
|
||||
static int f_check_dir_callback(int watch_id, const char* path, void* L) {
|
||||
lua_pushvalue(L, -1);
|
||||
#ifdef DIRMONITOR_WIN32
|
||||
char buffer[PATH_MAX*4];
|
||||
int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)path, watch_id, buffer, PATH_MAX*4 - 1, NULL, NULL);
|
||||
lua_pushlstring(L, buffer, count);
|
||||
#else
|
||||
lua_pushnumber(L, watch_id);
|
||||
#endif
|
||||
lua_call(L, 1, 1);
|
||||
int result = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return !result;
|
||||
}
|
||||
|
||||
static int f_dirmonitor_new(lua_State* L) {
|
||||
struct dirmonitor** monitor = lua_newuserdata(L, sizeof(struct dirmonitor**));
|
||||
*monitor = init_dirmonitor();
|
||||
luaL_setmetatable(L, API_TYPE_DIRMONITOR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int f_dirmonitor_gc(lua_State* L) {
|
||||
deinit_dirmonitor(*((struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int f_dirmonitor_watch(lua_State *L) {
|
||||
lua_pushnumber(L, add_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), luaL_checkstring(L, 2)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int f_dirmonitor_unwatch(lua_State *L) {
|
||||
remove_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), luaL_checknumber(L, 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int f_dirmonitor_check(lua_State* L) {
|
||||
lua_pushnumber(L, check_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), f_check_dir_callback, L));
|
||||
return 1;
|
||||
}
|
||||
static const luaL_Reg dirmonitor_lib[] = {
|
||||
{ "new", f_dirmonitor_new },
|
||||
{ "__gc", f_dirmonitor_gc },
|
||||
{ "watch", f_dirmonitor_watch },
|
||||
{ "unwatch", f_dirmonitor_unwatch },
|
||||
{ "check", f_dirmonitor_check },
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
int luaopen_dirmonitor(lua_State* L) {
|
||||
luaL_newmetatable(L, API_TYPE_DIRMONITOR);
|
||||
luaL_setfuncs(L, dirmonitor_lib, 0);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -2, "__index");
|
||||
return 1;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
struct dirmonitor {
|
||||
};
|
||||
|
||||
struct dirmonitor* init_dirmonitor_dummy() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void deinit_dirmonitor_dummy(struct dirmonitor* monitor) {
|
||||
}
|
||||
|
||||
int check_dirmonitor_dummy(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int add_dirmonitor_dummy(struct dirmonitor* monitor, const char* path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
void remove_dirmonitor_dummy(struct dirmonitor* monitor, int fd) {
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
#include <sys/inotify.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
struct dirmonitor {
|
||||
int fd;
|
||||
};
|
||||
|
||||
struct dirmonitor* init_dirmonitor_inotify() {
|
||||
struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1);
|
||||
|
||||
monitor->fd = inotify_init();
|
||||
fcntl(monitor->fd, F_SETFL, O_NONBLOCK);
|
||||
|
||||
|
||||
return monitor;
|
||||
}
|
||||
|
||||
void deinit_dirmonitor_inotify(struct dirmonitor* monitor) {
|
||||
close(monitor->fd);
|
||||
free(monitor);
|
||||
}
|
||||
|
||||
int check_dirmonitor_inotify(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) {
|
||||
char buf[PATH_MAX + sizeof(struct inotify_event)];
|
||||
ssize_t offset = 0;
|
||||
|
||||
while (1) {
|
||||
ssize_t len = read(monitor->fd, &buf[offset], sizeof(buf) - offset);
|
||||
|
||||
if (len == -1 && errno != EAGAIN) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
if (len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (len > sizeof(struct inotify_event) && len >= ((struct inotify_event*)buf)->len + sizeof(struct inotify_event)) {
|
||||
change_callback(((const struct inotify_event *)buf)->wd, NULL, data);
|
||||
len -= sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len;
|
||||
memmove(buf, &buf[sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len], len);
|
||||
offset = len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int add_dirmonitor_inotify(struct dirmonitor* monitor, const char* path) {
|
||||
return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO);
|
||||
}
|
||||
|
||||
void remove_dirmonitor_inotify(struct dirmonitor* monitor, int fd) {
|
||||
inotify_rm_watch(monitor->fd, fd);
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
#include <sys/event.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
struct dirmonitor {
|
||||
int fd;
|
||||
};
|
||||
|
||||
struct dirmonitor* init_dirmonitor_kqueue() {
|
||||
struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1);
|
||||
monitor->fd = kqueue();
|
||||
return monitor;
|
||||
}
|
||||
|
||||
void deinit_dirmonitor_kqueue(struct dirmonitor* monitor) {
|
||||
close(monitor->fd);
|
||||
free(monitor);
|
||||
}
|
||||
|
||||
int check_dirmonitor_kqueue(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) {
|
||||
struct kevent event;
|
||||
while (1) {
|
||||
struct timespec tm = {0};
|
||||
int nev = kevent(monitor->fd, NULL, 0, &event, 1, &tm);
|
||||
|
||||
if (nev == -1) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
if (nev <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
change_callback(event.ident, NULL, data);
|
||||
}
|
||||
}
|
||||
|
||||
int add_dirmonitor_kqueue(struct dirmonitor* monitor, const char* path) {
|
||||
int fd = open(path, O_RDONLY);
|
||||
struct kevent change;
|
||||
|
||||
EV_SET(&change, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_EXTEND | NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME, 0, (void*)path);
|
||||
kevent(monitor->fd, &change, 1, NULL, 0, NULL);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
void remove_dirmonitor_kqueue(struct dirmonitor* monitor, int fd) {
|
||||
close(fd);
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
#include <stdbool.h>
|
||||
#include <windows.h>
|
||||
|
||||
struct dirmonitor {
|
||||
HANDLE handle;
|
||||
char buffer[8192];
|
||||
OVERLAPPED overlapped;
|
||||
bool running;
|
||||
};
|
||||
|
||||
struct dirmonitor* init_dirmonitor_win32() {
|
||||
struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1);
|
||||
|
||||
return monitor;
|
||||
}
|
||||
|
||||
static void close_monitor_handle(struct dirmonitor* monitor) {
|
||||
if (monitor->handle) {
|
||||
if (monitor->running) {
|
||||
BOOL result = CancelIoEx(monitor->handle, &monitor->overlapped);
|
||||
DWORD error = GetLastError();
|
||||
if (result == TRUE || error != ERROR_NOT_FOUND) {
|
||||
DWORD bytes_transferred;
|
||||
GetOverlappedResult( monitor->handle, &monitor->overlapped, &bytes_transferred, TRUE );
|
||||
}
|
||||
monitor->running = false;
|
||||
}
|
||||
CloseHandle(monitor->handle);
|
||||
}
|
||||
monitor->handle = NULL;
|
||||
}
|
||||
|
||||
void deinit_dirmonitor_win32(struct dirmonitor* monitor) {
|
||||
close_monitor_handle(monitor);
|
||||
free(monitor);
|
||||
}
|
||||
|
||||
int check_dirmonitor_win32(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) {
|
||||
if (!monitor->running) {
|
||||
if (ReadDirectoryChangesW(monitor->handle, monitor->buffer, sizeof(monitor->buffer), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &monitor->overlapped, NULL) == 0) {
|
||||
return GetLastError();
|
||||
}
|
||||
monitor->running = true;
|
||||
}
|
||||
|
||||
DWORD bytes_transferred;
|
||||
|
||||
if (!GetOverlappedResult(monitor->handle, &monitor->overlapped, &bytes_transferred, FALSE)) {
|
||||
int error = GetLastError();
|
||||
return error == ERROR_IO_PENDING || error == ERROR_IO_INCOMPLETE ? 0 : error;
|
||||
}
|
||||
|
||||
monitor->running = false;
|
||||
|
||||
for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)((char*)info) + info->NextEntryOffset) {
|
||||
change_callback(info->FileNameLength, (char*)info->FileName, data);
|
||||
if (!info->NextEntryOffset)
|
||||
break;
|
||||
}
|
||||
|
||||
monitor->running = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int add_dirmonitor_win32(struct dirmonitor* monitor, const char* path) {
|
||||
close_monitor_handle(monitor);
|
||||
monitor->handle = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
|
||||
if (monitor->handle && monitor->handle != INVALID_HANDLE_VALUE) {
|
||||
return 1;
|
||||
}
|
||||
monitor->handle = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void remove_dirmonitor_win32(struct dirmonitor* monitor, int fd) {
|
||||
close_monitor_handle(monitor);
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL.h>
|
||||
|
||||
|
@ -59,17 +60,18 @@ typedef enum {
|
|||
|
||||
#ifdef _WIN32
|
||||
static volatile long PipeSerialNumber;
|
||||
static void close_fd(HANDLE handle) { CloseHandle(handle); }
|
||||
static void close_fd(HANDLE* handle) { if (*handle) CloseHandle(*handle); *handle = NULL; }
|
||||
#else
|
||||
static void close_fd(int fd) { close(fd); }
|
||||
static void close_fd(int* fd) { if (*fd) close(*fd); *fd = 0; }
|
||||
#endif
|
||||
|
||||
static bool poll_process(process_t* proc, int timeout) {
|
||||
if (!proc->running)
|
||||
return false;
|
||||
unsigned int ticks = SDL_GetTicks();
|
||||
uint32_t ticks = SDL_GetTicks();
|
||||
if (timeout == WAIT_DEADLINE)
|
||||
timeout = proc->deadline;
|
||||
|
||||
do {
|
||||
#ifdef _WIN32
|
||||
DWORD exit_code = -1;
|
||||
|
@ -90,13 +92,8 @@ static bool poll_process(process_t* proc, int timeout) {
|
|||
if (timeout)
|
||||
SDL_Delay(5);
|
||||
} while (timeout == WAIT_INFINITE || SDL_GetTicks() - ticks < timeout);
|
||||
if (!proc->running) {
|
||||
close_fd(proc->child_pipes[STDIN_FD ][1]);
|
||||
close_fd(proc->child_pipes[STDOUT_FD][0]);
|
||||
close_fd(proc->child_pipes[STDERR_FD][0]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
return proc->running;
|
||||
}
|
||||
|
||||
static bool signal_process(process_t* proc, signal_e sig) {
|
||||
|
@ -109,9 +106,9 @@ static bool signal_process(process_t* proc, signal_e sig) {
|
|||
}
|
||||
#else
|
||||
switch (sig) {
|
||||
case SIGNAL_TERM: terminate = kill(proc->pid, SIGTERM) == 1; break;
|
||||
case SIGNAL_KILL: terminate = kill(proc->pid, SIGKILL) == 1; break;
|
||||
case SIGNAL_INTERRUPT: kill(proc->pid, SIGINT); break;
|
||||
case SIGNAL_TERM: terminate = kill(-proc->pid, SIGTERM) == 1; break;
|
||||
case SIGNAL_KILL: terminate = kill(-proc->pid, SIGKILL) == 1; break;
|
||||
case SIGNAL_INTERRUPT: kill(-proc->pid, SIGINT); break;
|
||||
}
|
||||
#endif
|
||||
if (terminate)
|
||||
|
@ -121,19 +118,19 @@ static bool signal_process(process_t* proc, signal_e sig) {
|
|||
|
||||
static int process_start(lua_State* L) {
|
||||
size_t env_len = 0, key_len, val_len;
|
||||
const char *cmd[256], *env[256] = { NULL }, *cwd = NULL;
|
||||
const char *cmd[256], *env_names[256] = { NULL }, *env_values[256] = { NULL }, *cwd = NULL;
|
||||
bool detach = false;
|
||||
int deadline = 10, new_fds[3] = { STDIN_FD, STDOUT_FD, STDERR_FD };
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
#if LUA_VERSION_NUM > 501
|
||||
lua_len(L, 1);
|
||||
#else
|
||||
lua_pushnumber(L, (int)lua_objlen(L, 1));
|
||||
lua_pushinteger(L, (int)lua_objlen(L, 1));
|
||||
#endif
|
||||
size_t cmd_len = luaL_checknumber(L, -1); lua_pop(L, 1);
|
||||
size_t arg_len = lua_gettop(L);
|
||||
for (size_t i = 1; i <= cmd_len; ++i) {
|
||||
lua_pushnumber(L, i);
|
||||
lua_pushinteger(L, i);
|
||||
lua_rawget(L, 1);
|
||||
cmd[i-1] = luaL_checkstring(L, -1);
|
||||
}
|
||||
|
@ -145,9 +142,12 @@ static int process_start(lua_State* L) {
|
|||
while (lua_next(L, -2) != 0) {
|
||||
const char* key = luaL_checklstring(L, -2, &key_len);
|
||||
const char* val = luaL_checklstring(L, -1, &val_len);
|
||||
env[env_len] = malloc(key_len+val_len+2);
|
||||
snprintf((char*)env[env_len++], key_len+val_len+2, "%s=%s", key, val);
|
||||
env_names[env_len] = malloc(key_len+1);
|
||||
strcpy((char*)env_names[env_len], key);
|
||||
env_values[env_len] = malloc(val_len+1);
|
||||
strcpy((char*)env_values[env_len], val);
|
||||
lua_pop(L, 1);
|
||||
++env_len;
|
||||
}
|
||||
} else
|
||||
lua_pop(L, 1);
|
||||
|
@ -162,7 +162,6 @@ static int process_start(lua_State* L) {
|
|||
return luaL_error(L, "redirect to handles, FILE* and paths are not supported");
|
||||
}
|
||||
}
|
||||
env[env_len] = NULL;
|
||||
|
||||
process_t* self = lua_newuserdata(L, sizeof(process_t));
|
||||
memset(self, 0, sizeof(process_t));
|
||||
|
@ -217,26 +216,28 @@ static int process_start(lua_State* L) {
|
|||
siStartInfo.hStdInput = self->child_pipes[STDIN_FD][0];
|
||||
siStartInfo.hStdOutput = self->child_pipes[STDOUT_FD][1];
|
||||
siStartInfo.hStdError = self->child_pipes[STDERR_FD][1];
|
||||
char commandLine[32767] = { 0 }, environmentBlock[32767];
|
||||
int offset = 0;
|
||||
char commandLine[32767] = { 0 }, environmentBlock[32767], wideEnvironmentBlock[32767*2];
|
||||
strcpy(commandLine, cmd[0]);
|
||||
int offset = 0;
|
||||
for (size_t i = 1; i < cmd_len; ++i) {
|
||||
size_t len = strlen(cmd[i]);
|
||||
if (offset + len + 1 >= sizeof(commandLine))
|
||||
offset += len + 1;
|
||||
if (offset >= sizeof(commandLine))
|
||||
break;
|
||||
strcat(commandLine, " ");
|
||||
strcat(commandLine, cmd[i]);
|
||||
}
|
||||
offset = 0;
|
||||
for (size_t i = 0; i < env_len; ++i) {
|
||||
size_t len = strlen(env[i]);
|
||||
if (offset + len >= sizeof(environmentBlock))
|
||||
if (offset + strlen(env_values[i]) + strlen(env_names[i]) + 1 >= sizeof(environmentBlock))
|
||||
break;
|
||||
memcpy(&environmentBlock[offset], env[i], len);
|
||||
offset += len;
|
||||
offset += snprintf(&environmentBlock[offset], sizeof(environmentBlock) - offset, "%s=%s", env_names[i], env_values[i]);
|
||||
environmentBlock[offset++] = 0;
|
||||
}
|
||||
environmentBlock[offset++] = 0;
|
||||
if (!CreateProcess(NULL, commandLine, NULL, NULL, true, (detach ? DETACHED_PROCESS : CREATE_NO_WINDOW) | CREATE_UNICODE_ENVIRONMENT, env_len > 0 ? environmentBlock : NULL, cwd, &siStartInfo, &self->process_information))
|
||||
if (env_len > 0)
|
||||
MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, environmentBlock, offset, (LPWSTR)wideEnvironmentBlock, sizeof(wideEnvironmentBlock));
|
||||
if (!CreateProcess(NULL, commandLine, NULL, NULL, true, (detach ? DETACHED_PROCESS : CREATE_NO_WINDOW) | CREATE_UNICODE_ENVIRONMENT, env_len > 0 ? wideEnvironmentBlock : NULL, cwd, &siStartInfo, &self->process_information))
|
||||
return luaL_error(L, "Error creating a process: %d.", GetLastError());
|
||||
self->pid = (long)self->process_information.dwProcessId;
|
||||
if (detach)
|
||||
|
@ -255,6 +256,7 @@ static int process_start(lua_State* L) {
|
|||
}
|
||||
return luaL_error(L, "Error running fork: %s.", strerror(errno));
|
||||
} else if (!self->pid) {
|
||||
setpgrp();
|
||||
for (int stream = 0; stream < 3; ++stream) {
|
||||
if (new_fds[stream] == REDIRECT_DISCARD) { // Close the stream if we don't want it.
|
||||
close(self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]);
|
||||
|
@ -263,17 +265,21 @@ static int process_start(lua_State* L) {
|
|||
dup2(self->child_pipes[new_fds[stream]][new_fds[stream] == STDIN_FD ? 0 : 1], stream);
|
||||
close(self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]);
|
||||
}
|
||||
if ((!detach || setsid() != -1) && (!cwd || chdir(cwd) != -1))
|
||||
int set;
|
||||
for (set = 0; set < env_len && setenv(env_names[set], env_values[set], 1) == 0; ++set);
|
||||
if (set == env_len && (!detach || setsid() != -1) && (!cwd || chdir(cwd) != -1))
|
||||
execvp((const char*)cmd[0], (char* const*)cmd);
|
||||
const char* msg = strerror(errno);
|
||||
int result = write(STDERR_FD, msg, strlen(msg)+1);
|
||||
exit(result == strlen(msg)+1 ? -1 : -2);
|
||||
_exit(result == strlen(msg)+1 ? -1 : -2);
|
||||
}
|
||||
#endif
|
||||
for (size_t i = 0; i < env_len; ++i)
|
||||
free((char*)env[i]);
|
||||
for (size_t i = 0; i < env_len; ++i) {
|
||||
free((char*)env_names[i]);
|
||||
free((char*)env_values[i]);
|
||||
}
|
||||
for (int stream = 0; stream < 3; ++stream)
|
||||
close_fd(self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]);
|
||||
close_fd(&self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]);
|
||||
self->running = true;
|
||||
return 1;
|
||||
}
|
||||
|
@ -306,7 +312,7 @@ static int g_read(lua_State* L, int stream, unsigned long read_size) {
|
|||
#else
|
||||
luaL_Buffer b;
|
||||
luaL_buffinit(L, &b);
|
||||
uint8_t* buffer = (uint8_t*)luaL_prepbuffer(&b);
|
||||
uint8_t* buffer = (uint8_t*)luaL_prepbuffsize(&b, READ_BUF_SIZE);
|
||||
length = read(self->child_pipes[stream][0], buffer, read_size > READ_BUF_SIZE ? READ_BUF_SIZE : read_size);
|
||||
if (length == 0 && !poll_process(self, WAIT_NONE))
|
||||
return 0;
|
||||
|
@ -330,8 +336,9 @@ static int f_write(lua_State* L) {
|
|||
#if _WIN32
|
||||
DWORD dwWritten;
|
||||
if (!WriteFile(self->child_pipes[STDIN_FD][1], data, data_size, &dwWritten, NULL)) {
|
||||
int lastError = GetLastError();
|
||||
signal_process(self, SIGNAL_TERM);
|
||||
return luaL_error(L, "error writing to process: %d", GetLastError());
|
||||
return luaL_error(L, "error writing to process: %d", lastError);
|
||||
}
|
||||
length = dwWritten;
|
||||
#else
|
||||
|
@ -339,18 +346,19 @@ static int f_write(lua_State* L) {
|
|||
if (length < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
|
||||
length = 0;
|
||||
else if (length < 0) {
|
||||
const char* lastError = strerror(errno);
|
||||
signal_process(self, SIGNAL_TERM);
|
||||
return luaL_error(L, "error writing to process: %s", strerror(errno));
|
||||
return luaL_error(L, "error writing to process: %s", lastError);
|
||||
}
|
||||
#endif
|
||||
lua_pushnumber(L, length);
|
||||
lua_pushinteger(L, length);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int f_close_stream(lua_State* L) {
|
||||
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||
int stream = luaL_checknumber(L, 2);
|
||||
close_fd(self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]);
|
||||
close_fd(&self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]);
|
||||
lua_pushboolean(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
@ -375,7 +383,7 @@ static int f_tostring(lua_State* L) {
|
|||
|
||||
static int f_pid(lua_State* L) {
|
||||
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||
lua_pushnumber(L, self->pid);
|
||||
lua_pushinteger(L, self->pid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -383,7 +391,7 @@ static int f_returncode(lua_State *L) {
|
|||
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||
if (self->running)
|
||||
return 0;
|
||||
lua_pushnumber(L, self->returncode);
|
||||
lua_pushinteger(L, self->returncode);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -404,7 +412,7 @@ static int f_wait(lua_State* L) {
|
|||
int timeout = luaL_optnumber(L, 2, 0);
|
||||
if (poll_process(self, timeout))
|
||||
return 0;
|
||||
lua_pushnumber(L, self->returncode);
|
||||
lua_pushinteger(L, self->returncode);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -417,11 +425,19 @@ static int self_signal(lua_State* L, signal_e sig) {
|
|||
static int f_terminate(lua_State* L) { return self_signal(L, SIGNAL_TERM); }
|
||||
static int f_kill(lua_State* L) { return self_signal(L, SIGNAL_KILL); }
|
||||
static int f_interrupt(lua_State* L) { return self_signal(L, SIGNAL_INTERRUPT); }
|
||||
static int f_gc(lua_State* L) { return self_signal(L, SIGNAL_TERM); }
|
||||
static int f_gc(lua_State* L) {
|
||||
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||
signal_process(self, SIGNAL_TERM);
|
||||
close_fd(&self->child_pipes[STDIN_FD ][1]);
|
||||
close_fd(&self->child_pipes[STDOUT_FD][0]);
|
||||
close_fd(&self->child_pipes[STDERR_FD][0]);
|
||||
poll_process(self, 10);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int f_running(lua_State* L) {
|
||||
process_t* self = (process_t*)luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||
lua_pushboolean(L, self->running);
|
||||
lua_pushboolean(L, poll_process(self, WAIT_NONE));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -60,12 +60,14 @@ static int f_pcre_match(lua_State *L) {
|
|||
const char* str = luaL_checklstring(L, 2, &len);
|
||||
if (lua_gettop(L) > 2)
|
||||
offset = luaL_checknumber(L, 3);
|
||||
offset -= 1;
|
||||
len -= offset;
|
||||
if (lua_gettop(L) > 3)
|
||||
opts = luaL_checknumber(L, 4);
|
||||
lua_rawgeti(L, 1, 1);
|
||||
pcre2_code* re = (pcre2_code*)lua_touserdata(L, -1);
|
||||
pcre2_match_data* md = pcre2_match_data_create_from_pattern(re, NULL);
|
||||
int rc = pcre2_match(re, (PCRE2_SPTR)str, len, offset - 1, opts, md, NULL);
|
||||
int rc = pcre2_match(re, (PCRE2_SPTR)&str[offset], len, 0, opts, md, NULL);
|
||||
if (rc < 0) {
|
||||
pcre2_match_data_free(md);
|
||||
if (rc != PCRE2_ERROR_NOMATCH) {
|
||||
|
@ -86,7 +88,7 @@ static int f_pcre_match(lua_State *L) {
|
|||
return 0;
|
||||
}
|
||||
for (int i = 0; i < rc*2; i++)
|
||||
lua_pushnumber(L, ovector[i]+1);
|
||||
lua_pushnumber(L, ovector[i]+offset+1);
|
||||
pcre2_match_data_free(md);
|
||||
return rc*2;
|
||||
}
|
||||
|
@ -104,17 +106,17 @@ int luaopen_regex(lua_State *L) {
|
|||
lua_setfield(L, -2, "__name");
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, LUA_REGISTRYINDEX, "regex");
|
||||
lua_pushnumber(L, PCRE2_ANCHORED);
|
||||
lua_pushinteger(L, PCRE2_ANCHORED);
|
||||
lua_setfield(L, -2, "ANCHORED");
|
||||
lua_pushnumber(L, PCRE2_ANCHORED) ;
|
||||
lua_pushinteger(L, PCRE2_ANCHORED) ;
|
||||
lua_setfield(L, -2, "ENDANCHORED");
|
||||
lua_pushnumber(L, PCRE2_NOTBOL);
|
||||
lua_pushinteger(L, PCRE2_NOTBOL);
|
||||
lua_setfield(L, -2, "NOTBOL");
|
||||
lua_pushnumber(L, PCRE2_NOTEOL);
|
||||
lua_pushinteger(L, PCRE2_NOTEOL);
|
||||
lua_setfield(L, -2, "NOTEOL");
|
||||
lua_pushnumber(L, PCRE2_NOTEMPTY);
|
||||
lua_pushinteger(L, PCRE2_NOTEMPTY);
|
||||
lua_setfield(L, -2, "NOTEMPTY");
|
||||
lua_pushnumber(L, PCRE2_NOTEMPTY_ATSTART);
|
||||
lua_pushinteger(L, PCRE2_NOTEMPTY_ATSTART);
|
||||
lua_setfield(L, -2, "NOTEMPTY_ATSTART");
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -106,8 +106,10 @@ static int f_font_set_tab_size(lua_State *L) {
|
|||
}
|
||||
|
||||
static int f_font_gc(lua_State *L) {
|
||||
if (lua_istable(L, 1)) return 0; // do not run if its FontGroup
|
||||
RenFont** self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
ren_font_free(*self);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
148
src/api/system.c
148
src/api/system.c
|
@ -1,4 +1,5 @@
|
|||
#include <SDL.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
|
@ -6,7 +7,6 @@
|
|||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include "api.h"
|
||||
#include "../dirmonitor.h"
|
||||
#include "../rencache.h"
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
|
@ -109,9 +109,14 @@ static const char *get_key_name(const SDL_Event *e, char *buf) {
|
|||
/* We need to correctly handle non-standard layouts such as dvorak.
|
||||
Therefore, if a Latin letter(code<128) is pressed in the current layout,
|
||||
then we transmit it as it is. But we also need to support shortcuts in
|
||||
other languages, so for non-Latin characters we pass the scancode that
|
||||
matches the letter in the QWERTY layout. */
|
||||
if (e->key.keysym.sym < 128)
|
||||
other languages, so for non-Latin characters(code>128) we pass the
|
||||
scancode based name that matches the letter in the QWERTY layout.
|
||||
|
||||
In SDL, the codes of all special buttons such as control, shift, arrows
|
||||
and others, are masked with SDLK_SCANCODE_MASK, which moves them outside
|
||||
the unicode range (>0x10FFFF). Users can remap these buttons, so we need
|
||||
to return the correct name, not scancode based. */
|
||||
if ((e->key.keysym.sym < 128) || (e->key.keysym.sym & SDLK_SCANCODE_MASK))
|
||||
strcpy(buf, SDL_GetKeyName(e->key.keysym.sym));
|
||||
else
|
||||
strcpy(buf, SDL_GetScancodeName(scancode));
|
||||
|
@ -140,8 +145,8 @@ top:
|
|||
ren_resize_window();
|
||||
lua_pushstring(L, "resized");
|
||||
/* The size below will be in points. */
|
||||
lua_pushnumber(L, e.window.data1);
|
||||
lua_pushnumber(L, e.window.data2);
|
||||
lua_pushinteger(L, e.window.data1);
|
||||
lua_pushinteger(L, e.window.data2);
|
||||
return 3;
|
||||
} else if (e.window.event == SDL_WINDOWEVENT_EXPOSED) {
|
||||
rencache_invalidate();
|
||||
|
@ -174,8 +179,8 @@ top:
|
|||
SDL_GetWindowPosition(window, &wx, &wy);
|
||||
lua_pushstring(L, "filedropped");
|
||||
lua_pushstring(L, e.drop.file);
|
||||
lua_pushnumber(L, mx - wx);
|
||||
lua_pushnumber(L, my - wy);
|
||||
lua_pushinteger(L, mx - wx);
|
||||
lua_pushinteger(L, my - wy);
|
||||
SDL_free(e.drop.file);
|
||||
return 4;
|
||||
|
||||
|
@ -215,17 +220,17 @@ top:
|
|||
if (e.button.button == 1) { SDL_CaptureMouse(1); }
|
||||
lua_pushstring(L, "mousepressed");
|
||||
lua_pushstring(L, button_name(e.button.button));
|
||||
lua_pushnumber(L, e.button.x);
|
||||
lua_pushnumber(L, e.button.y);
|
||||
lua_pushnumber(L, e.button.clicks);
|
||||
lua_pushinteger(L, e.button.x);
|
||||
lua_pushinteger(L, e.button.y);
|
||||
lua_pushinteger(L, e.button.clicks);
|
||||
return 5;
|
||||
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
if (e.button.button == 1) { SDL_CaptureMouse(0); }
|
||||
lua_pushstring(L, "mousereleased");
|
||||
lua_pushstring(L, button_name(e.button.button));
|
||||
lua_pushnumber(L, e.button.x);
|
||||
lua_pushnumber(L, e.button.y);
|
||||
lua_pushinteger(L, e.button.x);
|
||||
lua_pushinteger(L, e.button.y);
|
||||
return 4;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
|
@ -238,37 +243,17 @@ top:
|
|||
e.motion.yrel += event_plus.motion.yrel;
|
||||
}
|
||||
lua_pushstring(L, "mousemoved");
|
||||
lua_pushnumber(L, e.motion.x);
|
||||
lua_pushnumber(L, e.motion.y);
|
||||
lua_pushnumber(L, e.motion.xrel);
|
||||
lua_pushnumber(L, e.motion.yrel);
|
||||
lua_pushinteger(L, e.motion.x);
|
||||
lua_pushinteger(L, e.motion.y);
|
||||
lua_pushinteger(L, e.motion.xrel);
|
||||
lua_pushinteger(L, e.motion.yrel);
|
||||
return 5;
|
||||
|
||||
case SDL_MOUSEWHEEL:
|
||||
lua_pushstring(L, "mousewheel");
|
||||
lua_pushnumber(L, e.wheel.y);
|
||||
lua_pushinteger(L, e.wheel.y);
|
||||
return 2;
|
||||
|
||||
case SDL_USEREVENT:
|
||||
lua_pushstring(L, "dirchange");
|
||||
lua_pushnumber(L, e.user.code >> 16);
|
||||
switch (e.user.code & 0xffff) {
|
||||
case DMON_ACTION_DELETE:
|
||||
lua_pushstring(L, "delete");
|
||||
break;
|
||||
case DMON_ACTION_CREATE:
|
||||
lua_pushstring(L, "create");
|
||||
break;
|
||||
case DMON_ACTION_MODIFY:
|
||||
lua_pushstring(L, "modify");
|
||||
break;
|
||||
default:
|
||||
return luaL_error(L, "unknown dmon event action: %d", e.user.code & 0xffff);
|
||||
}
|
||||
lua_pushstring(L, e.user.data1);
|
||||
free(e.user.data1);
|
||||
return 4;
|
||||
|
||||
default:
|
||||
goto top;
|
||||
}
|
||||
|
@ -366,10 +351,10 @@ static int f_get_window_size(lua_State *L) {
|
|||
int x, y, w, h;
|
||||
SDL_GetWindowSize(window, &w, &h);
|
||||
SDL_GetWindowPosition(window, &x, &y);
|
||||
lua_pushnumber(L, w);
|
||||
lua_pushnumber(L, h);
|
||||
lua_pushnumber(L, x);
|
||||
lua_pushnumber(L, y);
|
||||
lua_pushinteger(L, w);
|
||||
lua_pushinteger(L, h);
|
||||
lua_pushinteger(L, x);
|
||||
lua_pushinteger(L, y);
|
||||
return 4;
|
||||
}
|
||||
|
||||
|
@ -539,10 +524,10 @@ static int f_get_file_info(lua_State *L) {
|
|||
}
|
||||
|
||||
lua_newtable(L);
|
||||
lua_pushnumber(L, s.st_mtime);
|
||||
lua_pushinteger(L, s.st_mtime);
|
||||
lua_setfield(L, -2, "modified");
|
||||
|
||||
lua_pushnumber(L, s.st_size);
|
||||
lua_pushinteger(L, s.st_size);
|
||||
lua_setfield(L, -2, "size");
|
||||
|
||||
if (S_ISREG(s.st_mode)) {
|
||||
|
@ -554,6 +539,14 @@ static int f_get_file_info(lua_State *L) {
|
|||
}
|
||||
lua_setfield(L, -2, "type");
|
||||
|
||||
#if __linux__
|
||||
if (S_ISDIR(s.st_mode)) {
|
||||
if (lstat(path, &s) == 0) {
|
||||
lua_pushboolean(L, S_ISLNK(s.st_mode));
|
||||
lua_setfield(L, -2, "symlink");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -578,7 +571,10 @@ static struct f_type_names fs_names[] = {
|
|||
{ 0x0, NULL },
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
static int f_get_fs_type(lua_State *L) {
|
||||
#if __linux__
|
||||
const char *path = luaL_checkstring(L, 1);
|
||||
struct statfs buf;
|
||||
int status = statfs(path, &buf);
|
||||
|
@ -591,10 +587,10 @@ static int f_get_fs_type(lua_State *L) {
|
|||
return 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
lua_pushstring(L, "unknown");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static int f_mkdir(lua_State *L) {
|
||||
|
@ -632,6 +628,16 @@ static int f_set_clipboard(lua_State *L) {
|
|||
}
|
||||
|
||||
|
||||
static int f_get_process_id(lua_State *L) {
|
||||
#ifdef _WIN32
|
||||
lua_pushinteger(L, GetCurrentProcessId());
|
||||
#else
|
||||
lua_pushinteger(L, getpid());
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int f_get_time(lua_State *L) {
|
||||
double n = SDL_GetPerformanceCounter() / (double) SDL_GetPerformanceFrequency();
|
||||
lua_pushnumber(L, n);
|
||||
|
@ -688,7 +694,7 @@ static int f_fuzzy_match(lua_State *L) {
|
|||
strTarget += increment;
|
||||
}
|
||||
if (ptnTarget >= ptn && *ptnTarget) { return 0; }
|
||||
lua_pushnumber(L, score - (int)strLen * 10);
|
||||
lua_pushinteger(L, score - (int)strLen * 10);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -713,13 +719,13 @@ static void* api_require(const char* symbol) {
|
|||
P(error), P(gc), P(getallocf), P(getfield),
|
||||
P(gethook), P(gethookcount), P(gethookmask), P(getinfo), P(getlocal),
|
||||
P(getmetatable), P(getstack), P(gettable), P(gettop), P(getupvalue),
|
||||
P(insert), P(isnumber), P(isstring), P(isuserdata),
|
||||
P(load), P(newstate), P(newthread), P(newuserdata), P(next),
|
||||
P(isnumber), P(isstring), P(isuserdata),
|
||||
P(load), P(newstate), P(newthread), P(next),
|
||||
P(pushboolean), P(pushcclosure), P(pushfstring), P(pushinteger),
|
||||
P(pushlightuserdata), P(pushlstring), P(pushnil), P(pushnumber),
|
||||
P(pushstring), P(pushthread), P(pushvalue),
|
||||
P(pushvfstring), P(rawequal), P(rawget), P(rawgeti),
|
||||
P(rawset), P(rawseti), P(remove), P(replace), P(resume),
|
||||
P(rawset), P(rawseti), P(resume),
|
||||
P(setallocf), P(setfield), P(sethook), P(setlocal),
|
||||
P(setmetatable), P(settable), P(settop), P(setupvalue),
|
||||
P(status), P(tocfunction), P(tointegerx), P(tolstring), P(toboolean),
|
||||
|
@ -732,12 +738,12 @@ static void* api_require(const char* symbol) {
|
|||
U(newstate), U(setfuncs), U(buffinit), U(addlstring), U(addstring),
|
||||
U(addvalue), U(pushresult),
|
||||
#if LUA_VERSION_NUM >= 502
|
||||
P(absindex), P(arith), P(callk), P(compare), P(getctx), P(getglobal), P(getuservalue),
|
||||
P(len), P(pcallk), P(pushunsigned), P(rawgetp), P(rawlen), P(rawsetp), P(setglobal),
|
||||
P(iscfunction), P(setuservalue), P(tounsignedx), P(yieldk),
|
||||
U(checkversion_), U(tolstring), U(checkunsigned), U(len), U(getsubtable), U(prepbuffsize),
|
||||
P(absindex), P(arith), P(callk), P(compare), P(getglobal),
|
||||
P(len), P(pcallk), P(rawgetp), P(rawlen), P(rawsetp), P(setglobal),
|
||||
P(iscfunction), P(yieldk),
|
||||
U(checkversion_), U(tolstring), U(len), U(getsubtable), U(prepbuffsize),
|
||||
U(pushresultsize), U(buffinitsize), U(checklstring), U(checkoption), U(gsub), U(loadbufferx),
|
||||
U(loadfilex), U(optinteger), U(optlstring), U(optunsigned), U(requiref), U(traceback)
|
||||
U(loadfilex), U(optinteger), U(optlstring), U(requiref), U(traceback)
|
||||
#else
|
||||
P(objlen)
|
||||
#endif
|
||||
|
@ -786,34 +792,6 @@ static int f_load_native_plugin(lua_State *L) {
|
|||
return result;
|
||||
}
|
||||
|
||||
static int f_watch_dir(lua_State *L) {
|
||||
const char *path = luaL_checkstring(L, 1);
|
||||
const int recursive = lua_toboolean(L, 2);
|
||||
uint32_t dmon_flags = (recursive ? DMON_WATCHFLAGS_RECURSIVE : 0);
|
||||
dmon_watch_id watch_id = dmon_watch(path, dirmonitor_watch_callback, dmon_flags, NULL);
|
||||
if (watch_id.id == 0) { luaL_error(L, "directory monitoring watch failed"); }
|
||||
lua_pushnumber(L, watch_id.id);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if __linux__
|
||||
static int f_watch_dir_add(lua_State *L) {
|
||||
dmon_watch_id watch_id;
|
||||
watch_id.id = luaL_checkinteger(L, 1);
|
||||
const char *subdir = luaL_checkstring(L, 2);
|
||||
lua_pushboolean(L, dmon_watch_add(watch_id, subdir));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int f_watch_dir_rm(lua_State *L) {
|
||||
dmon_watch_id watch_id;
|
||||
watch_id.id = luaL_checkinteger(L, 1);
|
||||
const char *subdir = luaL_checkstring(L, 2);
|
||||
lua_pushboolean(L, dmon_watch_rm(watch_id, subdir));
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define PATHSEP '\\'
|
||||
#else
|
||||
|
@ -893,19 +871,15 @@ static const luaL_Reg lib[] = {
|
|||
{ "get_file_info", f_get_file_info },
|
||||
{ "get_clipboard", f_get_clipboard },
|
||||
{ "set_clipboard", f_set_clipboard },
|
||||
{ "get_process_id", f_get_process_id },
|
||||
{ "get_time", f_get_time },
|
||||
{ "sleep", f_sleep },
|
||||
{ "exec", f_exec },
|
||||
{ "fuzzy_match", f_fuzzy_match },
|
||||
{ "set_window_opacity", f_set_window_opacity },
|
||||
{ "load_native_plugin", f_load_native_plugin },
|
||||
{ "watch_dir", f_watch_dir },
|
||||
{ "path_compare", f_path_compare },
|
||||
#if __linux__
|
||||
{ "watch_dir_add", f_watch_dir_add },
|
||||
{ "watch_dir_rm", f_watch_dir_rm },
|
||||
{ "get_fs_type", f_get_fs_type },
|
||||
#endif
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#define DMON_IMPL
|
||||
#include "dmon.h"
|
||||
#include "dmon_extra.h"
|
||||
|
||||
#include "dirmonitor.h"
|
||||
|
||||
static void send_sdl_event(dmon_watch_id watch_id, dmon_action action, const char *filepath) {
|
||||
SDL_Event ev;
|
||||
const int size = strlen(filepath) + 1;
|
||||
/* The string allocated below should be deallocated as soon as the event is
|
||||
treated in the SDL main loop. */
|
||||
char *new_filepath = malloc(size);
|
||||
if (!new_filepath) return;
|
||||
memcpy(new_filepath, filepath, size);
|
||||
#ifdef _WIN32
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (new_filepath[i] == '/') {
|
||||
new_filepath[i] = '\\';
|
||||
}
|
||||
}
|
||||
#endif
|
||||
SDL_zero(ev);
|
||||
ev.type = SDL_USEREVENT;
|
||||
ev.user.code = ((watch_id.id & 0xffff) << 16) | (action & 0xffff);
|
||||
ev.user.data1 = new_filepath;
|
||||
SDL_PushEvent(&ev);
|
||||
}
|
||||
|
||||
void dirmonitor_init() {
|
||||
dmon_init();
|
||||
/* In theory we should register our user event but since we
|
||||
have just one type of user event this is not really needed. */
|
||||
/* sdl_dmon_event_type = SDL_RegisterEvents(1); */
|
||||
}
|
||||
|
||||
void dirmonitor_deinit() {
|
||||
dmon_deinit();
|
||||
}
|
||||
|
||||
void dirmonitor_watch_callback(dmon_watch_id watch_id, dmon_action action, const char *rootdir,
|
||||
const char *filepath, const char *oldfilepath, void *user)
|
||||
{
|
||||
(void) rootdir;
|
||||
(void) user;
|
||||
switch (action) {
|
||||
case DMON_ACTION_MOVE:
|
||||
send_sdl_event(watch_id, DMON_ACTION_DELETE, oldfilepath);
|
||||
send_sdl_event(watch_id, DMON_ACTION_CREATE, filepath);
|
||||
break;
|
||||
default:
|
||||
send_sdl_event(watch_id, action, filepath);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#ifndef DIRMONITOR_H
|
||||
#define DIRMONITOR_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "dmon.h"
|
||||
#include "dmon_extra.h"
|
||||
|
||||
void dirmonitor_init();
|
||||
void dirmonitor_deinit();
|
||||
void dirmonitor_watch_callback(dmon_watch_id watch_id, dmon_action action, const char *rootdir,
|
||||
const char *filepath, const char *oldfilepath, void *user);
|
||||
|
||||
#endif
|
||||
|
|
@ -7,15 +7,13 @@
|
|||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#elif __linux__
|
||||
#elif __linux__ || __FreeBSD__
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#elif __APPLE__
|
||||
#include <mach-o/dyld.h>
|
||||
#endif
|
||||
|
||||
#include "dirmonitor.h"
|
||||
|
||||
|
||||
SDL_Window *window;
|
||||
|
||||
|
@ -108,8 +106,6 @@ int main(int argc, char **argv) {
|
|||
SDL_DisplayMode dm;
|
||||
SDL_GetCurrentDisplayMode(0, &dm);
|
||||
|
||||
dirmonitor_init();
|
||||
|
||||
window = SDL_CreateWindow(
|
||||
"", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, dm.w * 0.8, dm.h * 0.8,
|
||||
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN);
|
||||
|
@ -192,7 +188,6 @@ init_lua:
|
|||
|
||||
lua_close(L);
|
||||
ren_free_window_resources();
|
||||
dirmonitor_deinit();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
|
@ -4,13 +4,47 @@ lite_sources = [
|
|||
'api/regex.c',
|
||||
'api/system.c',
|
||||
'api/process.c',
|
||||
'dirmonitor.c',
|
||||
'renderer.c',
|
||||
'renwindow.c',
|
||||
'rencache.c',
|
||||
'main.c',
|
||||
]
|
||||
|
||||
# dirmonitor backend
|
||||
if get_option('dirmonitor_backend') == ''
|
||||
if cc.has_function('inotify_init', prefix : '#include<sys/inotify.h>')
|
||||
dirmonitor_backend = 'inotify'
|
||||
elif cc.has_function('kqueue', prefix : '#include<sys/event.h>')
|
||||
dirmonitor_backend = 'kqueue'
|
||||
elif dependency('libkqueue', required : false).found()
|
||||
dirmonitor_backend = 'kqueue'
|
||||
elif host_machine.system() == 'windows'
|
||||
dirmonitor_backend = 'win32'
|
||||
else
|
||||
dirmonitor_backend = 'dummy'
|
||||
warning('no suitable backend found, defaulting to dummy backend')
|
||||
endif
|
||||
else
|
||||
dirmonitor_backend = get_option('dirmonitor_backend')
|
||||
endif
|
||||
|
||||
message('dirmonitor_backend: @0@'.format(dirmonitor_backend))
|
||||
|
||||
if dirmonitor_backend == 'kqueue'
|
||||
libkqueue_dep = dependency('libkqueue', required : false)
|
||||
if libkqueue_dep.found()
|
||||
lite_deps += libkqueue_dep
|
||||
endif
|
||||
endif
|
||||
|
||||
lite_sources += [
|
||||
'api/dirmonitor.c',
|
||||
'api/dirmonitor/' + dirmonitor_backend + '.c',
|
||||
]
|
||||
lite_cargs += '-DDIRMONITOR_BACKEND=' + dirmonitor_backend
|
||||
lite_cargs += '-DDIRMONITOR_' + dirmonitor_backend.to_upper()
|
||||
|
||||
|
||||
lite_rc = []
|
||||
if host_machine.system() == 'windows'
|
||||
windows = import('windows')
|
||||
|
|
|
@ -95,7 +95,7 @@ static Command* push_command(int type, int size) {
|
|||
return NULL;
|
||||
}
|
||||
command_buf_idx = n;
|
||||
memset(cmd, 0, COMMAND_BARE_SIZE);
|
||||
memset(cmd, 0, size);
|
||||
cmd->type = type;
|
||||
cmd->size = size;
|
||||
return cmd;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <ft2build.h>
|
||||
|
@ -42,7 +43,7 @@ typedef struct RenFont {
|
|||
FT_Face face;
|
||||
GlyphSet* sets[SUBPIXEL_BITMAPS_CACHED][MAX_LOADABLE_GLYPHSETS];
|
||||
float size, space_advance, tab_advance;
|
||||
short max_height;
|
||||
short max_height, baseline, height;
|
||||
ERenFontAntialiasing antialiasing;
|
||||
ERenFontHinting hinting;
|
||||
unsigned char style;
|
||||
|
@ -113,8 +114,10 @@ static void font_load_glyphset(RenFont* font, int idx) {
|
|||
font->sets[j][idx] = set;
|
||||
for (int i = 0; i < MAX_GLYPHSET; ++i) {
|
||||
int glyph_index = FT_Get_Char_Index(font->face, i + idx * MAX_GLYPHSET);
|
||||
if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, load_option | FT_LOAD_BITMAP_METRICS_ONLY) || font_set_style(&font->face->glyph->outline, j * (64 / SUBPIXEL_BITMAPS_CACHED), font->style) || FT_Render_Glyph(font->face->glyph, render_option))
|
||||
if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, load_option | FT_LOAD_BITMAP_METRICS_ONLY)
|
||||
|| font_set_style(&font->face->glyph->outline, j * (64 / SUBPIXEL_BITMAPS_CACHED), font->style) || FT_Render_Glyph(font->face->glyph, render_option)) {
|
||||
continue;
|
||||
}
|
||||
FT_GlyphSlot slot = font->face->glyph;
|
||||
int glyph_width = slot->bitmap.width / byte_width;
|
||||
if (font->antialiasing == FONT_ANTIALIASING_NONE)
|
||||
|
@ -122,6 +125,13 @@ static void font_load_glyphset(RenFont* font, int idx) {
|
|||
set->metrics[i] = (GlyphMetric){ pen_x, pen_x + glyph_width, 0, slot->bitmap.rows, true, slot->bitmap_left, slot->bitmap_top, (slot->advance.x + slot->lsb_delta - slot->rsb_delta) / 64.0f};
|
||||
pen_x += glyph_width;
|
||||
font->max_height = slot->bitmap.rows > font->max_height ? slot->bitmap.rows : font->max_height;
|
||||
// In order to fix issues with monospacing; we need the unhinted xadvance; as FreeType doesn't correctly report the hinted advance for spaces on monospace fonts (like RobotoMono). See #843.
|
||||
if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, (load_option | FT_LOAD_BITMAP_METRICS_ONLY | FT_LOAD_NO_HINTING) & ~FT_LOAD_FORCE_AUTOHINT)
|
||||
|| font_set_style(&font->face->glyph->outline, j * (64 / SUBPIXEL_BITMAPS_CACHED), font->style) || FT_Render_Glyph(font->face->glyph, render_option)) {
|
||||
continue;
|
||||
}
|
||||
slot = font->face->glyph;
|
||||
set->metrics[i].xadvance = slot->advance.x / 64.0f;
|
||||
}
|
||||
if (pen_x == 0)
|
||||
continue;
|
||||
|
@ -184,10 +194,12 @@ RenFont* ren_font_load(const char* path, float size, ERenFontAntialiasing antial
|
|||
strcpy(font->path, path);
|
||||
font->face = face;
|
||||
font->size = size;
|
||||
font->height = (short)((face->height / (float)face->units_per_EM) * font->size);
|
||||
font->baseline = (short)((face->bbox.yMax / (float)face->units_per_EM) * font->size);
|
||||
font->antialiasing = antialiasing;
|
||||
font->hinting = hinting;
|
||||
font->style = style;
|
||||
font->space_advance = (int)font_get_glyphset(font, ' ', 0)->metrics[' '].xadvance;
|
||||
font->space_advance = font_get_glyphset(font, ' ', 0)->metrics[' '].xadvance;
|
||||
font->tab_advance = font->space_advance * 2;
|
||||
return font;
|
||||
failure:
|
||||
|
@ -228,7 +240,7 @@ float ren_font_group_get_size(RenFont **fonts) {
|
|||
return fonts[0]->size;
|
||||
}
|
||||
int ren_font_group_get_height(RenFont **fonts) {
|
||||
return fonts[0]->size + 3;
|
||||
return fonts[0]->height;
|
||||
}
|
||||
|
||||
float ren_font_group_get_width(RenFont **fonts, const char *text) {
|
||||
|
@ -238,8 +250,8 @@ float ren_font_group_get_width(RenFont **fonts, const char *text) {
|
|||
while (text < end) {
|
||||
unsigned int codepoint;
|
||||
text = utf8_to_codepoint(text, &codepoint);
|
||||
font_group_get_glyph(&set, &metric, fonts, codepoint, 0);
|
||||
width += metric->xadvance ? metric->xadvance : fonts[0]->space_advance;
|
||||
RenFont* font = font_group_get_glyph(&set, &metric, fonts, codepoint, 0);
|
||||
width += (!font || metric->xadvance) ? metric->xadvance : fonts[0]->space_advance;
|
||||
}
|
||||
const int surface_scale = renwin_surface_scale(&window_renderer);
|
||||
return width / surface_scale;
|
||||
|
@ -270,14 +282,14 @@ float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor
|
|||
if (set->surface && color.a > 0 && end_x >= clip.x && start_x < clip_end_x) {
|
||||
unsigned char* source_pixels = set->surface->pixels;
|
||||
for (int line = metric->y0; line < metric->y1; ++line) {
|
||||
int target_y = line + y - metric->y0 - metric->bitmap_top + font->size * surface_scale;
|
||||
int target_y = line + y - metric->bitmap_top + font->baseline * surface_scale;
|
||||
if (target_y < clip.y)
|
||||
continue;
|
||||
if (target_y >= clip_end_y)
|
||||
break;
|
||||
if (start_x + (glyph_end - glyph_start) >= clip_end_x)
|
||||
glyph_end = glyph_start + (clip_end_x - start_x);
|
||||
else if (start_x < clip.x) {
|
||||
if (start_x < clip.x) {
|
||||
int offset = clip.x - start_x;
|
||||
start_x += offset;
|
||||
glyph_start += offset;
|
||||
|
@ -331,18 +343,23 @@ void ren_draw_rect(RenRect rect, RenColor color) {
|
|||
y2 = y2 > clip.y + clip.height ? clip.y + clip.height : y2;
|
||||
|
||||
SDL_Surface *surface = renwin_get_surface(&window_renderer);
|
||||
RenColor *d = (RenColor*) surface->pixels;
|
||||
uint32_t *d = surface->pixels;
|
||||
d += x1 + y1 * surface->w;
|
||||
int dr = surface->w - (x2 - x1);
|
||||
unsigned int translated = SDL_MapRGB(surface->format, color.r, color.g, color.b);
|
||||
if (color.a == 0xff) {
|
||||
uint32_t translated = SDL_MapRGB(surface->format, color.r, color.g, color.b);
|
||||
SDL_Rect rect = { x1, y1, x2 - x1, y2 - y1 };
|
||||
SDL_FillRect(surface, &rect, translated);
|
||||
} else {
|
||||
RenColor translated_color = (RenColor){ translated & 0xFF, (translated >> 8) & 0xFF, (translated >> 16) & 0xFF, color.a };
|
||||
RenColor current_color;
|
||||
RenColor blended_color;
|
||||
for (int j = y1; j < y2; j++) {
|
||||
for (int i = x1; i < x2; i++, d++)
|
||||
*d = blend_pixel(*d, translated_color);
|
||||
{
|
||||
SDL_GetRGB(*d, surface->format, ¤t_color.r, ¤t_color.g, ¤t_color.b);
|
||||
blended_color = blend_pixel(current_color, color);
|
||||
*d = SDL_MapRGB(surface->format, blended_color.r, blended_color.g, blended_color.b);
|
||||
}
|
||||
d += dr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
[wrap-file]
|
||||
directory = freetype-2.11.1
|
||||
source_url = https://download.savannah.gnu.org/releases/freetype/freetype-2.11.1.tar.gz
|
||||
source_filename = freetype-2.11.1.tar.gz
|
||||
source_hash = f8db94d307e9c54961b39a1cc799a67d46681480696ed72ecf78d4473770f09b
|
||||
|
||||
[provide]
|
||||
freetype2 = freetype_dep
|
||||
|
|
@ -1,4 +1,12 @@
|
|||
[wrap-git]
|
||||
directory = lua
|
||||
url = https://github.com/franko/lua
|
||||
revision = v5.2.4-7
|
||||
[wrap-file]
|
||||
directory = lua-5.4.3
|
||||
source_url = https://www.lua.org/ftp/lua-5.4.3.tar.gz
|
||||
source_filename = lua-5.4.3.tar.gz
|
||||
source_hash = f8612276169e3bfcbcfb8f226195bfc6e466fe13042f1076cbde92b7ec96bbfb
|
||||
patch_filename = lua_5.4.3-2_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/lua_5.4.3-2/get_patch
|
||||
patch_hash = 3c23ec14a3f000d80fe2e2fdddba63a56e13c758d74195daa4ff0da7bfdb02da
|
||||
|
||||
[provide]
|
||||
lua-5.4 = lua_dep
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
[wrap-file]
|
||||
directory = pcre2-10.39
|
||||
source_url = https://github.com/PhilipHazel/pcre2/releases/download/pcre2-10.39/pcre2-10.39.tar.bz2
|
||||
source_filename = pcre2-10.39.tar.bz2
|
||||
source_hash = 0f03caf57f81d9ff362ac28cd389c055ec2bf0678d277349a1a4bee00ad6d440
|
||||
patch_filename = pcre2_10.39-2_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/pcre2_10.39-2/get_patch
|
||||
patch_hash = c4cfffff83e7bb239c8c330339b08f4367b019f79bf810f10c415e35fb09cf14
|
||||
|
||||
[provide]
|
||||
libpcre2-8 = -libpcre2_8
|
||||
libpcre2-16 = -libpcre2_16
|
||||
libpcre2-32 = -libpcre2_32
|
||||
libpcre2-posix = -libpcre2_posix
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
[wrap-file]
|
||||
directory = SDL2-2.0.18
|
||||
source_url = https://www.libsdl.org/release/SDL2-2.0.18.tar.gz
|
||||
source_filename = SDL2-2.0.18.tar.gz
|
||||
source_hash = 94d40cd73dbfa10bb6eadfbc28f355992bb2d6ef6761ad9d4074eff95ee5711c
|
||||
patch_filename = sdl2_2.0.18-2_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/sdl2_2.0.18-2/get_patch
|
||||
patch_hash = cd77f33395d3d019bb89217b9da41fc640ed8c78cbbbebc5c662155a25e2820e
|
||||
|
||||
[provide]
|
||||
sdl2 = sdl2_dep
|
||||
|
Loading…
Reference in New Issue