Skip to content

Commit

Permalink
allow promises on validate. fix inconsistent selection, add CHANGELOG
Browse files Browse the repository at this point in the history
This allow creation of files and folders recursive path.
improve validation check, add cancellationtoken

separate validation for absolutpath and leading/trailing spaces

Signed-off-by: Uni Sayo <unibtc@gmail.com>
  • Loading branch information
uniibu committed Apr 2, 2019
1 parent 6af076b commit e49adaf
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 32 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
## v0.6.0

- [filesystem] added the menu item `Upload Files...` to easily upload files into a workspace
- [workspace] allow creation of files and folders using recursive paths

Breaking changes:

- [dialog] `validate` and `accept` methods are now Promisified [#4764](https://github.com/theia-ide/theia/pull/4764)

## v0.5.0

Expand Down
48 changes: 31 additions & 17 deletions packages/core/src/browser/dialogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
********************************************************************************/

import { injectable, inject } from 'inversify';
import { Disposable } from '../common';
import { Disposable, MaybePromise, CancellationTokenSource } from '../common';
import { Key } from './keys';
import { Widget, BaseWidget, Message } from './widgets';

Expand Down Expand Up @@ -194,31 +194,45 @@ export abstract class AbstractDialog<T> extends BaseWidget {
this.activeElement = undefined;
super.close();
}

protected onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
this.validate();
}

