Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,8 @@ require("flutter-tools").setup {
},
outline = {
open_cmd = "30vnew", -- command to use to open the outline buffer
auto_open = false -- if true this will open the outline automatically when it is first populated
auto_open = false, -- if true this will open the outline automatically when it is first populated
icons = true, -- false hides the icons (e.g. without a Nerd Font); a table overrides icons per kind, e.g. { CLASS = "C", DEFAULT = "*" }
},
lsp = {
on_attach = my_custom_on_attach,
Expand Down
6 changes: 5 additions & 1 deletion lua/flutter-tools/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.util
---@field debugger? {enabled: boolean, exception_breakpoints?: table, evaluate_to_string_in_debug_views?: boolean, register_configurations?: fun(paths: table)}
---@field closing_tags? {highlight: string, prefix: string, priority: number, enabled: boolean}
---@field lsp? {debug?: number, color?: {enabled: boolean, background: boolean, foreground: boolean, virtual_text: boolean, virtual_text_str: string, background_color?: string}, settings?: table}
---@field outline? {auto_open: boolean, open_cmd?: string}
---@field outline? {auto_open: boolean, open_cmd?: string, icons?: boolean|table<string, string>}
---@field dev_log? flutter.DevLogOpts
---@field dev_tools? {autostart: boolean, auto_open_browser: boolean}
---@field widget_preview? {web_server: boolean}
Expand Down Expand Up @@ -75,6 +75,9 @@ local function validate_prefs(prefs)
)
end
vim.validate("outline", prefs.outline, "table", true)
if prefs.outline then
vim.validate("outline.icons", prefs.outline.icons, { "boolean", "table" }, true)
end
vim.validate("dev_log", prefs.dev_log, "table", true)
vim.validate("closing_tags", prefs.closing_tags, "table", true)
end
Expand Down Expand Up @@ -142,6 +145,7 @@ local config = {
},
outline = setmetatable({
auto_open = false,
icons = true,
}, {
__index = function(_, k) return k == "open_cmd" and get_split_cmd(0.3, 40) or nil end,
}),
Expand Down
52 changes: 32 additions & 20 deletions lua/flutter-tools/outline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ local markers = {
horizontal = "─",
}

