-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcode_outline.lua
More file actions
157 lines (137 loc) · 5.28 KB
/
code_outline.lua
File metadata and controls
157 lines (137 loc) · 5.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
-- MCP Tool: code_outline
-- Generate a structural outline: functions, types, imports, variables
-- Uses language-specific Tree-sitter queries for accurate extraction
-- Entry kind: function.lua
local json = require("json")
local treesitter = require("treesitter")
local parser = require("parser")
local queries = require("queries")
--- Run a query pattern safely, returning captures or empty table
local function safe_captures(lang, pattern, root, code)
if not pattern then return {} end
local query, err = treesitter.query(lang, pattern)
if err then return {} end
local captures = query:captures(root, code)
return captures or {}
end
--- Group captures by their @name
local function group_captures(captures)
local groups = {}
for _, cap in ipairs(captures) do
if not groups[cap.name] then
groups[cap.name] = {}
end
table.insert(groups[cap.name], cap)
end
return groups
end
--- Build a section from captures, deduplicating by text
local function build_section(title, captures)
if not captures or #captures == 0 then return nil end
local items = {}
local seen = {}
for _, cap in ipairs(captures) do
local text = cap.text
if text and not seen[text] then
seen[text] = true
local pos = parser.node_pos(cap.node)
table.insert(items, {
name = text,
line = pos.start_line,
end_line = pos.end_line,
})
end
end
if #items == 0 then return nil end
return { title = title, count = #items, items = items }
end
--- Fallback: list top-level named children
local function fallback_outline(root)
local children = {}
local count = root:child_count()
for i = 0, count - 1 do
local child = root:child(i)
if child and child:is_named() then
local pos = parser.node_pos(child)
local text = child:text()
if #text > 80 then text = string.sub(text, 1, 77) .. "..." end
table.insert(children, {
kind = child:kind(),
line = pos.start_line,
text = text,
})
end
end
if #children == 0 then return nil end
return { title = "Top-level nodes", count = #children, items = children }
end
local function call(arguments)
local code, lang, err = parser.resolve_source(arguments)
if err then return "Error: " .. err end
local tree, root, parse_err = parser.parse(lang, code)
if parse_err then return "Error: " .. parse_err end
local patterns = queries.get_patterns(lang)
local sections = {}
if patterns then
-- Functions & Methods
local fcaps = safe_captures(lang, patterns.functions, root, code)
local fgroups = group_captures(fcaps)
local fsec = build_section("Functions", fgroups["name"])
if fsec then table.insert(sections, fsec) end
-- Types / Classes
local tcaps = safe_captures(lang, patterns.types, root, code)
local tgroups = group_captures(tcaps)
local tsec = build_section("Types", tgroups["name"])
if tsec then table.insert(sections, tsec) end
-- Imports
local icaps = safe_captures(lang, patterns.imports, root, code)
local igroups = group_captures(icaps)
local isec = build_section("Imports",
igroups["path"] or igroups["name"]
or igroups["module"] or igroups["source"])
if isec then table.insert(sections, isec) end
-- Variables / Constants
local vcaps = safe_captures(lang, patterns.variables, root, code)
local vgroups = group_captures(vcaps)
local vsec = build_section("Variables", vgroups["name"])
if vsec then table.insert(sections, vsec) end
-- Exports (JS/TS only)
if patterns.exports then
local ecaps = safe_captures(lang, patterns.exports, root, code)
local egroups = group_captures(ecaps)
local esec = build_section("Exports", egroups["export"])
if esec then table.insert(sections, esec) end
end
else
-- No language-specific patterns — fallback
local fb = fallback_outline(root)
if fb then table.insert(sections, fb) end
end
-- Format output
local lines = {}
table.insert(lines, "# Code Outline (" .. lang .. ")")
if parser.has_errors(root) then
table.insert(lines, "⚠ Source contains syntax errors")
end
table.insert(lines, "")
if #sections == 0 then
table.insert(lines, "No symbols found.")
end
for _, section in ipairs(sections) do
table.insert(lines, "## " .. section.title .. " (" .. section.count .. ")")
for _, item in ipairs(section.items) do
local name = item.name or item.text or item.kind
if #name > 100 then name = string.sub(name, 1, 97) .. "..." end
local range = ""
if item.end_line and item.end_line ~= item.line then
range = string.format("L%d-%d", item.line, item.end_line)
else
range = string.format("L%d", item.line)
end
table.insert(lines, string.format(" %-10s %s", range, name))
end
table.insert(lines, "")
end
return table.concat(lines, "\n")
end
return { call = call }