protected validate(): void {
protected validateCancellationSource = new CancellationTokenSource();
protected async validate(): Promise<void> {
if (!this.resolve) {
return;
}
this.validateCancellationSource.cancel();
this.validateCancellationSource = new CancellationTokenSource();
const token = this.validateCancellationSource.token;
const value = this.value;
const error = this.isValid(value, 'preview');
const error = await this.isValid(value, 'preview');
if (token.isCancellationRequested) {
return;
}
this.setErrorMessage(error);
}

protected accept(): void {
if (this.resolve) {
const value = this.value;
const error = this.isValid(value, 'open');
if (!DialogError.getResult(error)) {
this.setErrorMessage(error);
} else {
this.resolve(value);
Widget.detach(this);
}
protected acceptCancellationSource = new CancellationTokenSource();
protected async accept(): Promise<void> {
if (!this.resolve) {
return;
}
this.acceptCancellationSource.cancel();
this.acceptCancellationSource = new CancellationTokenSource();
const token = this.acceptCancellationSource.token;
const value = this.value;
const error = await this.isValid(value, 'open');
if (token.isCancellationRequested) {
return;
}
if (!DialogError.getResult(error)) {
this.setErrorMessage(error);
} else {
this.resolve(value);
Widget.detach(this);
}
}

Expand All @@ -227,7 +241,7 @@ export abstract class AbstractDialog<T> extends BaseWidget {
/**
* Return a string of zero-length or true if valid.
*/
protected isValid(value: T, mode: DialogMode): DialogError {
protected isValid(value: T, mode: DialogMode): MaybePromise<DialogError> {
return '';
}

Expand Down Expand Up @@ -299,7 +313,7 @@ export class SingleTextInputDialogProps extends DialogProps {
end: number
direction?: 'forward' | 'backward' | 'none'
};
readonly validate?: (input: string, mode: DialogMode) => DialogError;
readonly validate?: (input: string, mode: DialogMode) => MaybePromise<DialogError>;
}

export class SingleTextInputDialog extends AbstractDialog<string> {
Expand Down Expand Up @@ -333,7 +347,7 @@ export class SingleTextInputDialog extends AbstractDialog<string> {
return this.inputField.value;
}

protected isValid(value: string, mode: DialogMode): DialogError {
protected isValid(value: string, mode: DialogMode): MaybePromise<DialogError> {
if (this.props.validate) {
return this.props.validate(value, mode);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/filesystem/src/browser/file-dialog/file-dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ export class OpenFileDialog extends FileDialog<MaybeArray<FileStatNode>> {
}
}

protected accept(): void {
protected async accept(): Promise<void> {
if (this.props.canSelectFolders === false && !Array.isArray(this.value)) {
this.widget.model.openNode(this.value);
return;
Expand Down
2 changes: 1 addition & 1 deletion packages/filesystem/src/browser/file-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export class FileResource implements Resource {
}

protected async getFileStat(): Promise<FileStat | undefined> {
if (!this.fileSystem.exists(this.uriString)) {
if (!await this.fileSystem.exists(this.uriString)) {
return undefined;
}
try {
Expand Down
46 changes: 33 additions & 13 deletions packages/workspace/src/browser/workspace-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,13 @@ export class WorkspaceCommandContribution implements CommandContribution {
const parentUri = new URI(parent.uri);
const { fileName, fileExtension } = this.getDefaultFileConfig();
const vacantChildUri = FileSystemUtils.generateUniqueResourceURI(parentUri, parent, fileName, fileExtension);

const dialog = new SingleTextInputDialog({
title: 'New File',
initialValue: vacantChildUri.path.base,
validate: name => this.validateFileName(name, parent)
validate: name => this.validateFileName(name, parent, true)
});

dialog.open().then(name => {
if (name) {
const fileUri = parentUri.resolve(name);
Expand All @@ -206,7 +208,7 @@ export class WorkspaceCommandContribution implements CommandContribution {
const dialog = new SingleTextInputDialog({
title: 'New Folder',
initialValue: vacantChildUri.path.base,
validate: name => this.validateFileName(name, parent)
validate: name => this.validateFileName(name, parent, true)
});
dialog.open().then(name => {
if (name) {
Expand Down Expand Up @@ -240,7 +242,7 @@ export class WorkspaceCommandContribution implements CommandContribution {
if (initialValue === name && mode === 'preview') {
return false;
}
return this.validateFileName(name, parent);
return this.validateFileName(name, parent, false);
}
});
dialog.open().then(name => {
Expand Down Expand Up @@ -308,21 +310,40 @@ export class WorkspaceCommandContribution implements CommandContribution {
*
* @param name the simple file name of the file to validate.
* @param parent the parent directory's file stat.
* @param recursive allow file or folder creation using recursive path
*/
protected validateFileName(name: string, parent: FileStat): string {
if (!validFilename(name)) {
return 'Invalid name, try other';
protected async validateFileName(name: string, parent: FileStat, recursive: boolean = false): Promise<string> {
if (!name) {
return '';
}
if (parent.children) {
for (const child of parent.children) {
if (new URI(child.uri).path.base === name) {
return 'A file with this name already exists.';
}
}
// do not allow recursive rename
if (!recursive && !validFilename(name)) {
return 'Invalid file or folder name';
}
if (name.startsWith('/')) {
return 'Absolute paths or names that starts with / are not allowed';
} else if (name.startsWith(' ') || name.endsWith(' ')) {
return 'Names with leading or trailing whitespaces are not allowed';
}
// check and validate each sub-paths
if (name.split(/[\\/]/).some(file => !file || !validFilename(file) || /^\s+$/.test(file))) {
return `The name <strong>${this.trimFileName(name)}</strong> is not a valid file or folder name.`;
}
const childUri = new URI(parent.uri).resolve(name).toString();
const exists = await this.fileSystem.exists(childUri);
if (exists) {
return `A file or folder <strong>${this.trimFileName(name)}</strong> already exists at this location.`;
}
return '';
}

protected trimFileName(name: string): string {
if (name && name.length > 30) {
return `${name.substr(0, 30)}...`;
}
return name;
}

protected async getDirectory(candidate: URI): Promise<FileStat | undefined> {
const stat = await this.fileSystem.getFileStat(candidate.toString());
if (stat && stat.isDirectory) {
Expand Down Expand Up @@ -400,7 +421,6 @@ export class WorkspaceCommandContribution implements CommandContribution {
}
return false;
}

}

export class WorkspaceRootUriAwareCommandHandler extends UriAwareCommandHandler<URI> {
Expand Down

0 comments on commit e49adaf

Please sign in to comment.