Skip to content

Commit

Permalink
fix #5294: 1. reopen/save file in a specific encoding; 2. configurati…
Browse files Browse the repository at this point in the history
…on for default encoding;

Signed-off-by: Cai Xuye <[email protected]>
  • Loading branch information
a1994846931931 authored and akosyakov committed Aug 14, 2019
1 parent 3d2d2fd commit 5a1f7b1
Show file tree
Hide file tree
Showing 16 changed files with 583 additions and 24 deletions.
3 changes: 2 additions & 1 deletion packages/core/src/common/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ export interface Resource extends Disposable {
saveContents?(content: string, options?: { encoding?: string }): Promise<void>;
saveContentChanges?(changes: TextDocumentContentChangeEvent[], options?: { encoding?: string }): Promise<void>;
readonly onDidChangeContents?: Event<void>;
guessEncoding?(): Promise<string | undefined>
}
export namespace Resource {
export interface SaveContext {
content: string
changes?: TextDocumentContentChangeEvent[]
options?: { encoding?: string }
options?: { encoding?: string, overwriteEncoding?: string }
}
export async function save(resource: Resource, context: SaveContext, token?: CancellationToken): Promise<void> {
if (!resource.saveContents) {
Expand Down
93 changes: 93 additions & 0 deletions packages/editor/src/browser/editor-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import URI from '@theia/core/lib/common/uri';
import { CommonCommands, PreferenceService, QuickPickItem, QuickPickService, LabelProvider, QuickPickValue } from '@theia/core/lib/browser';
import { Languages, Language } from '@theia/languages/lib/browser';
import { EditorManager } from './editor-manager';
import { EncodingMode } from './editor';
import { EditorPreferences } from './editor-preferences';
import { SUPPORTED_ENCODINGS } from './supported-encodings';
import { ResourceProvider, MessageService } from '@theia/core';

export namespace EditorCommands {

Expand Down Expand Up @@ -59,6 +63,11 @@ export namespace EditorCommands {
category: EDITOR_CATEGORY,
label: 'Change Language Mode'
};
export const CHANGE_ENCODING: Command = {
id: 'textEditor.change.encoding',
category: EDITOR_CATEGORY,
label: 'Change File Encoding'
};

/**
* Command for going back to the last editor navigation location.
Expand Down Expand Up @@ -118,9 +127,14 @@ export class EditorCommandContribution implements CommandContribution {
@inject(PreferenceService)
protected readonly preferencesService: PreferenceService;

@inject(EditorPreferences)
protected readonly editorPreferences: EditorPreferences;

@inject(QuickPickService)
protected readonly quickPick: QuickPickService;

@inject(MessageService) protected readonly messageService: MessageService;

@inject(LabelProvider)
protected readonly labelProvider: LabelProvider;

Expand All @@ -130,6 +144,9 @@ export class EditorCommandContribution implements CommandContribution {
@inject(EditorManager)
protected readonly editorManager: EditorManager;

@inject(ResourceProvider)
protected readonly resourceProvider: ResourceProvider;

registerCommands(registry: CommandRegistry): void {
registry.registerCommand(EditorCommands.SHOW_REFERENCES);
registry.registerCommand(EditorCommands.CONFIG_INDENTATION);
Expand All @@ -141,6 +158,11 @@ export class EditorCommandContribution implements CommandContribution {
isVisible: () => this.canConfigureLanguage(),
execute: () => this.configureLanguage()
});
registry.registerCommand(EditorCommands.CHANGE_ENCODING, {
isEnabled: () => this.canConfigureEncoding(),
isVisible: () => this.canConfigureEncoding(),
execute: () => this.configureEncoding()
});

registry.registerCommand(EditorCommands.GO_BACK);
registry.registerCommand(EditorCommands.GO_FORWARD);
Expand Down Expand Up @@ -182,6 +204,77 @@ export class EditorCommandContribution implements CommandContribution {
editor.setLanguage(selected.id);
}
}

protected canConfigureEncoding(): boolean {
const widget = this.editorManager.currentEditor;
const editor = widget && widget.editor;
return !!editor;
}
protected async configureEncoding(): Promise<void> {
const widget = this.editorManager.currentEditor;
const editor = widget && widget.editor;
if (!editor) {
return;
}
const reopenWithEncodingPick = { label: 'Reopen with Encoding', value: 'reopen' };
const saveWithEncodingPick = { label: 'Save with Encoding', value: 'save' };
const actionItems: QuickPickItem<string>[] = [
reopenWithEncodingPick,
saveWithEncodingPick
];
const action = await this.quickPick.show(actionItems, {
placeholder: 'Select Action'
});
if (!action) {
return;
}
const isReopenWithEncoding = (action === reopenWithEncodingPick.value);

const configuredEncoding = this.editorPreferences.get('files.encoding');

const resource = await this.resourceProvider(editor.uri);
const guessedEncoding = resource.guessEncoding ? await resource.guessEncoding() : undefined;
resource.dispose();

const encodingItems: QuickPickItem<{ id: string, description: string }>[] = Object.keys(SUPPORTED_ENCODINGS)
.sort((k1, k2) => {
if (k1 === configuredEncoding) {
return -1;
} else if (k2 === configuredEncoding) {
return 1;
}
return SUPPORTED_ENCODINGS[k1].order - SUPPORTED_ENCODINGS[k2].order;
})
.filter(k => {
if (k === guessedEncoding && guessedEncoding !== configuredEncoding) {
return false; // do not show encoding if it is the guessed encoding that does not match the configured
}

return !isReopenWithEncoding || !SUPPORTED_ENCODINGS[k].encodeOnly; // hide those that can only be used for encoding if we are about to decode
})
.map(key => ({ label: SUPPORTED_ENCODINGS[key].labelLong, value: { id: key, description: key } }));

// Insert guessed encoding
if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) {
encodingItems.unshift({
label: `Guessed from content: ${SUPPORTED_ENCODINGS[guessedEncoding].labelLong}`,
value: { id: guessedEncoding, description: guessedEncoding }
});
}
const encoding = await this.quickPick.show(encodingItems, {
placeholder: isReopenWithEncoding ? 'Select File Encoding to Reopen File' : 'Select File Encoding to Save with'
});
if (!encoding) {
return;
}
if (editor.document.dirty && isReopenWithEncoding) {
this.messageService.info('The file is dirty. Please save it first before reopening it with another encoding.');
return;
} else {
editor.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode);
}
}

protected async toQuickPickLanguage(value: Language, current: string): Promise<QuickPickValue<Language>> {
const languageUri = this.toLanguageUri(value);
const iconClass = await this.labelProvider.getIcon(languageUri) + ' file-icon';
Expand Down
16 changes: 16 additions & 0 deletions packages/editor/src/browser/editor-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { EditorCommands } from './editor-command';
import { EditorQuickOpenService } from './editor-quick-open-service';
import { CommandRegistry, CommandContribution } from '@theia/core/lib/common';
import { KeybindingRegistry, KeybindingContribution, QuickOpenContribution, QuickOpenHandlerRegistry } from '@theia/core/lib/browser';
import { SUPPORTED_ENCODINGS } from './supported-encodings';

@injectable()
export class EditorContribution implements FrontendApplicationContribution, CommandContribution, KeybindingContribution, QuickOpenContribution {
Expand Down Expand Up @@ -89,10 +90,12 @@ export class EditorContribution implements FrontendApplicationContribution, Comm
const widget = this.editorManager.currentEditor;
const editor = widget && widget.editor;
this.updateLanguageStatus(editor);
this.updateEncodingStatus(editor);
this.setCursorPositionStatus(editor);
if (editor) {
this.toDisposeOnCurrentEditorChanged.pushAll([
editor.onLanguageChanged(() => this.updateLanguageStatus(editor)),
editor.onEncodingChanged(() => this.updateEncodingStatus(editor)),
editor.onCursorPositionChanged(() => this.setCursorPositionStatus(editor))
]);
}
Expand All @@ -113,6 +116,19 @@ export class EditorContribution implements FrontendApplicationContribution, Comm
});
}

protected updateEncodingStatus(editor: TextEditor | undefined): void {
if (!editor) {
this.statusBar.removeElement('editor-status-encoding');
return;
}
this.statusBar.setElement('editor-status-encoding', {
text: SUPPORTED_ENCODINGS[editor.getEncoding()].labelShort,
alignment: StatusBarAlignment.RIGHT,
priority: 10,
command: EditorCommands.CHANGE_ENCODING.id
});
}

protected setCursorPositionStatus(editor: TextEditor | undefined): void {
if (!editor) {
this.statusBar.removeElement('editor-status-cursor-position');
Expand Down
7 changes: 7 additions & 0 deletions packages/editor/src/browser/editor-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
PreferenceChangeEvent
} from '@theia/core/lib/browser/preferences';
import { isWindows, isOSX } from '@theia/core/lib/common/os';
import { SUPPORTED_ENCODINGS } from './supported-encodings';

const DEFAULT_WINDOWS_FONT_FAMILY = 'Consolas, \'Courier New\', monospace';
const DEFAULT_MAC_FONT_FAMILY = 'Menlo, Monaco, \'Courier New\', monospace';
Expand Down Expand Up @@ -518,6 +519,11 @@ export const editorPreferenceSchema: PreferenceSchema = {
],
'default': 'auto',
'description': 'The default end of line character.'
},
'files.encoding': {
'enum': Object.keys(SUPPORTED_ENCODINGS).sort(),
'default': 'utf8',
'description': 'The default character set encoding to use when reading and writing files.'
}
}
};
Expand Down Expand Up @@ -597,6 +603,7 @@ export interface EditorConfiguration {
'diffEditor.ignoreCharChanges'?: boolean
'diffEditor.alwaysRevealFirst'?: boolean
'files.eol': EndOfLinePreference
'files.encoding': string
}
export type EndOfLinePreference = '\n' | '\r\n' | 'auto';

Expand Down
25 changes: 25 additions & 0 deletions packages/editor/src/browser/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,19 @@ export interface EditorMouseEvent {
readonly target: MouseTarget;
}

export const enum EncodingMode {

/**
* Instructs the encoding support to encode the current input with the provided encoding
*/
Encode,

/**
* Instructs the encoding support to decode the current input with the provided encoding
*/
Decode
}

export interface TextEditor extends Disposable, TextEditorSelection, Navigatable {
readonly node: HTMLElement;

Expand Down Expand Up @@ -219,6 +232,18 @@ export interface TextEditor extends Disposable, TextEditorSelection, Navigatable
detectLanguage(): void;
setLanguage(languageId: string): void;
readonly onLanguageChanged: Event<string>;

/**
* Gets the encoding of the input if known.
*/
getEncoding(): string;

/**
* Sets the encoding for the input for saving.
*/
setEncoding(encoding: string, mode: EncodingMode): void;

readonly onEncodingChanged: Event<string>;
}

export interface Dimension {
Expand Down
Loading

0 comments on commit 5a1f7b1

Please sign in to comment.