|
| 1 | +import * as remote from '@theia/core/electron-shared/@electron/remote'; |
| 2 | +import { nls } from '@theia/core/lib/common/nls'; |
| 3 | +import { Deferred } from '@theia/core/lib/common/promise-util'; |
| 4 | +import { injectable } from '@theia/core/shared/inversify'; |
| 5 | +import { WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands'; |
| 6 | +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; |
| 7 | +import { Sketch, SketchContribution, URI } from './contribution'; |
| 8 | +import { SaveAsSketch } from './save-as-sketch'; |
| 9 | + |
| 10 | +@injectable() |
| 11 | +export class ValidateSketch extends SketchContribution { |
| 12 | + override onReady(): void { |
| 13 | + this.promptFixActions().then(async (result) => { |
| 14 | + if (!result) { |
| 15 | + const sketch = await this.sketchService.createNewSketch(); |
| 16 | + this.workspaceService.open(new URI(sketch.uri), { |
| 17 | + preserveWindow: true, |
| 18 | + }); |
| 19 | + } |
| 20 | + }); |
| 21 | + } |
| 22 | + |
| 23 | + private async currentSketch(): Promise<Sketch> { |
| 24 | + const sketch = this.sketchServiceClient.tryGetCurrentSketch(); |
| 25 | + if (CurrentSketch.isValid(sketch)) { |
| 26 | + return sketch; |
| 27 | + } |
| 28 | + const deferred = new Deferred<Sketch>(); |
| 29 | + const disposable = this.sketchServiceClient.onCurrentSketchDidChange( |
| 30 | + (sketch) => { |
| 31 | + if (CurrentSketch.isValid(sketch)) { |
| 32 | + disposable.dispose(); |
| 33 | + deferred.resolve(sketch); |
| 34 | + } |
| 35 | + } |
| 36 | + ); |
| 37 | + return deferred.promise; |
| 38 | + } |
| 39 | + |
| 40 | + /** |
| 41 | + * Returns with an array of actions the user has to perform to fix the invalid sketch. |
| 42 | + */ |
| 43 | + private validate(sketch: Sketch): FixAction[] { |
| 44 | + // sketch folder + main sketch file (requires `Save as...` and window reload) |
| 45 | + const sketchFolderName = new URI(sketch.uri).path.base; |
| 46 | + const sketchFolderNameError = |
| 47 | + Sketch.validateSketchFolderName(sketchFolderName); |
| 48 | + if (sketchFolderNameError) { |
| 49 | + return [ |
| 50 | + { |
| 51 | + execute: async () => { |
| 52 | + const unknown = |
| 53 | + (await this.promptRenameSketch(sketch)) && |
| 54 | + (await this.commandService.executeCommand( |
| 55 | + SaveAsSketch.Commands.SAVE_AS_SKETCH.id, |
| 56 | + <SaveAsSketch.Options>{ |
| 57 | + markAsRecentlyOpened: true, |
| 58 | + openAfterMove: true, |
| 59 | + wipeOriginal: true, |
| 60 | + } |
| 61 | + )); |
| 62 | + return !!unknown; |
| 63 | + }, |
| 64 | + }, |
| 65 | + ]; |
| 66 | + } |
| 67 | + |
| 68 | + // sketch code files (does not require window reload) |
| 69 | + return Sketch.uris(sketch) |
| 70 | + .filter((uri) => uri !== sketch.mainFileUri) |
| 71 | + .map((uri) => new URI(uri)) |
| 72 | + .filter((uri) => Sketch.Extensions.CODE_FILES.includes(uri.path.ext)) |
| 73 | + .map((uri) => ({ |
| 74 | + uri, |
| 75 | + error: Sketch.validateSketchFolderName(uri.path.name), |
| 76 | + })) |
| 77 | + .filter(({ error }) => Boolean(error)) |
| 78 | + .map(({ uri }) => ({ |
| 79 | + execute: async () => { |
| 80 | + const unknown = |
| 81 | + (await this.promptRenameSketchFile(uri)) && |
| 82 | + (await this.commandService.executeCommand( |
| 83 | + WorkspaceCommands.FILE_RENAME.id, |
| 84 | + uri |
| 85 | + )); |
| 86 | + return !!unknown; |
| 87 | + }, |
| 88 | + })); |
| 89 | + } |
| 90 | + |
| 91 | + private async promptFixActions(): Promise<boolean> { |
| 92 | + const sketch = await this.currentSketch(); |
| 93 | + const fixActions = this.validate(sketch); |
| 94 | + for (const fixAction of fixActions) { |
| 95 | + const result = await fixAction.execute(); |
| 96 | + if (!result) { |
| 97 | + return false; |
| 98 | + } |
| 99 | + } |
| 100 | + return true; |
| 101 | + } |
| 102 | + |
| 103 | + private async promptRenameSketch(sketch: Sketch): Promise<boolean> { |
| 104 | + return this.prompt( |
| 105 | + nls.localize( |
| 106 | + 'arduino/sketch/renameSketchFolderTitle', |
| 107 | + 'Invalid sketch name' |
| 108 | + ), |
| 109 | + nls.localize( |
| 110 | + 'arduino/sketch/renameSketchFolderMessage', |
| 111 | + "The sketch '{0}' cannot be used. Sketch names must start with a letter or number, followed by letters, numbers, dashes, dots and underscores. Maximum length is 63 characters. To get rid of this message, rename the sketch. Do you want to rename the sketch now?", |
| 112 | + sketch.name |
| 113 | + ) |
| 114 | + ); |
| 115 | + } |
| 116 | + |
| 117 | + private async promptRenameSketchFile(uri: URI): Promise<boolean> { |
| 118 | + return this.prompt( |
| 119 | + nls.localize( |
| 120 | + 'arduino/sketch/renameSketchFileTitle', |
| 121 | + 'Invalid sketch filename' |
| 122 | + ), |
| 123 | + nls.localize( |
| 124 | + 'arduino/sketch/renameSketchFileMessage', |
| 125 | + "The sketch file '{0}' cannot be used. Sketch filenames must start with a letter or number, followed by letters, numbers, dashes, dots and underscores. Maximum length is 63 characters without the file extension. To get rid of this message, rename the sketch file. Do you want to rename the sketch file now?", |
| 126 | + uri.path.base |
| 127 | + ) |
| 128 | + ); |
| 129 | + } |
| 130 | + |
| 131 | + private async prompt(title: string, message: string): Promise<boolean> { |
| 132 | + const { response } = await remote.dialog.showMessageBox( |
| 133 | + remote.getCurrentWindow(), |
| 134 | + { |
| 135 | + title, |
| 136 | + message, |
| 137 | + type: 'warning', |
| 138 | + buttons: [ |
| 139 | + nls.localize('vscode/issueMainService/cancel', 'Cancel'), |
| 140 | + nls.localize('vscode/issueMainService/ok', 'OK'), |
| 141 | + ], |
| 142 | + } |
| 143 | + ); |
| 144 | + // cancel |
| 145 | + if (response === 0) { |
| 146 | + return false; |
| 147 | + } |
| 148 | + return true; |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +interface FixAction { |
| 153 | + execute(): Promise<boolean>; |
| 154 | +} |
0 commit comments