Skip to content

Commit 428653a

Browse files
committed
feat(dev): improve "Show Global State" UX
- Avoid extra step for "Show all globalState". - Introduce `SkipPrompter` util class. - Implement "Show all globalState" using `DevDocumentProvider` to avoid unsaved buffer sticking around after vscode restart.
1 parent 24f70ab commit 428653a

File tree

2 files changed

+85
-28
lines changed

2 files changed

+85
-28
lines changed

src/dev/activation.ts

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as config from './config'
88
import { ExtContext } from '../shared/extensions'
99
import { createCommonButtons } from '../shared/ui/buttons'
1010
import { createQuickPick } from '../shared/ui/pickerPrompter'
11+
import { SkipPrompter } from '../shared/ui/common/skipPrompter'
1112
import { DevSettings } from '../shared/settings'
1213
import { FileProvider, VirtualFileSystem } from '../shared/virtualFilesystem'
1314
import { Commands } from '../shared/vscode/commands2'
@@ -45,7 +46,7 @@ const menuOptions: Record<string, MenuOption> = {
4546
openTerminal: {
4647
label: 'Open Remote Terminal',
4748
description: 'CodeCatalyst',
48-
detail: 'Open a new terminal connected to the remote environment',
49+
detail: 'Opens a new terminal connected to the remote environment',
4950
executor: openTerminalCommand,
5051
},
5152
deleteDevEnv: {
@@ -55,16 +56,16 @@ const menuOptions: Record<string, MenuOption> = {
5556
executor: deleteDevEnvCommand,
5657
},
5758
editStorage: {
58-
label: 'Edit Storage',
59+
label: 'Show or Edit Global Storage',
5960
description: 'VS Code',
60-
detail: 'Edit a key in global/secret storage as a JSON document',
61+
detail: 'Shows all globalState values, or edit a specific globalState/secret key as a JSON document',
6162
executor: openStorageFromInput,
6263
},
63-
showGlobalState: {
64-
label: 'Show Global State',
64+
showEnvVars: {
65+
label: 'Show Environment Variables',
6566
description: 'AWS Toolkit',
66-
detail: 'Shows various state (including environment variables)',
67-
executor: showGlobalState,
67+
detail: 'Shows all environment variable values',
68+
executor: () => showState('envvars'),
6869
},
6970
deleteSsoConnections: {
7071
label: 'Auth: Delete SSO Connections',
@@ -78,13 +79,33 @@ const menuOptions: Record<string, MenuOption> = {
7879
},
7980
}
8081

81-
export class GlobalStateDocumentProvider implements vscode.TextDocumentContentProvider {
82+
/**
83+
* Provides (readonly, as opposed to `ObjectEditor`) content for the aws-dev2:/ URI scheme.
84+
*
85+
* ```
86+
* aws-dev2:/state/envvars
87+
* aws-dev2:/state/globalstate
88+
* ```
89+
*
90+
* TODO: This only purpose of this provider is to avoid an annoying unsaved, empty document that
91+
* re-appears after vscode restart. Ideally there should be only one scheme (aws-dev:/).
92+
*/
93+
export class DevDocumentProvider implements vscode.TextDocumentContentProvider {
94+
constructor(private readonly ctx: ExtContext) {}
8295
provideTextDocumentContent(uri: vscode.Uri): string {
83-
let s = 'Environment variables known to AWS Toolkit:\n'
84-
for (const [k, v] of Object.entries(process.env)) {
85-
s += `${k}=${v}\n`
96+
if (uri.path === '/envvars') {
97+
let s = 'Environment variables known to AWS Toolkit:\n\n'
98+
for (const [k, v] of Object.entries(process.env)) {
99+
s += `${k}=${v}\n`
100+
}
101+
return s
102+
} else if (uri.path === '/globalstate') {
103+
// lol hax
104+
// as of November 2023, all of a memento's properties are stored as property `f` when minified
105+
return JSON.stringify((this.ctx.extensionContext.globalState as any).f, undefined, 4)
106+
} else {
107+
return `unknown URI path: ${uri}`
86108
}
87-
return s
88109
}
89110
}
90111

@@ -105,7 +126,7 @@ export function activate(ctx: ExtContext): void {
105126
ctx.extensionContext.subscriptions.push(
106127
devSettings.onDidChangeActiveSettings(updateMode),
107128
vscode.commands.registerCommand('aws.dev.openMenu', () => openMenu(ctx, menuOptions)),
108-
vscode.workspace.registerTextDocumentContentProvider('aws-dev2', new GlobalStateDocumentProvider())
129+
vscode.workspace.registerTextDocumentContentProvider('aws-dev2', new DevDocumentProvider(ctx))
109130
)
110131

111132
updateMode()
@@ -130,6 +151,8 @@ async function openMenu(ctx: ExtContext, options: typeof menuOptions): Promise<v
130151
const prompter = createQuickPick(items, {
131152
title: 'Developer Menu',
132153
buttons: createCommonButtons(),
154+
matchOnDescription: true,
155+
matchOnDetail: true,
133156
})
134157

135158
await prompter.prompt()
@@ -169,12 +192,10 @@ class VirtualObjectFile implements FileProvider {
169192
const value = (await this.storage.get(key)) ?? ''
170193
return JSON.stringify(JSON.parse(value), undefined, 4)
171194
} else {
172-
if (key !== '') {
173-
return JSON.stringify(this.storage.get(key, {}), undefined, 4)
195+
if (key === '') {
196+
return '(empty key)'
174197
}
175-
// lol hax
176-
// as of November 2023, all of a memento's properties are stored as property `f` when minified
177-
return JSON.stringify((this.storage as any).f, undefined, 4)
198+
return JSON.stringify(this.storage.get(key, {}), undefined, 4)
178199
}
179200
}
180201

@@ -208,8 +229,10 @@ class ObjectEditor {
208229
vscode.workspace.registerFileSystemProvider(ObjectEditor.scheme, this.fs)
209230
}
210231

211-
public async openStorage(type: 'globals' | 'secrets', key: string): Promise<void> {
232+
public async openStorage(type: 'globalsView' | 'globals' | 'secrets', key: string): Promise<void> {
212233
switch (type) {
234+
case 'globalsView':
235+
return showState('globalstate')
213236
case 'globals':
214237
return this.openState(this.context.globalState, key)
215238
case 'secrets':
@@ -263,14 +286,15 @@ class ObjectEditor {
263286
}
264287

265288
async function openStorageFromInput(ctx: ExtContext) {
266-
const wizard = new (class extends Wizard<{ target: 'globals' | 'secrets'; key: string }> {
289+
const wizard = new (class extends Wizard<{ target: 'globalsView' | 'globals' | 'secrets'; key: string }> {
267290
constructor() {
268291
super()
269292

270293
this.form.target.bindPrompter(() =>
271294
createQuickPick(
272295
[
273-
{ label: 'Global State', data: 'globals' },
296+
{ label: 'Show all globalState', data: 'globalsView' },
297+
{ label: 'Edit globalState', data: 'globals' },
274298
{ label: 'Secrets', data: 'secrets' },
275299
],
276300
{
@@ -284,7 +308,10 @@ async function openStorageFromInput(ctx: ExtContext) {
284308
return createInputBox({
285309
title: 'Enter a key',
286310
})
311+
} else if (target === 'globalsView') {
312+
return new SkipPrompter('')
287313
} else if (target === 'globals') {
314+
// List all globalState keys in the quickpick menu.
288315
const items = ctx.extensionContext.globalState
289316
.keys()
290317
.map(key => {
@@ -296,12 +323,8 @@ async function openStorageFromInput(ctx: ExtContext) {
296323
.sort((a, b) => {
297324
return a.data.localeCompare(b.data)
298325
})
299-
items.unshift({
300-
data: '',
301-
label: "SHOW ALL (edits here won't write to global state)",
302-
})
303326

304-
return createQuickPick(items, { title: 'Pick a global state key' })
327+
return createQuickPick(items, { title: 'Select a key' })
305328
} else {
306329
throw new Error('invalid storage target')
307330
}
@@ -330,8 +353,8 @@ async function expireSsoConnections() {
330353
vscode.window.showInformationMessage(`Expired: ${ssoConns.map(c => c.startUrl).join(', ')}`)
331354
}
332355

333-
async function showGlobalState() {
334-
const uri = vscode.Uri.parse('aws-dev2:global-state')
356+
async function showState(path: string) {
357+
const uri = vscode.Uri.parse(`aws-dev2://state/${path}`)
335358
const doc = await vscode.workspace.openTextDocument(uri)
336359
await vscode.window.showTextDocument(doc, { preview: false })
337360
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { StepEstimator } from '../../wizards/wizard'
7+
import { Prompter, PromptResult } from '../prompter'
8+
9+
/** Pseudo-prompter that immediately returns a value (and thus "skips" a step). */
10+
export class SkipPrompter<T> extends Prompter<T> {
11+
/**
12+
* @param val Value immediately returned by the prompter.
13+
*/
14+
constructor(public readonly val: T) {
15+
super()
16+
}
17+
18+
protected async promptUser(): Promise<PromptResult<T>> {
19+
const promptPromise = new Promise<PromptResult<T>>(resolve => {
20+
resolve(this.val)
21+
})
22+
23+
return await promptPromise
24+
}
25+
26+
public get recentItem(): any {
27+
return undefined
28+
}
29+
30+
public set recentItem(response: string | undefined) {}
31+
32+
public setSteps(current: number, total: number): void {}
33+
public setStepEstimator(estimator: StepEstimator<T>): void {}
34+
}

0 commit comments

Comments
 (0)