diff --git a/src/vs/editor/common/modes/textToHtmlTokenizer.ts b/src/vs/editor/common/modes/textToHtmlTokenizer.ts
index 80212b742647156da43cbb38be64653dcc5cae78..4b1afa955ea4b48c08a6a443fa976a90767732c8 100644
--- a/src/vs/editor/common/modes/textToHtmlTokenizer.ts
+++ b/src/vs/editor/common/modes/textToHtmlTokenizer.ts
@@ -77,6 +77,10 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens
partContent += '';
break;
+ case CharCode.Space:
+ partContent += ' ';
+ break;
+
default:
partContent += String.fromCharCode(charCode);
}
diff --git a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts
index 781ea1bb6a5c3dcfd71caea0c31de139cb66d234..56dda979b62501c93639b5607a64118ad94a8caa 100644
--- a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts
+++ b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts
@@ -109,9 +109,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => {
[
'
',
'Ciao',
- ' ',
+ ' ',
'hello',
- ' ',
+ ' ',
'world!',
'
'
].join('')
@@ -122,9 +122,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => {
[
'',
'Ciao',
- ' ',
+ ' ',
'hello',
- ' ',
+ ' ',
'w',
'
'
].join('')
@@ -135,9 +135,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => {
[
'',
'Ciao',
- ' ',
+ ' ',
'hello',
- ' ',
+ ' ',
'
'
].join('')
);
@@ -147,9 +147,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => {
[
'',
'iao',
- ' ',
+ ' ',
'hello',
- ' ',
+ ' ',
'
'
].join('')
);
@@ -158,9 +158,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => {
tokenizeLineToHTML(text, lineTokens, colorMap, 4, 11, 4),
[
'',
- ' ',
+ ' ',
'hello',
- ' ',
+ ' ',
'
'
].join('')
);
@@ -170,7 +170,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => {
[
'',
'hello',
- ' ',
+ ' ',
'
'
].join('')
);
@@ -193,6 +193,88 @@ suite('Editor Modes - textToHtmlTokenizer', () => {
].join('')
);
});
+ test('tokenizeLineToHTML handle spaces #35954', () => {
+ const text = ' Ciao hello world!';
+ const lineTokens = new ViewLineTokens([
+ new ViewLineToken(
+ 2,
+ (
+ (1 << MetadataConsts.FOREGROUND_OFFSET)
+ ) >>> 0
+ ),
+ new ViewLineToken(
+ 6,
+ (
+ (3 << MetadataConsts.FOREGROUND_OFFSET)
+ | ((FontStyle.Bold | FontStyle.Italic) << MetadataConsts.FONT_STYLE_OFFSET)
+ ) >>> 0
+ ),
+ new ViewLineToken(
+ 9,
+ (
+ (1 << MetadataConsts.FOREGROUND_OFFSET)
+ ) >>> 0
+ ),
+ new ViewLineToken(
+ 14,
+ (
+ (4 << MetadataConsts.FOREGROUND_OFFSET)
+ ) >>> 0
+ ),
+ new ViewLineToken(
+ 15,
+ (
+ (1 << MetadataConsts.FOREGROUND_OFFSET)
+ ) >>> 0
+ ),
+ new ViewLineToken(
+ 21,
+ (
+ (5 << MetadataConsts.FOREGROUND_OFFSET)
+ | ((FontStyle.Underline) << MetadataConsts.FONT_STYLE_OFFSET)
+ ) >>> 0
+ )
+ ]);
+ const colorMap = [null!, '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff'];
+
+ assert.equal(
+ tokenizeLineToHTML(text, lineTokens, colorMap, 0, 21, 4),
+ [
+ '',
+ '  ',
+ 'Ciao',
+ '   ',
+ 'hello',
+ ' ',
+ 'world!',
+ '
'
+ ].join('')
+ );
+
+ assert.equal(
+ tokenizeLineToHTML(text, lineTokens, colorMap, 0, 17, 4),
+ [
+ '',
+ '  ',
+ 'Ciao',
+ '   ',
+ 'hello',
+ ' ',
+ 'wo',
+ '
'
+ ].join('')
+ );
+
+ assert.equal(
+ tokenizeLineToHTML(text, lineTokens, colorMap, 0, 3, 4),
+ [
+ '',
+ '  ',
+ 'C',
+ '
'
+ ].join('')
+ );
+ });
});
diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts
index c9e3bdb74a36f2627ff0e296e5e10a131f543c3c..418b2900f179e6e35a761abe2be50fe593226147 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminal.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts
@@ -35,5 +35,5 @@ export interface ITerminalInstanceService {
}
export interface IBrowserTerminalConfigHelper extends ITerminalConfigHelper {
- panelContainer: HTMLElement;
+ panelContainer: HTMLElement | undefined;
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts
index 23077803ab15c6e310069f1e3eaa178726f57590..41ceb3e71b1bc3dbc76a3c6d046d86002d9a5a8b 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts
@@ -1168,7 +1168,7 @@ export class ScrollToPreviousCommandAction extends Action {
public run(): Promise {
const instance = this.terminalService.getActiveInstance();
- if (instance) {
+ if (instance && instance.commandTracker) {
instance.commandTracker.scrollToPreviousCommand();
instance.focus();
}
@@ -1189,7 +1189,7 @@ export class ScrollToNextCommandAction extends Action {
public run(): Promise {
const instance = this.terminalService.getActiveInstance();
- if (instance) {
+ if (instance && instance.commandTracker) {
instance.commandTracker.scrollToNextCommand();
instance.focus();
}
@@ -1210,7 +1210,7 @@ export class SelectToPreviousCommandAction extends Action {
public run(): Promise {
const instance = this.terminalService.getActiveInstance();
- if (instance) {
+ if (instance && instance.commandTracker) {
instance.commandTracker.selectToPreviousCommand();
instance.focus();
}
@@ -1231,7 +1231,7 @@ export class SelectToNextCommandAction extends Action {
public run(): Promise {
const instance = this.terminalService.getActiveInstance();
- if (instance) {
+ if (instance && instance.commandTracker) {
instance.commandTracker.selectToNextCommand();
instance.focus();
}
@@ -1252,7 +1252,7 @@ export class SelectToPreviousLineAction extends Action {
public run(): Promise {
const instance = this.terminalService.getActiveInstance();
- if (instance) {
+ if (instance && instance.commandTracker) {
instance.commandTracker.selectToPreviousLine();
instance.focus();
}
@@ -1273,7 +1273,7 @@ export class SelectToNextLineAction extends Action {
public run(): Promise {
const instance = this.terminalService.getActiveInstance();
- if (instance) {
+ if (instance && instance.commandTracker) {
instance.commandTracker.selectToNextLine();
instance.focus();
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts
index 0da7d6c690bf807b7835513bfadd08282a173278..7d7aafdb6b39bdcdfb663e49a72afbcba61a48db 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts
@@ -26,11 +26,11 @@ const MAXIMUM_FONT_SIZE = 25;
* specific test cases can be written.
*/
export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
- public panelContainer: HTMLElement;
+ public panelContainer: HTMLElement | undefined;
- private _charMeasureElement: HTMLElement;
- private _lastFontMeasurement: ITerminalFont;
- public config: ITerminalConfiguration;
+ private _charMeasureElement: HTMLElement | undefined;
+ private _lastFontMeasurement: ITerminalFont | undefined;
+ public config!: ITerminalConfiguration;
private readonly _onWorkspacePermissionsChanged = new Emitter();
public get onWorkspacePermissionsChanged(): Event { return this._onWorkspacePermissionsChanged.event; }
@@ -55,49 +55,55 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
}
public configFontIsMonospace(): boolean {
- this._createCharMeasureElementIfNecessary();
const fontSize = 15;
const fontFamily = this.config.fontFamily || this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily;
const i_rect = this._getBoundingRectFor('i', fontFamily, fontSize);
const w_rect = this._getBoundingRectFor('w', fontFamily, fontSize);
- const invalidBounds = !i_rect.width || !w_rect.width;
- if (invalidBounds) {
- // There is no reason to believe the font is not Monospace.
+ // Check for invalid bounds, there is no reason to believe the font is not monospace
+ if (!i_rect || !w_rect || !i_rect.width || !w_rect.width) {
return true;
}
return i_rect.width === w_rect.width;
}
- private _createCharMeasureElementIfNecessary() {
+ private _createCharMeasureElementIfNecessary(): HTMLElement {
+ if (!this.panelContainer) {
+ throw new Error('Cannot measure element when terminal is not attached');
+ }
// Create charMeasureElement if it hasn't been created or if it was orphaned by its parent
if (!this._charMeasureElement || !this._charMeasureElement.parentElement) {
this._charMeasureElement = document.createElement('div');
this.panelContainer.appendChild(this._charMeasureElement);
}
+ return this._charMeasureElement;
}
- private _getBoundingRectFor(char: string, fontFamily: string, fontSize: number): ClientRect | DOMRect {
- const style = this._charMeasureElement.style;
+ private _getBoundingRectFor(char: string, fontFamily: string, fontSize: number): ClientRect | DOMRect | undefined {
+ let charMeasureElement: HTMLElement;
+ try {
+ charMeasureElement = this._createCharMeasureElementIfNecessary();
+ } catch {
+ return undefined;
+ }
+ const style = charMeasureElement.style;
style.display = 'inline-block';
style.fontFamily = fontFamily;
style.fontSize = fontSize + 'px';
style.lineHeight = 'normal';
- this._charMeasureElement.innerText = char;
- const rect = this._charMeasureElement.getBoundingClientRect();
+ charMeasureElement.innerText = char;
+ const rect = charMeasureElement.getBoundingClientRect();
style.display = 'none';
return rect;
}
private _measureFont(fontFamily: string, fontSize: number, letterSpacing: number, lineHeight: number): ITerminalFont {
- this._createCharMeasureElementIfNecessary();
-
const rect = this._getBoundingRectFor('X', fontFamily, fontSize);
// Bounding client rect was invalid, use last font measurement if available.
- if (this._lastFontMeasurement && !rect.width && !rect.height) {
+ if (this._lastFontMeasurement && (!rect || !rect.width || !rect.height)) {
return this._lastFontMeasurement;
}
@@ -106,8 +112,8 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
fontSize,
letterSpacing,
lineHeight,
- charWidth: rect.width,
- charHeight: Math.ceil(rect.height)
+ charWidth: rect && rect.width ? rect.width : 0,
+ charHeight: rect && rect.height ? Math.ceil(rect.height) : 0
};
return this._lastFontMeasurement;
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
index 1953e9f3b31c4faca77def702ec831d7601319bf..48edebe7c193aa0062b898f50247b32c5f251820 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
@@ -172,7 +172,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private static _lastKnownGridDimensions: IGridDimensions | undefined;
private static _idCounter = 1;
- private _processManager: ITerminalProcessManager;
+ private _processManager!: ITerminalProcessManager;
private _pressAnyKeyToCloseListener: IDisposable | undefined;
private _id: number;
@@ -185,22 +185,22 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _wrapperElement: (HTMLElement & { xterm?: XTermTerminal }) | undefined;
private _xterm: XTermTerminal | undefined;
private _xtermSearch: SearchAddon | undefined;
- private _xtermElement: HTMLDivElement;
+ private _xtermElement: HTMLDivElement | undefined;
private _terminalHasTextContextKey: IContextKey;
private _terminalA11yTreeFocusContextKey: IContextKey;
- private _cols: number;
- private _rows: number;
+ private _cols: number = 0;
+ private _rows: number = 0;
private _dimensionsOverride: ITerminalDimensions | undefined;
private _windowsShellHelper: IWindowsShellHelper | undefined;
private _xtermReadyPromise: Promise;
private _titleReadyPromise: Promise;
- private _titleReadyComplete: (title: string) => any;
+ private _titleReadyComplete: ((title: string) => any) | undefined;
private _messageTitleDisposable: IDisposable | undefined;
- private _widgetManager: TerminalWidgetManager;
- private _linkHandler: TerminalLinkHandler;
- private _commandTrackerAddon: CommandTrackerAddon;
+ private _widgetManager: TerminalWidgetManager | undefined;
+ private _linkHandler: TerminalLinkHandler | undefined;
+ private _commandTrackerAddon: CommandTrackerAddon | undefined;
private _navigationModeAddon: INavigationMode & ITerminalAddon | undefined;
public disableLayout: boolean;
@@ -228,7 +228,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
public get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; }
public get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; }
- public get commandTracker(): CommandTrackerAddon { return this._commandTrackerAddon; }
+ public get commandTracker(): CommandTrackerAddon | undefined { return this._commandTrackerAddon; }
public get navigationMode(): INavigationMode | undefined { return this._navigationModeAddon; }
private readonly _onExit = new Emitter();
@@ -487,7 +487,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._xterm.onData(data => this._processManager.write(data));
// TODO: How does the cwd work on detached processes?
this.processReady.then(async () => {
- this._linkHandler.processCwd = await this._processManager.getInitialCwd();
+ if (this._linkHandler) {
+ this._linkHandler.processCwd = await this._processManager.getInitialCwd();
+ }
});
// Init winpty compat and link handler after process creation as they rely on the
// underlying process OS
@@ -642,8 +644,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._wrapperElement.appendChild(this._xtermElement);
this._container.appendChild(this._wrapperElement);
- this._widgetManager = new TerminalWidgetManager(this._wrapperElement);
- this._processManager.onProcessReady(() => this._linkHandler.setWidgetManager(this._widgetManager));
+ const widgetManager = new TerminalWidgetManager(this._wrapperElement);
+ this._widgetManager = widgetManager;
+ this._processManager.onProcessReady(() => {
+ if (this._linkHandler) {
+ this._linkHandler.setWidgetManager(widgetManager);
+ }
+ });
const computedStyle = window.getComputedStyle(this._container);
const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10);
@@ -713,7 +720,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
public registerLinkMatcher(regex: RegExp, handler: (url: string) => void, matchIndex?: number, validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void): number {
- return this._linkHandler.registerCustomLinkHandler(regex, handler, matchIndex, validationCallback);
+ return this._linkHandler!.registerCustomLinkHandler(regex, handler, matchIndex, validationCallback);
}
public deregisterLinkMatcher(linkMatcherId: number): void {
@@ -1179,7 +1186,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private async _updateProcessCwd(): Promise {
// reset cwd if it has changed, so file based url paths can be resolved
const cwd = await this.getCwd();
- if (cwd) {
+ if (cwd && this._linkHandler) {
this._linkHandler.processCwd = cwd;
}
return cwd;
@@ -1342,11 +1349,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._windowsShellHelper = undefined;
}
const didTitleChange = title !== this._title;
- const oldTitle = this._title;
this._title = title;
if (didTitleChange) {
- if (!oldTitle) {
+ if (this._titleReadyComplete) {
this._titleReadyComplete(title);
+ this._titleReadyComplete = undefined;
}
this._onTitleChanged.fire(this);
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts
index ccfa0c6258ca325d1dcb19dcbf1b1132ea580683..ba6ddcea32f3663a8fe1bd88b720f43f1de192d3 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts
@@ -5,7 +5,7 @@
import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
-import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
+import { DisposableStore } from 'vs/base/common/lifecycle';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -67,7 +67,6 @@ interface IPath {
export class TerminalLinkHandler {
private readonly _hoverDisposables = new DisposableStore();
- private _mouseMoveDisposable: IDisposable;
private _widgetManager: TerminalWidgetManager | undefined;
private _processCwd: string | undefined;
private _gitDiffPreImagePattern: RegExp;
@@ -184,7 +183,6 @@ export class TerminalLinkHandler {
public dispose(): void {
this._hoverDisposables.dispose();
- this._mouseMoveDisposable = dispose(this._mouseMoveDisposable);
}
private _wrapLinkHandler(handler: (uri: string) => boolean | void): XtermLinkMatcherHandler {
diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts
index 6c2b76271916f0eaccefba773c40db765405e544..eb991bcabfa32fbe97516e360e6e906fb9859b4c 100644
--- a/src/vs/workbench/contrib/terminal/common/terminal.ts
+++ b/src/vs/workbench/contrib/terminal/common/terminal.ts
@@ -465,7 +465,7 @@ export interface ITerminalInstance {
* An object that tracks when commands are run and enables navigating and selecting between
* them.
*/
- readonly commandTracker: ICommandTracker;
+ readonly commandTracker: ICommandTracker | undefined;
readonly navigationMode: INavigationMode | undefined;
diff --git a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts
index 2c2635f7c1c646c7bb5eccb3a9cd083a3caf813c..b8ad7fd4c28233ff8995a36cb9eaf997a185c276 100644
--- a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts
+++ b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts
@@ -19,7 +19,7 @@ import { findExecutable } from 'vs/workbench/contrib/terminal/node/terminalEnvir
import { URI } from 'vs/base/common/uri';
export class TerminalProcess extends Disposable implements ITerminalChildProcess {
- private _exitCode: number;
+ private _exitCode: number | undefined;
private _closeTimeout: any;
private _ptyProcess: pty.IPty | undefined;
private _currentTitle: string = '';
@@ -188,7 +188,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
} catch (ex) {
// Swallow, the pty has already been killed
}
- this._onProcessExit.fire(this._exitCode);
+ this._onProcessExit.fire(this._exitCode || 0);
this.dispose();
});
}