Skip to content

Commit b643142

Browse files
committed
feat: add "Excluded path suggestion globs" preference option
1 parent 1c43965 commit b643142

File tree

4 files changed

+97
-10
lines changed

4 files changed

+97
-10
lines changed

org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/markdown/MarkdownLanguageClient.java

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
import java.net.URISyntaxException;
1919
import java.net.URL;
2020
import java.nio.charset.StandardCharsets;
21+
import java.nio.file.FileSystems;
2122
import java.nio.file.Files;
2223
import java.nio.file.Path;
24+
import java.nio.file.PathMatcher;
2325
import java.nio.file.Paths;
2426
import java.util.ArrayList;
25-
import java.util.Collections;
2627
import java.util.HashMap;
2728
import java.util.List;
2829
import java.util.Map;
@@ -38,6 +39,7 @@
3839
import org.eclipse.core.resources.ResourcesPlugin;
3940
import org.eclipse.core.runtime.FileLocator;
4041
import org.eclipse.core.runtime.ILog;
42+
import org.eclipse.core.runtime.IPath;
4143
import org.eclipse.lsp4e.LSPEclipseUtils;
4244
import org.eclipse.lsp4e.client.DefaultLanguageClient;
4345
import org.eclipse.lsp4j.ConfigurationItem;
@@ -212,7 +214,7 @@ public CompletableFuture<List<Map<String, Object>>> parseMarkdown(final Map<Stri
212214
}
213215
}
214216
if (!hasText && uriPath == null) {
215-
return Collections.emptyList();
217+
return List.of();
216218
}
217219
final String argPath;
218220
if (hasText) {
@@ -229,7 +231,7 @@ public CompletableFuture<List<Map<String, Object>>> parseMarkdown(final Map<Stri
229231
if (exit != 0) {
230232
final var err = new String(proc.getErrorStream().readAllBytes(), StandardCharsets.UTF_8);
231233
ILog.get().warn("markdown-it parser failed (" + exit + "): " + err, null);
232-
return Collections.emptyList();
234+
return List.of();
233235
}
234236
final var gson = new Gson();
235237
final List<Map<String, Object>> tokens = gson.fromJson(out, List.class);
@@ -242,7 +244,7 @@ public CompletableFuture<List<Map<String, Object>>> parseMarkdown(final Map<Stri
242244
}
243245
} catch (final Exception ignore) {
244246
}
245-
return tokens != null ? tokens : Collections.emptyList();
247+
return tokens != null ? tokens : List.of();
246248
} catch (final InterruptedException ex) {
247249
ILog.get().warn(ex.getMessage(), ex);
248250
/* Clean up whatever needs to be handled before interrupting */
@@ -256,7 +258,7 @@ public CompletableFuture<List<Map<String, Object>>> parseMarkdown(final Map<Stri
256258
} catch (final Exception ignore) {
257259
}
258260
}
259-
return Collections.emptyList();
261+
return List.of();
260262
});
261263
}
262264

