1 module served.commands.file_search;
2 
3 import served.extension;
4 import served.lsp.filereader;
5 import served.types;
6 
7 import workspaced.api;
8 import workspaced.coms;
9 
10 import std.algorithm : endsWith, sort, startsWith, uniq;
11 import std.path : baseName, buildNormalizedPath, buildPath, isAbsolute, stripExtension;
12 import std.string : makeTransTable, translate;
13 
14 import fs = std.file;
15 import io = std.stdio;
16 
17 @protocolMethod("served/searchFile")
18 string[] searchFile(string query)
19 {
20 	if (!query.length)
21 		return null;
22 
23 	if (query.isAbsolute)
24 	{
25 		if (fs.exists(query))
26 			return [query];
27 		else
28 			return null;
29 	}
30 
31 	string[] ret;
32 	string[] importFiles, importPaths;
33 	importPaths = selectedWorkspace.stdlibPath();
34 
35 	foreach (instance; backend.instances)
36 	{
37 		importFiles ~= instance.importFiles;
38 		importPaths ~= instance.importPaths;
39 	}
40 
41 	importFiles.sort!"a<b";
42 	importPaths.sort!"a<b";
43 
44 	foreach (file; importFiles.uniq)
45 	{
46 		if (fs.exists(file) && fs.isFile(file))
47 			if (file.endsWith(query))
48 			{
49 				auto rest = file[0 .. $ - query.length];
50 				if (!rest.length || rest.endsWith("/", "\\"))
51 					ret ~= file;
52 			}
53 	}
54 	foreach (dir; importPaths.uniq)
55 	{
56 		if (fs.exists(dir) && fs.isDir(dir))
57 			foreach (filename; fs.dirEntries(dir, fs.SpanMode.breadth))
58 				if (filename.isFile)
59 				{
60 					auto file = buildPath(dir, filename);
61 					if (file.endsWith(query))
62 					{
63 						auto rest = file[0 .. $ - query.length];
64 						if (!rest.length || rest.endsWith("/", "\\"))
65 							ret ~= file;
66 					}
67 				}
68 	}
69 
70 	return ret;
71 }
72 
73 private string[] cachedModuleFiles;
74 private string[string] modFileCache;
75 @protocolMethod("served/findFilesByModule")
76 string[] findFilesByModule(string module_)
77 {
78 	if (!module_.length)
79 		return null;
80 
81 	if (auto cache = module_ in modFileCache)
82 		return [*cache];
83 
84 	scope ubyte[] buffer = new ubyte[8 * 1024];
85 	scope (exit)
86 		buffer.destroy();
87 
88 	string[] ret;
89 	string[] importFiles, importPaths;
90 	importPaths = selectedWorkspace.stdlibPath();
91 
92 	foreach (instance; backend.instances)
93 	{
94 		importFiles ~= instance.importFiles;
95 		importPaths ~= instance.importPaths;
96 	}
97 
98 	importFiles.sort!"a<b";
99 	importPaths.sort!"a<b";
100 
101 	foreach (file; importFiles.uniq)
102 	{
103 		if (cachedModuleFiles.binarySearch(file) >= 0)
104 			continue;
105 		if (fs.exists(file) && fs.isFile(file))
106 		{
107 			auto fileMod = backend.get!ModulemanComponent.moduleName(
108 					cast(string) file.readCodeWithBuffer(buffer));
109 			if (fileMod.startsWith("std"))
110 			{
111 				modFileCache[fileMod] = file;
112 				cachedModuleFiles.insertSorted(file);
113 			}
114 			if (fileMod == module_)
115 				ret ~= file;
116 		}
117 	}
118 	foreach (dir; importPaths.uniq)
119 	{
120 		if (fs.exists(dir) && fs.isDir(dir))
121 			foreach (filename; fs.dirEntries(dir, fs.SpanMode.breadth))
122 				if (filename.isFile)
123 				{
124 					auto file = buildPath(dir, filename);
125 					if (cachedModuleFiles.binarySearch(file) >= 0)
126 						continue;
127 					auto fileMod = moduleNameForFile(file, dir, buffer);
128 					if (fileMod.startsWith("std"))
129 					{
130 						modFileCache[fileMod] = file;
131 						cachedModuleFiles.insertSorted(file);
132 					}
133 					if (fileMod == module_)
134 						ret ~= file;
135 				}
136 	}
137 
138 	return ret;
139 }
140 
141 private string moduleNameForFile(string file, string dir, ref ubyte[] buffer)
142 {
143 	auto ret = backend.get!ModulemanComponent.moduleName(
144 			cast(string) file.readCodeWithBuffer(buffer));
145 	if (ret.length)
146 		return ret;
147 	file = buildNormalizedPath(file);
148 	dir = buildNormalizedPath(dir);
149 	if (file.startsWith(dir))
150 		return file[dir.length .. $].stripExtension.translate(makeTransTable("/\\", ".."));
151 	else
152 		return baseName(file).stripExtension;
153 }