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