-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdirectory_list.lua
More file actions
184 lines (156 loc) · 5.75 KB
/
Copy pathdirectory_list.lua
File metadata and controls
184 lines (156 loc) · 5.75 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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
-- MCP Tool: directory_list
-- List directories and files with depth, glob patterns, type filter, and tree view
-- Entry kind: function.lua
local json = require("json")
local projects = require("projects")
--- Convert a glob pattern to a Lua pattern
local function glob_to_lua(glob)
local p = string.gsub(glob, "[%(%)%.%%%+%-%^%$%[%]]", "%%%1")
p = string.gsub(p, "%*", ".*")
p = string.gsub(p, "%?", ".")
return "^" .. p .. "$"
end
--- Check if filename matches comma-separated glob patterns
local function match_glob(name, globs)
for pat in string.gmatch(globs, "[^,]+") do
pat = pat:match("^%s*(.-)%s*$") -- trim
if string.match(name, glob_to_lua(pat)) then
return true
end
end
return false
end
--- Directories to skip
local SKIP_DIRS = {
[".git"] = true, [".idea"] = true, [".vscode"] = true,
["vendor"] = true, ["node_modules"] = true,
[".wippy"] = true, [".claude"] = true
}
local function call(arguments)
local vol, err = projects.resolve(arguments.project)
if err then
return "Error: " .. err
end
local base_path = arguments.path
if not base_path or base_path == "" then
base_path = "."
end
local max_depth = arguments.depth or 0
local file_pattern = arguments.pattern
local type_filter = arguments.type or "any"
local show_tree = arguments.showTree or false
local show_size = arguments.showSize or false
local max_results = arguments.maxResults or 500
local results = {}
local tree_lines = {}
local total_matched = 0
--- Scan a directory recursively
local function scan(dir, current_depth, prefix)
if total_matched >= max_results then return end
-- Collect entries first for sorting and tree rendering
local entries = {}
local ok, _ = pcall(function()
for entry in vol:readdir(dir) do
-- Skip hidden
if string.sub(entry.name, 1, 1) == "." then
goto continue
end
table.insert(entries, entry)
::continue::
end
end)
if not ok then return end
-- Sort: directories first, then by name
table.sort(entries, function(a, b)
if a.type ~= b.type then
return a.type == "directory"
end
return a.name < b.name
end)
for idx, entry in ipairs(entries) do
if total_matched >= max_results then return end
local name = entry.name
local full_path = dir == "." and name or (dir .. "/" .. name)
local is_dir = entry.type == "directory"
local is_last = idx == #entries
-- Type filtering
local type_match = type_filter == "any"
or (type_filter == "file" and not is_dir)
or (type_filter == "directory" and is_dir)
-- Pattern filtering (only for files, dirs always pass for recursion)
local pattern_match = true
if file_pattern and not is_dir then
pattern_match = match_glob(name, file_pattern)
end
-- Add to results if matches filters
if type_match and (is_dir or pattern_match) then
local item = {
name = name,
path = full_path,
isDirectory = is_dir
}
if show_size and not is_dir then
local stat = vol:stat(full_path)
if stat then
item.size = stat.size
end
end
-- Only add files if pattern matches, always add dirs
if not is_dir and pattern_match then
table.insert(results, item)
total_matched = total_matched + 1
elseif is_dir then
table.insert(results, item)
total_matched = total_matched + 1
end
end
-- Tree view
if show_tree then
local connector = is_last and "└── " or "├── "
local display = name
if is_dir then display = display .. "/" end
if show_size and not is_dir then
local stat = vol:stat(full_path)
if stat then
display = display .. " (" .. format_size(stat.size) .. ")"
end
end
table.insert(tree_lines, prefix .. connector .. display)
end
-- Recurse into directories
if is_dir and not SKIP_DIRS[name] and current_depth < max_depth then
local next_prefix = prefix .. (is_last and " " or "│ ")
scan(full_path, current_depth + 1, next_prefix)
end
end
end
scan(base_path, 0, "")
local response = {
count = #results,
totalMatched = total_matched,
basePath = base_path
}
if show_tree and #tree_lines > 0 then
response.treeView = table.concat(tree_lines, "\n")
end
-- For compact output, just return names and types
local files = {}
for _, r in ipairs(results) do
table.insert(files, {
name = r.name,
path = r.path,
isDirectory = r.isDirectory,
size = r.size
})
end
response.files = files
return json.encode(response)
end
--- Format byte size to human readable
function format_size(bytes)
if not bytes then return "?" end
if bytes < 1024 then return bytes .. "B" end
if bytes < 1048576 then return string.format("%.1fK", bytes / 1024) end
return string.format("%.1fM", bytes / 1048576)
end
return { call = call }