feat: make UI extensions easier

This commit is contained in:
sxyazi 2024-07-04 23:54:35 +08:00
parent 11547eefe0
commit 6f665fe887
No known key found for this signature in database
18 changed files with 218 additions and 147 deletions

44
Cargo.lock generated
View File

@ -206,9 +206,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "block-buffer"
@ -303,9 +303,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.7"
version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d"
dependencies = [
"clap_builder",
"clap_derive",
@ -313,9 +313,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.7"
version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708"
dependencies = [
"anstream",
"anstyle",
@ -325,9 +325,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.6"
version = "4.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbca90c87c2a04da41e95d1856e8bcd22f159bdbfa147314d2ce5218057b0e58"
checksum = "1d598e88f6874d4b888ed40c71efbcbf4076f1dfbae128a08a8c9e45f710605d"
dependencies = [
"clap",
]
@ -354,9 +354,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.5"
version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
dependencies = [
"heck",
"proc-macro2",
@ -480,7 +480,7 @@ version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"crossterm_winapi",
"futures-core",
"libc",
@ -1110,7 +1110,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"libc",
]
@ -1282,7 +1282,7 @@ version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"filetime",
"fsevent-sys",
"inotify",
@ -1366,7 +1366,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"block2",
"libc",
"objc2",
@ -1588,7 +1588,7 @@ version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"cassowary",
"compact_str",
"crossterm",
@ -1638,7 +1638,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
]
[[package]]
@ -1699,7 +1699,7 @@ version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"errno",
"libc",
"linux-raw-sys",
@ -1765,9 +1765,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.117"
version = "1.0.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
dependencies = [
"itoa",
"ryu",
@ -2796,7 +2796,7 @@ version = "0.2.5"
dependencies = [
"anyhow",
"arc-swap",
"bitflags 2.5.0",
"bitflags 2.6.0",
"crossterm",
"globset",
"indexmap",
@ -2812,7 +2812,7 @@ name = "yazi-core"
version = "0.2.5"
dependencies = [
"anyhow",
"bitflags 2.5.0",
"bitflags 2.6.0",
"crossterm",
"dirs",
"futures",
@ -2962,7 +2962,7 @@ name = "yazi-shared"
version = "0.2.5"
dependencies = [
"anyhow",
"bitflags 2.5.0",
"bitflags 2.6.0",
"crossterm",
"dirs",
"futures",

View File

@ -15,12 +15,12 @@ yazi-config = { path = "../yazi-config", version = "0.2.5" }
yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
clap = { version = "4.5.7", features = [ "derive" ] }
clap = { version = "4.5.8", features = [ "derive" ] }
serde = { version = "1.0.203", features = [ "derive" ] }
[build-dependencies]
clap = { version = "4.5.7", features = [ "derive" ] }
clap_complete = "4.5.6"
clap = { version = "4.5.8", features = [ "derive" ] }
clap_complete = "4.5.7"
clap_complete_nushell = "4.5.2"
clap_complete_fig = "4.5.1"
vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] }

View File

@ -14,20 +14,20 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
anyhow = "1.0.86"
clap = { version = "4.5.7", features = [ "derive" ] }
clap = { version = "4.5.8", features = [ "derive" ] }
crossterm = "0.27.0"
md-5 = "0.10.6"
serde_json = "1.0.117"
serde_json = "1.0.120"
tokio = { version = "1.38.0", features = [ "full" ] }
toml_edit = "0.22.14"
[build-dependencies]
anyhow = "1.0.86"
clap = { version = "4.5.7", features = [ "derive" ] }
clap_complete = "4.5.6"
clap = { version = "4.5.8", features = [ "derive" ] }
clap_complete = "4.5.7"
clap_complete_fig = "4.5.1"
clap_complete_nushell = "4.5.2"
serde_json = "1.0.117"
serde_json = "1.0.120"
vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] }
[[bin]]

View File

@ -14,7 +14,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
anyhow = "1.0.86"
arc-swap = "1.7.1"
bitflags = "2.5.0"
bitflags = "2.6.0"
crossterm = "0.27.0"
globset = "0.4.14"
indexmap = "2.2.6"

View File

@ -20,7 +20,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
anyhow = "1.0.86"
bitflags = "2.5.0"
bitflags = "2.6.0"
crossterm = "0.27.0"
dirs = "5.0.1"
futures = "0.3.30"

View File

@ -21,7 +21,7 @@ anyhow = "1.0.86"
mlua = { version = "0.9.9", features = [ "lua54" ] }
parking_lot = "0.12.3"
serde = { version = "1.0.203", features = [ "derive" ] }
serde_json = "1.0.117"
serde_json = "1.0.120"
tokio = { version = "1.38.0", features = [ "full" ] }
tokio-stream = "0.1.15"
tokio-util = "0.7.11"

View File

@ -55,11 +55,11 @@ impl Lives {
let ret = f(scope)?;
LAYOUT.store(Arc::new(yazi_config::Layout {
header: *globals.raw_get::<_, Table>("Header")?.raw_get::<_, RectRef>("area")?,
parent: *globals.raw_get::<_, Table>("Parent")?.raw_get::<_, RectRef>("area")?,
current: *globals.raw_get::<_, Table>("Current")?.raw_get::<_, RectRef>("area")?,
preview: *globals.raw_get::<_, Table>("Preview")?.raw_get::<_, RectRef>("area")?,
status: *globals.raw_get::<_, Table>("Status")?.raw_get::<_, RectRef>("area")?,
header: *globals.raw_get::<_, Table>("Header")?.raw_get::<_, RectRef>("_area")?,
parent: *globals.raw_get::<_, Table>("Parent")?.raw_get::<_, RectRef>("_area")?,
current: *globals.raw_get::<_, Table>("Current")?.raw_get::<_, RectRef>("_area")?,
preview: *globals.raw_get::<_, Table>("Preview")?.raw_get::<_, RectRef>("_area")?,
status: *globals.raw_get::<_, Table>("Status")?.raw_get::<_, RectRef>("_area")?,
}));
Ok(ret)

View File

@ -1,5 +1,5 @@
Current = {
area = ui.Rect.default,
_area = ui.Rect.default,
}
function Current:empty(area)
@ -18,7 +18,7 @@ function Current:empty(area)
end
function Current:render(area)
self.area = area
self._area = area
local files = Folder:by_kind(Folder.CURRENT).window
if #files == 0 then
@ -27,7 +27,7 @@ function Current:render(area)
local items, markers = {}, {}
for i, f in ipairs(files) do
items[#items + 1] = ui.ListItem(ui.Line(File:full(f))):style(File:style(f))
items[#items + 1] = ui.ListItem(File:children_render(f)):style(File:style(f))
-- Yanked/marked/selected files
local marker = File:marker(f)

View File

@ -1,76 +1,6 @@
File = {}
function File:icon(file)
local icon = file:icon()
if not icon then
return {}
elseif file:is_hovered() then
return { ui.Span(" " .. icon.text .. " ") }
else
return { ui.Span(" " .. icon.text .. " "):style(icon.style) }
end
end
function File:prefix(file)
local prefix = file:prefix() or ""
return prefix == "" and {} or { ui.Span(prefix .. "/") }
end
function File:highlights(file)
local name = file.name:gsub("\r", "?", 1)
local highlights = file:highlights()
if not highlights or #highlights == 0 then
return { ui.Span(name) }
end
local spans, last = {}, 0
for _, h in ipairs(highlights) do
if h[1] > last then
spans[#spans + 1] = ui.Span(name:sub(last + 1, h[1]))
end
spans[#spans + 1] = ui.Span(name:sub(h[1] + 1, h[2])):style(THEME.manager.find_keyword)
last = h[2]
end
if last < #name then
spans[#spans + 1] = ui.Span(name:sub(last + 1))
end
return spans
end
function File:found(file)
if not file:is_hovered() then
return {}
end
local found = file:found()
if not found then
return {}
end
return {
ui.Span(" "),
ui.Span(string.format("[%d/%d]", found[1] + 1, found[2])):style(THEME.manager.find_position),
}
end
function File:symlink(file)
if not MANAGER.show_symlink then
return {}
end
local to = file.link_to
return to and { ui.Span(" -> " .. tostring(to)):italic() } or {}
end
function File:full(file)
return ya.flat {
self:icon(file),
self:prefix(file),
self:highlights(file),
self:found(file),
self:symlink(file),
}
end
File = {
_inc = 1000,
}
function File:style(file)
local style = file:style()
@ -97,3 +27,98 @@ function File:marker(file)
end
return 0
end
function File:icon(file)
local icon = file:icon()
if not icon then
return ui.Line("")
elseif file:is_hovered() then
return ui.Line(" " .. icon.text .. " ")
else
return ui.Line(" " .. icon.text .. " "):style(icon.style)
end
end
function File:prefix(file)
local prefix = file:prefix() or ""
return ui.Line(prefix ~= "" and prefix .. "/" or "")
end
function File:highlights(file)
local name = file.name:gsub("\r", "?", 1)
local highlights = file:highlights()
if not highlights or #highlights == 0 then
return ui.Line(name)
end
local spans, last = {}, 0
for _, h in ipairs(highlights) do
if h[1] > last then
spans[#spans + 1] = ui.Span(name:sub(last + 1, h[1]))
end
spans[#spans + 1] = ui.Span(name:sub(h[1] + 1, h[2])):style(THEME.manager.find_keyword)
last = h[2]
end
if last < #name then
spans[#spans + 1] = ui.Span(name:sub(last + 1))
end
return ui.Line(spans)
end
function File:found(file)
if not file:is_hovered() then
return ui.Line {}
end
local found = file:found()
if not found then
return ui.Line {}
end
return ui.Line {
ui.Span(" "),
ui.Span(string.format("[%d/%d]", found[1] + 1, found[2])):style(THEME.manager.find_position),
}
end
function File:symlink(file)
if not MANAGER.show_symlink then
return ui.Line {}
end
local to = file.link_to
return ui.Line(to and { ui.Span(" -> " .. tostring(to)):italic() } or {})
end
-- Initialize children
File._children = {
{ File.icon, id = 1, order = 1000 },
{ File.prefix, id = 2, order = 2000 },
{ File.highlights, id = 3, order = 3000 },
{ File.found, id = 4, order = 4000 },
{ File.symlink, id = 5, order = 5000 },
}
function File:children_add(fn, order)
self._inc = self._inc + 1
self._children[#self._children + 1] = { fn, id = self._inc, order = order }
table.sort(self._children, function(a, b) return a.order < b.order end)
return self._inc
end
function File:children_remove(id)
for i, child in ipairs(self._children) do
if child.id == id then
table.remove(self._children, i)
break
end
end
end
function File:children_render(file)
local lines = {}
for _, child in ipairs(self._children) do
lines[#lines + 1] = child[1](self, file)
end
return ui.Line(lines)
end

View File

@ -1,5 +1,5 @@
Header = {
area = ui.Rect.default,
_area = ui.Rect.default,
}
function Header:cwd(max)
@ -68,7 +68,7 @@ function Header:tabs()
end
function Header:render(area)
self.area = area
self._area = area
local right = ui.Line { self:count(), self:tabs() }
local left = ui.Line { self:cwd(math.max(0, area.w - right:width())) }

View File

@ -1,9 +1,9 @@
Manager = {
area = ui.Rect.default,
_area = ui.Rect.default,
}
function Manager:layout(area)
self.area = area
self._area = area
return ui.Layout()
:direction(ui.Layout.HORIZONTAL)

View File

@ -1,9 +1,9 @@
Parent = {
area = ui.Rect.default,
_area = ui.Rect.default,
}
function Parent:render(area)
self.area = area
self._area = area
local folder = Folder:by_kind(Folder.PARENT)
if not folder then
@ -12,7 +12,7 @@ function Parent:render(area)
local items, markers = {}, {}
for i, f in ipairs(folder.window) do
items[#items + 1] = ui.ListItem(ui.Line(File:full(f))):style(File:style(f))
items[#items + 1] = ui.ListItem(File:children_render(f)):style(File:style(f))
-- Yanked/marked/selected files
local marker = File:marker(f)

View File

@ -1,9 +1,9 @@
Preview = {
area = ui.Rect.default,
_area = ui.Rect.default,
}
function Preview:render(area)
self.area = area
self._area = area
return {}
end

View File

@ -1,9 +1,9 @@
Progress = {
area = ui.Rect.default,
_area = ui.Rect.default,
}
function Progress:render(area, offset)
self.area = ui.Rect {
self._area = ui.Rect {
x = math.max(0, area.w - offset - 21),
y = area.y,
w = ya.clamp(0, area.w - offset - 1, 20),
@ -15,16 +15,13 @@ end
-- Progress bars usually need frequent updates to report the latest task progress.
-- We use `partial_render()` to partially render it when there is progress change,
-- which has almost no cost compared to a full render by `render()`.
--
-- However, at this time, we can only access `cx.tasks`. If you need certain data from the complete `cx`,
-- just cache it to `self` during `render()`, and read it in `partial_render()` - this process is referred to as "composition".
function Progress:partial_render()
local progress = cx.tasks.progress
if progress.total == 0 then
return { ui.Paragraph(self.area, {}) }
return { ui.Paragraph(self._area, {}) }
end
local gauge = ui.Gauge(self.area)
local gauge = ui.Gauge(self._area)
if progress.fail == 0 then
gauge = gauge:gauge_style(THEME.status.progress_normal)
else

View File

@ -1,8 +1,12 @@
Status = {
area = ui.Rect.default,
LEFT = 0,
RIGHT = 1,
_area = ui.Rect.default,
_inc = 1000,
}
function Status.style()
function Status:style()
if cx.active.mode.is_select then
return THEME.status.mode_select
elseif cx.active.mode.is_unset then
@ -18,7 +22,7 @@ function Status:mode()
mode = "UN-SET"
end
local style = self.style()
local style = self:style()
return ui.Line {
ui.Span(THEME.status.separator_open):fg(style.bg),
ui.Span(" " .. mode .. " "):style(style),
@ -31,7 +35,7 @@ function Status:size()
return ui.Line {}
end
local style = self.style()
local style = self:style()
return ui.Line {
ui.Span(" " .. ya.readable_size(h:size() or h.cha.length) .. " "):fg(style.bg):bg(THEME.status.separator_style.bg),
ui.Span(THEME.status.separator_close):fg(THEME.status.separator_style.fg),
@ -41,10 +45,10 @@ end
function Status:name()
local h = cx.active.current.hovered
if not h then
return ui.Span("")
return ui.Line {}
end
return ui.Span(" " .. h.name)
return ui.Line(" " .. h.name)
end
function Status:permissions()
@ -92,7 +96,7 @@ function Status:percentage()
percent = string.format(" %3d%% ", percent)
end
local style = self.style()
local style = self:style()
return ui.Line {
ui.Span(" " .. THEME.status.separator_open):fg(THEME.status.separator_style.fg),
ui.Span(percent):fg(style.bg):bg(THEME.status.separator_style.bg),
@ -103,7 +107,7 @@ function Status:position()
local cursor = cx.active.current.cursor
local length = #cx.active.current.files
local style = self.style()
local style = self:style()
return ui.Line {
ui.Span(string.format(" %2d/%-2d ", cursor + 1, length)):style(style),
ui.Span(THEME.status.separator_close):fg(style.bg),
@ -111,10 +115,10 @@ function Status:position()
end
function Status:render(area)
self.area = area
self._area = area
local left = ui.Line { self:mode(), self:size(), self:name() }
local right = ui.Line { self:permissions(), self:percentage(), self:position() }
local left = self:children_render(self.LEFT)
local right = self:children_render(self.RIGHT)
return {
ui.Paragraph(area, { left }),
ui.Paragraph(area, { right }):align(ui.Paragraph.RIGHT),
@ -122,8 +126,49 @@ function Status:render(area)
}
end
-- Mouse events
function Status:click(event, up) end
function Status:scroll(event, step) end
function Status:touch(event, step) end
-- Initialize children
Status._left = {
{ Status.mode, id = 1, order = 1000 },
{ Status.size, id = 2, order = 2000 },
{ Status.name, id = 3, order = 3000 },
}
Status._right = {
{ Status.permissions, id = 4, order = 1000 },
{ Status.percentage, id = 5, order = 2000 },
{ Status.position, id = 6, order = 3000 },
}
function Status:children_add(fn, order, side)
self._inc = self._inc + 1
local children = side == self.RIGHT and self._right or self._left
children[#children + 1] = { fn, id = self._inc, order = order }
table.sort(children, function(a, b) return a.order < b.order end)
return self._inc
end
function Status:children_remove(id, side)
local children = side == self.RIGHT and self._right or self._left
for i, child in ipairs(children) do
if child.id == id then
table.remove(children, i)
break
end
end
end
function Status:children_render(side)
local lines = {}
for _, child in ipairs(side == self.RIGHT and self._right or self._left) do
lines[#lines + 1] = child[1](self)
end
return ui.Line(lines)
end

View File

@ -20,7 +20,7 @@ function M:peek()
local items, markers = {}, {}
for i, f in ipairs(folder.window) do
items[#items + 1] = ui.ListItem(ui.Line(File:full(f))):style(File:style(f))
items[#items + 1] = ui.ListItem(File:children_render(f)):style(File:style(f))
-- Yanked/marked/selected files
local marker = File:marker(f)

View File

@ -23,7 +23,11 @@ impl TryFrom<Table<'_>> for Line {
if let Ok(span) = ud.take::<Span>() {
spans.push(span.0);
} else if let Ok(line) = ud.take::<Line>() {
spans.extend(line.0.spans.into_iter().collect::<Vec<_>>());
let style = line.0.style;
spans.extend(line.0.spans.into_iter().map(|mut span| {
span.style = style.patch(span.style);
span
}));
} else {
return Err("expected a table of Spans or Lines".into_lua_err());
}
@ -83,9 +87,9 @@ impl UserData for Line {
{
let mut me = ud.borrow_mut::<Self>()?;
me.0.style = match value {
Value::Nil => me.0.style.patch(ratatui::style::Style::reset()),
Value::Table(tb) => me.0.style.patch(Style::try_from(tb)?.0),
Value::UserData(ud) => me.0.style.patch(ud.borrow::<Style>()?.0),
Value::Nil => ratatui::style::Style::default(),
Value::Table(tb) => Style::try_from(tb)?.0,
Value::UserData(ud) => ud.borrow::<Style>()?.0,
_ => return Err("expected a Style or Table or nil".into_lua_err()),
};
}

View File

@ -11,7 +11,7 @@ rust-version = "1.78.0"
[dependencies]
anyhow = "1.0.86"
bitflags = "2.5.0"
bitflags = "2.6.0"
crossterm = "0.27.0"
dirs = "5.0.1"
futures = "0.3.30"