@@ -270,6 +272,9 @@ public CompletableFuture<List<Map<String, Object>>> parseMarkdown(final Map<Stri
270272
public CompletableFuture<List<String>> findMarkdownFilesInWorkspace(final Object unused) {
271273
return CompletableFuture.supplyAsync(() -> {
272274
final var uris = new ArrayList<String>();
275+
// Compile exclude globs from preferences once per request
276+
final String[] excludeGlobs = MarkdownPreferences.getSuggestPathsExcludeGlobs();
277+
final List<PathMatcher> excludeMatchers = compileGlobMatchers(excludeGlobs);
273278
try {
274279
final var roots = MarkdownLanguageServer.getServerRoots();
275280
if (roots != null && !roots.isEmpty()) {
@@ -285,7 +290,8 @@ public CompletableFuture<List<String>> findMarkdownFilesInWorkspace(final Object
285290
return false;
286291
if (res.getType() == IResource.FILE) {
287292
final String name = res.getName().toLowerCase();
288-
if (name.endsWith(".md") || name.endsWith(".markdown") || name.endsWith(".mdown")) {
293+
if ((name.endsWith(".md") || name.endsWith(".markdown") || name.endsWith(".mdown"))
294+
&& !isExcludedByGlobs(res, excludeMatchers)) {
289295
uris.add(normalizeFileUriForLanguageServer(res.getLocationURI()));
290296
}
291297
return false; // no children
@@ -303,7 +309,8 @@ public CompletableFuture<List<String>> findMarkdownFilesInWorkspace(final Object
303309
return false;
304310
if (res.getType() == IResource.FILE) {
305311
final String name = res.getName().toLowerCase();
306-
if (name.endsWith(".md") || name.endsWith(".markdown") || name.endsWith(".mdown")) {
312+
if ((name.endsWith(".md") || name.endsWith(".markdown") || name.endsWith(".mdown"))
313+
&& !isExcludedByGlobs(res, excludeMatchers)) {
307314
uris.add(normalizeFileUriForLanguageServer(res.getLocationURI()));
308315
}
309316
return false; // no children
@@ -318,6 +325,48 @@ public CompletableFuture<List<String>> findMarkdownFilesInWorkspace(final Object
318325
});
319326
}
320327

328+
private static List<PathMatcher> compileGlobMatchers(String... globs) {
329+
if (globs == null || globs.length == 0)
330+
return List.of();
331+
332+
var fs = FileSystems.getDefault();
333+
var matchers = new ArrayList<PathMatcher>();
334+
335+
for (String glob : globs) {
336+
if (glob == null || (glob = glob.trim()).isEmpty())
337+
continue;
338+
339+
// If pattern starts with "**/", also add a root-level variant without it.
340+
// This makes "**/node_modules/**" also match "node_modules/**".
341+
List<String> patterns = glob.startsWith("**/") && glob.length() > 3
342+
? List.of(glob, glob.substring(3))
343+
: List.of(glob);
344+
345+
for (String pattern : patterns) {
346+
try {
347+
matchers.add(fs.getPathMatcher("glob:" + pattern));
348+
} catch (Exception ex) {
349+
ILog.get().warn(ex.getMessage(), ex);
350+
}
351+
}
352+
}
353+
return matchers;
354+
}
355+
356+
private static boolean isExcludedByGlobs(final IResource res, final List<PathMatcher> matchers) {
357+
if (matchers == null || matchers.isEmpty())
358+
return false;
359+
final IPath pr = res.getProjectRelativePath();
360+
if (pr == null)
361+
return false;
362+
final Path p = pr.toPath();
363+
for (final PathMatcher m : matchers) {
364+
if (m.matches(p))
365+
return true;
366+
}
367+
return false;
368+
}
369+
321370
/**
322371
* <pre>
323372
* Request: { uri: string }
@@ -415,10 +464,10 @@ public CompletableFuture<Void> fsWatcherCreate(final Map<String, Object> params)
415464
@SuppressWarnings("unchecked")
416465
final Map<String, Object> options = params.get("options") instanceof Map //
417466
? (Map<String, Object>) params.get("options")
418-
: Collections.emptyMap();
419-
final var watcher = new Watcher((MarkdownLanguageServerAPI) getLanguageServer(), id, path,
467+
: Map.of();
468+
final var watcher = new Watcher((MarkdownLanguageServerAPI) getLanguageServer(), id, path, //
420469
Boolean.TRUE.equals(options.get("ignoreCreate")), //
421-
Boolean.TRUE.equals(options.get("ignoreChange")),
470+
Boolean.TRUE.equals(options.get("ignoreChange")), //
422471
Boolean.TRUE.equals(options.get("ignoreDelete")));
423472
ResourcesPlugin.getWorkspace().addResourceChangeListener(watcher, IResourceChangeEvent.POST_CHANGE);
424473
watchersById.put(Integer.valueOf(id), watcher);

org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/markdown/ui/preferences/MarkdownPreferenceInitializer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public void initializeDefaultPreferences() {
4141
store.setDefault(MD_PREFERRED_MD_PATH_EXTENSION_STYLE, PreferredMdPathExtensionStyle.auto.value);
4242
store.setDefault(MD_SUGGEST_PATHS_ENABLED, true);
4343
store.setDefault(MD_SUGGEST_PATHS_INCLUDE_WKS_HEADER_COMPLETIONS, IncludeWorkspaceHeaderCompletions.onDoubleHash.value);
44+
store.setDefault(MD_SUGGEST_PATHS_EXCLUDE_GLOBS, "**/node_modules/**");
4445

4546
/*
4647
* Validation

org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/markdown/ui/preferences/MarkdownPreferencePage.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,22 @@ protected void createFieldEditors() {
108108
toLabelValueArray(IncludeWorkspaceHeaderCompletions.class),
109109
suggestionsGroup));
110110

111+
final var excludeGlobs = new StringFieldEditor(
112+
MD_SUGGEST_PATHS_EXCLUDE_GLOBS,
113+
"Excluded path suggestion globs (comma-separated)",
114+
suggestionsGroup);
115+
addField(excludeGlobs);
116+
final String excludeTooltip = """
117+
Glob patterns to exclude Markdown files from path suggestions.
118+
Matched against project-relative paths, for example:
119+
• **/node_modules/**
120+
• docs/generated/**
121+
• **/drafts/**
122+
• **/*.tmp.md
123+
""";
124+
excludeGlobs.getLabelControl(suggestionsGroup).setToolTipText(excludeTooltip);
125+
excludeGlobs.getTextControl(suggestionsGroup).setToolTipText(excludeTooltip);
126+
111127
// Validation
112128
validateEnabledEditor = new BooleanFieldEditor(MD_VALIDATE_ENABLED, "Enable validation", pageParent);
113129
addField(validateEnabledEditor);

org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/markdown/ui/preferences/MarkdownPreferences.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ private ValidateEnabledForFragmentLinks(final String label) {
113113
static final String MD_SUGGEST_PATHS_INCLUDE_WKS_HEADER_COMPLETIONS = MD_SECTION
114114
+ ".suggest.paths.includeWorkspaceHeaderCompletions"; // IncludeWorkspaceHeaderCompletions
115115

116+
117+
// Note: MD_SUGGEST_PATHS_EXCLUDE_GLOBS is a client-only preference used for
118+
// filtering path suggestions on the Eclipse side and is not sent to the server.
119+
static final String MD_SUGGEST_PATHS_EXCLUDE_GLOBS = MD_SECTION + ".suggest.paths.excludeGlobs"; // comma list
120+
116121
/*
117122
* Validation
118123
*/
@@ -166,6 +171,22 @@ public static Settings getGlobalSettings() {
166171
return settings;
167172
}
168173

174+
/**
175+
* Returns comma-separated glob patterns from preferences for excluding files from
176+
* Markdown path suggestions. Empty or blank entries are filtered out.
177+
*/
178+
public static String[] getSuggestPathsExcludeGlobs() {
179+
final IPreferenceStore store = Activator.getDefault().getPreferenceStore();
180+
final String raw = store.getString(MD_SUGGEST_PATHS_EXCLUDE_GLOBS);
181+
if (raw == null || raw.trim().isEmpty()) {
182+
return new String[0];
183+
}
184+
return Arrays.stream(raw.split(","))
185+
.map(String::trim)
186+
.filter(s -> !s.isEmpty())
187+
.toArray(String[]::new);
188+
}
189+
169190
public static boolean isMatchMarkdownSection(final String section) {
170191
return isMatchSection(section, MD_SECTION);
171192
}

0 commit comments

Comments
 (0)