Skip to content

Commit d3549a1

Browse files
committed
perf(sam): skip files excluded by user settings
Problem: "Scanning CloudFormation templates..." takes a long time on big workspaces, and there is no workaround. #3510 Solution: When scanning for CFN/SAM templates, exclude all directories/files specified in _all_ of the vscode settings `files.exclude`, `search.exclude`, or `files.watcherExclude`.
1 parent 262bd39 commit d3549a1

File tree

5 files changed

+83
-6
lines changed

5 files changed

+83
-6
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "SAM template detection now skips directories specified in [user settings](https://code.visualstudio.com/docs/getstarted/settings) `files.exclude`, `search.exclude`, or `files.watcherExclude`. This improves performance on big workspaces and avoids the \"Scanning CloudFormation templates...\" message. [#3510](https://github.com/aws/aws-toolkit-vscode/issues/3510)"
4+
}

src/shared/fs/watchedFiles.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,27 @@ import * as vscode from 'vscode'
77
import { getLogger } from '../logger/logger'
88
import * as pathutils from '../utilities/pathUtils'
99
import * as path from 'path'
10-
import { isUntitledScheme, normalizeVSCodeUri } from '../utilities/vsCodeUtils'
10+
import { globDirs, isUntitledScheme, normalizeVSCodeUri } from '../utilities/vsCodeUtils'
11+
import { Settings } from '../settings'
12+
import { once } from '../utilities/functionUtils'
1113

1214
/**
1315
* Prevent `findFiles()` from recursing into these directories.
1416
*
1517
* `findFiles()` defaults to the vscode `files.exclude` setting, which by default does not exclude "node_modules/".
1618
*/
17-
const alwaysExclude = '**/{.aws-sam,.git,.svn,.hg,.rvm,.gem,.project,node_modules,venv,bower_components}/'
19+
const alwaysExclude = {
20+
'.aws-sam': true,
21+
'.git': true,
22+
'.svn': true,
23+
'.hg': true,
24+
'.rvm': true,
25+
'.gem': true,
26+
'.project': true,
27+
node_modules: true,
28+
venv: true,
29+
bower_components: true,
30+
}
1831

1932
export interface WatchedItem<T> {
2033
/**
@@ -27,6 +40,21 @@ export interface WatchedItem<T> {
2740
item: T
2841
}
2942

43+
/** Builds an exclude pattern based on vscode global settings and the `alwaysExclude` default. */
44+
export function getExcludePattern() {
45+
const vscodeFilesExclude = Settings.instance.get<object>('files.exclude', Object, {})
46+
const vscodeSearchExclude = Settings.instance.get<object>('search.exclude', Object, {})
47+
const vscodeWatcherExclude = Settings.instance.get<object>('files.watcherExclude', Object, {})
48+
const all = [
49+
...Object.keys(alwaysExclude),
50+
...Object.keys(vscodeFilesExclude),
51+
...Object.keys(vscodeSearchExclude),
52+
...Object.keys(vscodeWatcherExclude),
53+
]
54+
return globDirs(all)
55+
}
56+
const getExcludePatternOnce = once(getExcludePattern)
57+
3058
/**
3159
* WatchedFiles lets us index files in the current registry. It is used
3260
* for CFN templates among other things. WatchedFiles holds a list of pairs of
@@ -230,10 +258,19 @@ export abstract class WatchedFiles<T> implements vscode.Disposable {
230258
*/
231259
public async rebuild(): Promise<void> {
232260
this.reset()
261+
262+
const exclude = getExcludePatternOnce()
233263
for (const glob of this.globs) {
234-
const itemUris = await vscode.workspace.findFiles(glob, alwaysExclude)
235-
for (const item of itemUris) {
236-
await this.addItemToRegistry(item, true)
264+
try {
265+
const found = await vscode.workspace.findFiles(glob, exclude)
266+
for (const item of found) {
267+
await this.addItemToRegistry(item, true)
268+
}
269+
} catch (e) {
270+
const err = e as Error
271+
if (err.name !== 'Canceled') {
272+
getLogger().error('watchedFiles: findFiles("%s", "%s"): %s', glob, exclude, err.message)
273+
}
237274
}
238275
}
239276
}

src/shared/logger/activation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export async function activate(
4848
)
4949

5050
setLogger(mainLogger)
51-
getLogger().error(`log level: ${getLogLevel()}`)
51+
getLogger().info(`log level: ${getLogLevel()}`)
5252

5353
// channel logger
5454
setLogger(

src/shared/utilities/vsCodeUtils.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,30 @@ export function isUntitledScheme(uri: vscode.Uri): boolean {
169169
return uri.scheme === 'untitled'
170170
}
171171

172+
/**
173+
* Creates a glob pattern that matches all directories specified in `dirs`.
174+
*
175+
* "/" and "*" chars are trimmed from `dirs` items, and the final glob is defined such that the
176+
* directories and their contents are matched any _any_ depth.
177+
*
178+
* Example: `['foo', '**\/bar/'] => "**\/{foo,bar}/"`
179+
*/
180+
export function globDirs(dirs: string[]): string {
181+
const excludePatternsStr = dirs.reduce((prev, current) => {
182+
// Trim all "*" and "/" chars.
183+
// Note that the replace() patterns and order is intentionaly so that "**/*foo*/**" yields "*foo*".
184+
const scrubbed = current
185+
.replace(/^\**/, '')
186+
.replace(/^[/\\]*/, '')
187+
.replace(/\**$/, '')
188+
.replace(/[/\\]*$/, '')
189+
const comma = prev === '' ? '' : ','
190+
return `${prev}${comma}${scrubbed}`
191+
}, '')
192+
const excludePattern = `**/{${excludePatternsStr}}/`
193+
return excludePattern
194+
}
195+
172196
// If the VSCode URI is not a file then return the string representation, otherwise normalize the filesystem path
173197
export function normalizeVSCodeUri(uri: vscode.Uri): string {
174198
if (uri.scheme !== 'file') {

src/test/shared/utilities/vscodeUtils.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as assert from 'assert'
77
import { VSCODE_EXTENSION_ID } from '../../../shared/extensions'
88
import * as vscodeUtil from '../../../shared/utilities/vsCodeUtils'
99
import * as vscode from 'vscode'
10+
import { getExcludePattern } from '../../../shared/fs/watchedFiles'
1011

1112
describe('vscodeUtils', async function () {
1213
it('activateExtension(), isExtensionActive()', async function () {
@@ -20,6 +21,17 @@ describe('vscodeUtils', async function () {
2021
await vscodeUtil.activateExtension(VSCODE_EXTENSION_ID.awstoolkit, false)
2122
assert.deepStrictEqual(vscodeUtil.isExtensionActive(VSCODE_EXTENSION_ID.awstoolkit), true)
2223
})
24+
25+
it('globDirs()', async function () {
26+
const input = ['foo', '**/bar/**', '*baz*', '**/*with.star*/**', '/zub', 'zim/', '/zoo/']
27+
assert.deepStrictEqual(vscodeUtil.globDirs(input), '**/{foo,bar,baz,*with.star*,zub,zim,zoo}/')
28+
})
29+
30+
it('watchedFiles.getExcludePattern()', async function () {
31+
// If vscode defaults change in the future, just update this test.
32+
// We intentionally want visibility into real-world defaults.
33+
assert.match(getExcludePattern(), /node_modules,bower_components,\*\.code-search,/)
34+
})
2335
})
2436

2537
describe('isExtensionInstalled()', function () {

0 commit comments

Comments
 (0)