1818import java .net .URISyntaxException ;
1919import java .net .URL ;
2020import java .nio .charset .StandardCharsets ;
21+ import java .nio .file .FileSystems ;
2122import java .nio .file .Files ;
2223import java .nio .file .Path ;
24+ import java .nio .file .PathMatcher ;
2325import java .nio .file .Paths ;
2426import java .util .ArrayList ;
25- import java .util .Collections ;
2627import java .util .HashMap ;
2728import java .util .List ;
2829import java .util .Map ;
3839import org .eclipse .core .resources .ResourcesPlugin ;
3940import org .eclipse .core .runtime .FileLocator ;
4041import org .eclipse .core .runtime .ILog ;
42+ import org .eclipse .core .runtime .IPath ;
4143import org .eclipse .lsp4e .LSPEclipseUtils ;
4244import org .eclipse .lsp4e .client .DefaultLanguageClient ;
4345import 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 );
0 commit comments