Merge branch 'master' into fixing-#5270

This commit is contained in:
Daniel Imms
2025-01-07 06:34:33 -08:00
committed by GitHub
9 changed files with 92 additions and 30 deletions
+2 -2
View File
@@ -167,9 +167,9 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [18] # just one as integration tests are about testing in browser node-version: [18] # just one as integration tests are about testing in browser
runs-on: [ubuntu] # macos is flaky runs-on: [ubuntu-22.04] # macos is flaky
browser: [chromium, firefox, webkit] browser: [chromium, firefox, webkit]
runs-on: ${{ matrix.runs-on }}-latest runs-on: ${{ matrix.runs-on }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}.x - name: Use Node.js ${{ matrix.node-version }}.x
+34 -11
View File
@@ -377,8 +377,11 @@ export class WebglRenderer extends Disposable implements IRenderer {
let line: IBufferLine; let line: IBufferLine;
let joinedRanges: [number, number][]; let joinedRanges: [number, number][];
let isJoined: boolean; let isJoined: boolean;
let skipJoinedCheckUntilX: number = 0;
let isValidJoinRange: boolean = true;
let lastCharX: number; let lastCharX: number;
let range: [number, number]; let range: [number, number];
let isCursorRow: boolean;
let chars: string; let chars: string;
let code: number; let code: number;
let width: number; let width: number;
@@ -405,6 +408,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
row = y + terminal.buffer.ydisp; row = y + terminal.buffer.ydisp;
line = terminal.buffer.lines.get(row)!; line = terminal.buffer.lines.get(row)!;
this._model.lineLengths[y] = 0; this._model.lineLengths[y] = 0;
isCursorRow = cursorY === row;
skipJoinedCheckUntilX = 0;
joinedRanges = this._characterJoinerService.getJoinedCharacters(row); joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
for (x = 0; x < terminal.cols; x++) { for (x = 0; x < terminal.cols; x++) {
lastBg = this._cellColorResolver.result.bg; lastBg = this._cellColorResolver.result.bg;
@@ -416,25 +421,43 @@ export class WebglRenderer extends Disposable implements IRenderer {
// If true, indicates that the current character(s) to draw were joined. // If true, indicates that the current character(s) to draw were joined.
isJoined = false; isJoined = false;
// Indicates whether this cell is part of a joined range that should be ignored as it cannot
// be rendered entirely, like the selection state differs across the range.
isValidJoinRange = (x >= skipJoinedCheckUntilX);
lastCharX = x; lastCharX = x;
// Process any joined character ranges as needed. Because of how the // Process any joined character ranges as needed. Because of how the
// ranges are produced, we know that they are valid for the characters // ranges are produced, we know that they are valid for the characters
// and attributes of our input. // and attributes of our input.
if (joinedRanges.length > 0 && x === joinedRanges[0][0]) { if (joinedRanges.length > 0 && x === joinedRanges[0][0] && isValidJoinRange) {
isJoined = true;
range = joinedRanges.shift()!; range = joinedRanges.shift()!;
// We already know the exact start and end column of the joined range, // If the ligature's selection state is not consistent, don't join it. This helps the
// so we get the string and width representing it directly. // selection render correctly regardless whether they should be joined.
cell = new JoinedCellData( const firstSelectionState = this._model.selection.isCellSelected(this._terminal, range[0], row);
cell, for (i = range[0] + 1; i < range[1]; i++) {
line!.translateToString(true, range[0], range[1]), isValidJoinRange &&= (firstSelectionState === this._model.selection.isCellSelected(this._terminal, i, row));
range[1] - range[0] }
); // Similarly, if the cursor is in the ligature, don't join it.
isValidJoinRange &&= !isCursorRow || cursorX < range[0] || cursorX >= range[1];
if (!isValidJoinRange) {
skipJoinedCheckUntilX = range[1];
} else {
isJoined = true;
// Skip over the cells occupied by this range in the loop // We already know the exact start and end column of the joined range,
lastCharX = range[1] - 1; // so we get the string and width representing it directly.
cell = new JoinedCellData(
cell,
line!.translateToString(true, range[0], range[1]),
range[1] - range[0]
);
// Skip over the cells occupied by this range in the loop
lastCharX = range[1] - 1;
}
} }
chars = cell.getChars(); chars = cell.getChars();
+15
View File
@@ -240,6 +240,7 @@ if (document.location.pathname === '/test') {
document.getElementById('add-decoration').addEventListener('click', addDecoration); document.getElementById('add-decoration').addEventListener('click', addDecoration);
document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler); document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler);
document.getElementById('decoration-stress-test').addEventListener('click', decorationStressTest); document.getElementById('decoration-stress-test').addEventListener('click', decorationStressTest);
document.getElementById('ligatures-test').addEventListener('click', ligaturesTest);
document.getElementById('weblinks-test').addEventListener('click', testWeblinks); document.getElementById('weblinks-test').addEventListener('click', testWeblinks);
document.getElementById('bce').addEventListener('click', coloredErase); document.getElementById('bce').addEventListener('click', coloredErase);
addVtButtons(); addVtButtons();
@@ -1307,6 +1308,20 @@ function addVtButtons(): void {
document.querySelector('#vt-container').appendChild(vtFragment); document.querySelector('#vt-container').appendChild(vtFragment);
} }
function ligaturesTest(): void {
term.write([
'',
'-<< -< -<- <-- <--- <<- <- -> ->> --> ---> ->- >- >>-',
'=<< =< =<= <== <=== <<= <= => =>> ==> ===> =>= >= >>=',
'<-> <--> <---> <----> <=> <==> <===> <====> :: ::: __',
'<~~ </ </> /> ~~> == != /= ~= <> === !== !=== =/= =!=',
'<: := *= *+ <* <*> *> <| <|> |> <. <.> .> +* =* =: :>',
'(* *) /* */ [| |] {| |} ++ +++ \/ /\ |- -| <!-- <!---',
'==== ===== ====== ======= ======== =========',
'---- ----- ------ ------- -------- ---------'
].join('\r\n'));
}
function testWeblinks(): void { function testWeblinks(): void {
const linkExamples = ` const linkExamples = `
aaa http://example.com aaa http://example.com aaa aaa http://example.com aaa http://example.com aaa
+3
View File
@@ -106,6 +106,9 @@
<dd><button id="add-overview-ruler" title="Add an overview ruler to the terminal">Add Overview Ruler</button></dd> <dd><button id="add-overview-ruler" title="Add an overview ruler to the terminal">Add Overview Ruler</button></dd>
<dd><button id="decoration-stress-test" title="Toggle between adding and removing a decoration to each line">Stress Test</button></dd> <dd><button id="decoration-stress-test" title="Toggle between adding and removing a decoration to each line">Stress Test</button></dd>
<dt>Ligatures Addon</dt>
<dd><button id="ligatures-test" title="Write common ligatures sequences">Common ligatures</button></dd>
<dt>Weblinks Addon</dt> <dt>Weblinks Addon</dt>
<dd><button id="weblinks-test" title="Various url conditions from demo data, hover&click to test">Test URLs</button></dd> <dd><button id="weblinks-test" title="Various url conditions from demo data, hover&click to test">Test URLs</button></dd>
+1 -1
View File
@@ -400,7 +400,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
} }
// If the terminal is already opened // If the terminal is already opened
if (this.element?.ownerDocument.defaultView && this._coreBrowserService) { if (this.element?.ownerDocument.defaultView && this._coreBrowserService && this.element?.isConnected) {
// Adjust the window if needed // Adjust the window if needed
if (this.element.ownerDocument.defaultView !== this._coreBrowserService.window) { if (this.element.ownerDocument.defaultView !== this._coreBrowserService.window) {
this._coreBrowserService.window = this.element.ownerDocument.defaultView; this._coreBrowserService.window = this.element.ownerDocument.defaultView;
@@ -84,6 +84,7 @@ export class DomRendererRowFactory {
let charElement: HTMLSpanElement | undefined; let charElement: HTMLSpanElement | undefined;
let cellAmount = 0; let cellAmount = 0;
let text = ''; let text = '';
let i = 0;
let oldBg = 0; let oldBg = 0;
let oldFg = 0; let oldFg = 0;
let oldExt = 0; let oldExt = 0;
@@ -91,6 +92,7 @@ export class DomRendererRowFactory {
let oldSpacing = 0; let oldSpacing = 0;
let oldIsInSelection: boolean = false; let oldIsInSelection: boolean = false;
let spacing = 0; let spacing = 0;
let skipJoinedCheckUntilX = 0;
const classes: string[] = []; const classes: string[] = [];
const hasHover = linkStart !== -1 && linkEnd !== -1; const hasHover = linkStart !== -1 && linkEnd !== -1;
@@ -106,29 +108,46 @@ export class DomRendererRowFactory {
// If true, indicates that the current character(s) to draw were joined. // If true, indicates that the current character(s) to draw were joined.
let isJoined = false; let isJoined = false;
// Indicates whether this cell is part of a joined range that should be ignored as it cannot
// be rendered entirely, like the selection state differs across the range.
let isValidJoinRange = (x >= skipJoinedCheckUntilX);
let lastCharX = x; let lastCharX = x;
// Process any joined character ranges as needed. Because of how the // Process any joined character ranges as needed. Because of how the
// ranges are produced, we know that they are valid for the characters // ranges are produced, we know that they are valid for the characters
// and attributes of our input. // and attributes of our input.
let cell = this._workCell; let cell = this._workCell;
if (joinedRanges.length > 0 && x === joinedRanges[0][0]) { if (joinedRanges.length > 0 && x === joinedRanges[0][0] && isValidJoinRange) {
isJoined = true;
const range = joinedRanges.shift()!; const range = joinedRanges.shift()!;
// If the ligature's selection state is not consistent, don't join it. This helps the
// selection render correctly regardless whether they should be joined.
const firstSelectionState = this._isCellInSelection(range[0], row);
for (i = range[0] + 1; i < range[1]; i++) {
isValidJoinRange &&= (firstSelectionState === this._isCellInSelection(i, row));
}
// Similarly, if the cursor is in the ligature, don't join it.
isValidJoinRange &&= !isCursorRow || cursorX < range[0] || cursorX >= range[1];
if (!isValidJoinRange) {
skipJoinedCheckUntilX = range[1];
} else {
isJoined = true;
// We already know the exact start and end column of the joined range, // We already know the exact start and end column of the joined range,
// so we get the string and width representing it directly // so we get the string and width representing it directly
cell = new JoinedCellData( cell = new JoinedCellData(
this._workCell, this._workCell,
lineData.translateToString(true, range[0], range[1]), lineData.translateToString(true, range[0], range[1]),
range[1] - range[0] range[1] - range[0]
); );
// Skip over the cells occupied by this range in the loop // Skip over the cells occupied by this range in the loop
lastCharX = range[1] - 1; lastCharX = range[1] - 1;
// Recalculate width // Recalculate width
width = cell.getWidth(); width = cell.getWidth();
}
} }
const isInSelection = this._isCellInSelection(x, row); const isInSelection = this._isCellInSelection(x, row);
@@ -178,6 +197,7 @@ export class DomRendererRowFactory {
&& !isCursorCell && !isCursorCell
&& !isJoined && !isJoined
&& !isDecorated && !isDecorated
&& isValidJoinRange
) { ) {
// no span alterations, thus only account chars skipping all code below // no span alterations, thus only account chars skipping all code below
if (cell.isInvisible()) { if (cell.isInvisible()) {
@@ -435,7 +455,7 @@ export class DomRendererRowFactory {
} }
// exclude conditions for cell merging - never merge these // exclude conditions for cell merging - never merge these
if (!isCursorCell && !isJoined && !isDecorated) { if (!isCursorCell && !isJoined && !isDecorated && isValidJoinRange) {
cellAmount++; cellAmount++;
} else { } else {
charElement.textContent = text; charElement.textContent = text;
+1 -1
View File
@@ -67,7 +67,7 @@ export class InstantiationService implements IInstantiationService {
for (const dependency of serviceDependencies) { for (const dependency of serviceDependencies) {
const service = this._services.get(dependency.id); const service = this._services.get(dependency.id);
if (!service) { if (!service) {
throw new Error(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id}.`); throw new Error(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id._id}.`);
} }
serviceArgs.push(service); serviceArgs.push(service);
} }
+1 -1
View File
@@ -33,7 +33,7 @@ export function createDecorator<T>(id: string): IServiceIdentifier<T> {
storeServiceDependency(decorator, target, index); storeServiceDependency(decorator, target, index);
}; };
decorator.toString = () => id; decorator._id = id;
serviceRegistry.set(id, decorator); serviceRegistry.set(id, decorator);
return decorator; return decorator;
+1
View File
@@ -124,6 +124,7 @@ export interface ICharsetService {
export interface IServiceIdentifier<T> { export interface IServiceIdentifier<T> {
(...args: any[]): void; (...args: any[]): void;
type: T; type: T;
_id: string;
} }
export interface IBrandedService { export interface IBrandedService {