local icons = setmetatable({
local default_icons = {
TOP_LEVEL_VARIABLE = "",
CLASS = "",
FIELD = "󰐾",
Expand All @@ -38,27 +38,38 @@ local icons = setmetatable({
ENUM = "󰉺",
ENUM_CONSTANT = "",
DEFAULT = "",
}, {
__index = function(t, _) return t.DEFAULT end,
})
}

local HL_PREFIX = "FlutterToolsOutline"
local MARKER_HL = "FlutterToolsOutlineIndentGuides"

local icon_highlights = {
[icons.TOP_LEVEL_VARIABLE] = { name = "TopLevelVar", link = "Identifier" },
[icons.CLASS] = { name = "Class", link = "Type" },
[icons.FIELD] = { name = "Field", link = "Identifier" },
[icons.CONSTRUCTOR] = { name = "Constructor", link = "Identifier" },
[icons.CONSTRUCTOR_INVOCATION] = { name = "ConstructorInvocation", link = "Special" },
[icons.FUNCTION] = { name = "Function", link = "Function" },
[icons.METHOD] = { name = "Method", link = "Function" },
[icons.GETTER] = { name = "Getter", link = "Function" },
[icons.ENUM] = { name = "Enum", link = "Type" },
[icons.ENUM_CONSTANT] = { name = "EnumConstant", link = "Type" },
[icons.DEFAULT] = { name = "Default", link = ui.DIM_HL },
TOP_LEVEL_VARIABLE = { name = "TopLevelVar", link = "Identifier" },
CLASS = { name = "Class", link = "Type" },
FIELD = { name = "Field", link = "Identifier" },
CONSTRUCTOR = { name = "Constructor", link = "Identifier" },
CONSTRUCTOR_INVOCATION = { name = "ConstructorInvocation", link = "Special" },
FUNCTION = { name = "Function", link = "Function" },
METHOD = { name = "Method", link = "Function" },
GETTER = { name = "Getter", link = "Function" },
ENUM = { name = "Enum", link = "Type" },
ENUM_CONSTANT = { name = "EnumConstant", link = "Type" },
DEFAULT = { name = "Default", link = ui.DIM_HL },
}

---@param kind string?
---@return string? icon
---@return string? highlight
local function get_icon(kind)
local opts = config.outline.icons
if opts == false then return end
local overrides = type(opts) == "table" and opts or {}
kind = kind or "DEFAULT"
local icon = overrides[kind] or default_icons[kind] or overrides.DEFAULT or default_icons.DEFAULT
if icon == "" then return end
return icon, HL_PREFIX .. (icon_highlights[kind] or icon_highlights.DEFAULT).name
end

api.nvim_set_hl(0, MARKER_HL, { default = true, link = "NonText" })

-----------------------------------------------------------------------------//
Expand Down Expand Up @@ -103,8 +114,8 @@ local function set_outline_highlights()
for key, value in pairs(markers) do
highlight_item(key:gsub("^%l", string.upper), value, MARKER_HL)
end
for icon, hl in pairs(icon_highlights) do
highlight_item(hl.name, icon, hl.link)
for _, hl in pairs(icon_highlights) do
hl_link(hl.name, hl.link)
end
end

Expand Down Expand Up @@ -142,10 +153,11 @@ local function parse_outline(result, node, indent, marker)
local range = node.codeRange
local element = node.element or {}
local text = {}
local icon = icons[element.kind]
local display_str = { indent, marker, icon }

local display_str = { indent, marker }
local hl = {}

local icon, icon_hl = get_icon(element.kind)
if icon then add_segment(display_str, hl, icon, icon_hl, #table.concat(display_str, " ")) end
local length = #table.concat(display_str, " ")

local return_type = element.returnType and element.returnType .. " "
Expand Down
58 changes: 58 additions & 0 deletions tests/outline_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
describe("outline", function()
local config
local outline
local uri = "file:///tmp/main.dart"
local class_icon = "\xef\x83\xa8"
local method_icon = "\xf3\xb0\x86\xa7"

local function node(kind, name, children)
return {
element = { kind = kind, name = name },
codeRange = { start = { line = 0, character = 0 }, ["end"] = { line = 1, character = 0 } },
children = children,
}
end

local function render(outline_config)
config.set({ outline = outline_config })
outline.document_outline(nil, {
uri = uri,
outline = { children = { node("CLASS", "Foo", { node("METHOD", "bar") }) } },
})
return outline.outlines[uri]
end

before_each(function()
package.loaded["flutter-tools.config"] = nil
package.loaded["flutter-tools.outline"] = nil
config = require("flutter-tools.config")
outline = require("flutter-tools.outline")
end)

it("shows icons highlighted by kind by default", function()
local result = render(nil)
assert.are.same(" " .. class_icon .. " Foo", result[1].text)
assert.are.same({
value = class_icon,
highlight = "FlutterToolsOutlineClass",
column_start = 2,
column_end = 2 + #class_icon,
}, result[1].hl[1])
assert.are.same("FlutterToolsOutlineMethod", result[2].hl[1].highlight)
end)

it("hides icons and their highlights when disabled", function()
local result = render({ icons = false })
assert.are.same(" Foo", result[1].text)
assert.are.same(" └ bar", result[2].text)
assert.are.same("None", result[1].hl[1].highlight)
assert.are.same(2, result[1].hl[1].column_start)
end)

it("uses icon overrides and falls back to the defaults", function()
local result = render({ icons = { CLASS = "C" } })
assert.are.same(" C Foo", result[1].text)
assert.are.same(4, result[1].hl[2].column_start)
assert.are.same(" └ " .. method_icon .. " bar", result[2].text)
end)
end)
Loading