Archived
Merge from vscode 7653d836944892f83ce9e1f95c1204bafa1aec31
This commit is contained in:
+21
-1
@@ -39,6 +39,25 @@ if (args['nogpu']) { // {{SQL CARBON EDIT}}
|
||||
const userDataPath = getUserDataPath(args);
|
||||
app.setPath('userData', userDataPath);
|
||||
|
||||
// Set temp directory based on crash-reporter-directory CLI argument
|
||||
// The crash reporter will store crashes in temp folder so we need
|
||||
// to change that location accordingly.
|
||||
let crashReporterDirectory = args['crash-reporter-directory'];
|
||||
if (crashReporterDirectory) {
|
||||
crashReporterDirectory = path.normalize(crashReporterDirectory);
|
||||
|
||||
if (!fs.existsSync(crashReporterDirectory)) {
|
||||
try {
|
||||
fs.mkdirSync(crashReporterDirectory);
|
||||
} catch (error) {
|
||||
console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory does not seem to exist or cannot be created.`);
|
||||
app.exit(1);
|
||||
}
|
||||
}
|
||||
console.log(`Found --crash-reporter-directory argument. Setting temp directory to be '${crashReporterDirectory}'`);
|
||||
app.setPath('temp', crashReporterDirectory);
|
||||
}
|
||||
|
||||
// Set logs path before app 'ready' event if running portable
|
||||
// to ensure that no 'logs' folder is created on disk at a
|
||||
// location outside of the portable directory
|
||||
@@ -336,7 +355,8 @@ function parseCLIArgs() {
|
||||
'user-data-dir',
|
||||
'locale',
|
||||
'js-flags',
|
||||
'max-memory'
|
||||
'max-memory',
|
||||
'crash-reporter-directory'
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -477,27 +477,27 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
DOM.addClass(this.domNode, 'animated');
|
||||
}
|
||||
|
||||
let previousKey: KeyCode;
|
||||
let nextKey: KeyCode;
|
||||
let previousKeys: KeyCode[];
|
||||
let nextKeys: KeyCode[];
|
||||
|
||||
switch (this.options.orientation) {
|
||||
case ActionsOrientation.HORIZONTAL:
|
||||
previousKey = KeyCode.LeftArrow;
|
||||
nextKey = KeyCode.RightArrow;
|
||||
previousKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
|
||||
nextKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
|
||||
break;
|
||||
case ActionsOrientation.HORIZONTAL_REVERSE:
|
||||
previousKey = KeyCode.RightArrow;
|
||||
nextKey = KeyCode.LeftArrow;
|
||||
previousKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
|
||||
nextKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
|
||||
this.domNode.className += ' reverse';
|
||||
break;
|
||||
case ActionsOrientation.VERTICAL:
|
||||
previousKey = KeyCode.UpArrow;
|
||||
nextKey = KeyCode.DownArrow;
|
||||
previousKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
|
||||
nextKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
|
||||
this.domNode.className += ' vertical';
|
||||
break;
|
||||
case ActionsOrientation.VERTICAL_REVERSE:
|
||||
previousKey = KeyCode.DownArrow;
|
||||
nextKey = KeyCode.UpArrow;
|
||||
previousKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
|
||||
nextKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
|
||||
this.domNode.className += ' vertical reverse';
|
||||
break;
|
||||
}
|
||||
@@ -506,9 +506,9 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
let eventHandled = true;
|
||||
|
||||
if (event.equals(previousKey)) {
|
||||
if (previousKeys && (event.equals(previousKeys[0]) || event.equals(previousKeys[1]))) {
|
||||
this.focusPrevious();
|
||||
} else if (event.equals(nextKey)) {
|
||||
} else if (nextKeys && (event.equals(nextKeys[0]) || event.equals(nextKeys[1]))) {
|
||||
this.focusNext();
|
||||
} else if (event.equals(KeyCode.Escape)) {
|
||||
this._onDidCancel.fire();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-count-badge {
|
||||
padding: 3px 5px;
|
||||
padding: 3px 6px;
|
||||
border-radius: 11px;
|
||||
font-size: 11px;
|
||||
min-width: 18px;
|
||||
@@ -14,4 +14,9 @@
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.monaco-count-badge.long {
|
||||
padding: 2px 3px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface IIndexTreeNode<T, TFilterData = void> extends ITreeNode<T, TFil
|
||||
collapsible: boolean;
|
||||
collapsed: boolean;
|
||||
renderNodeCount: number;
|
||||
visibility: TreeVisibility;
|
||||
visible: boolean;
|
||||
filterData: TFilterData | undefined;
|
||||
}
|
||||
@@ -95,6 +96,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
collapsible: false,
|
||||
collapsed: false,
|
||||
renderNodeCount: 0,
|
||||
visibility: TreeVisibility.Visible,
|
||||
visible: true,
|
||||
filterData: undefined
|
||||
};
|
||||
@@ -185,6 +187,17 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
}
|
||||
|
||||
this._onDidSplice.fire({ insertedNodes: nodesToInsert, deletedNodes });
|
||||
|
||||
let node: IIndexTreeNode<T, TFilterData> | undefined = parentNode;
|
||||
|
||||
while (node) {
|
||||
if (node.visibility === TreeVisibility.Recurse) {
|
||||
this.refilter();
|
||||
break;
|
||||
}
|
||||
|
||||
node = node.parent;
|
||||
}
|
||||
}
|
||||
|
||||
rerender(location: number[]): void {
|
||||
@@ -194,7 +207,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
|
||||
const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location);
|
||||
|
||||
if (revealed) {
|
||||
if (node.visible && revealed) {
|
||||
this.list.splice(listIndex, 1, [node]);
|
||||
}
|
||||
}
|
||||
@@ -355,11 +368,13 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
collapsible: typeof treeElement.collapsible === 'boolean' ? treeElement.collapsible : (typeof treeElement.collapsed !== 'undefined'),
|
||||
collapsed: typeof treeElement.collapsed === 'undefined' ? this.collapseByDefault : treeElement.collapsed,
|
||||
renderNodeCount: 1,
|
||||
visibility: TreeVisibility.Visible,
|
||||
visible: true,
|
||||
filterData: undefined
|
||||
};
|
||||
|
||||
const visibility = this._filterNode(node, parentVisibility);
|
||||
node.visibility = visibility;
|
||||
|
||||
if (revealed) {
|
||||
treeListElements.push(node);
|
||||
|
||||
@@ -43,12 +43,12 @@ const _registry = new Registry();
|
||||
|
||||
export const iconRegistry: IIconRegistry = _registry;
|
||||
|
||||
export function registerIcon(id: string, def: Codicon) {
|
||||
export function registerIcon(id: string, def: Codicon, description?: string) {
|
||||
return new Codicon(id, def);
|
||||
}
|
||||
|
||||
export class Codicon {
|
||||
constructor(public readonly id: string, public readonly definition: Codicon | IconDefinition) {
|
||||
constructor(public readonly id: string, public readonly definition: Codicon | IconDefinition, public description?: string) {
|
||||
_registry.add(this);
|
||||
}
|
||||
public get classNames() { return 'codicon codicon-' + this.id; }
|
||||
|
||||
@@ -63,6 +63,7 @@ export interface IJSONSchema {
|
||||
markdownEnumDescriptions?: string[];
|
||||
markdownDescription?: string;
|
||||
doNotSuggest?: boolean;
|
||||
suggestSortText?: string;
|
||||
allowComments?: boolean;
|
||||
allowTrailingCommas?: boolean;
|
||||
}
|
||||
|
||||
+29
-34
@@ -580,9 +580,7 @@ export const enum Touch {
|
||||
AsNew = 2
|
||||
}
|
||||
|
||||
export class LinkedMap<K, V> implements Map<K, V>{
|
||||
|
||||
readonly [Symbol.toStringTag] = 'LinkedMap';
|
||||
export class LinkedMap<K, V> {
|
||||
|
||||
private _map: Map<K, Item<K, V>>;
|
||||
private _head: Item<K, V> | undefined;
|
||||
@@ -634,7 +632,7 @@ export class LinkedMap<K, V> implements Map<K, V>{
|
||||
return item.value;
|
||||
}
|
||||
|
||||
set(key: K, value: V, touch: Touch = Touch.None): this {
|
||||
set(key: K, value: V, touch: Touch = Touch.None): void {
|
||||
let item = this._map.get(key);
|
||||
if (item) {
|
||||
item.value = value;
|
||||
@@ -660,7 +658,6 @@ export class LinkedMap<K, V> implements Map<K, V>{
|
||||
this._map.set(key, item);
|
||||
this._size++;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
delete(key: K): boolean {
|
||||
@@ -704,13 +701,34 @@ export class LinkedMap<K, V> implements Map<K, V>{
|
||||
}
|
||||
}
|
||||
|
||||
keys(): IterableIterator<K> {
|
||||
values(): V[] {
|
||||
const result: V[] = [];
|
||||
let current = this._head;
|
||||
while (current) {
|
||||
result.push(current.value);
|
||||
current = current.next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
keys(): K[] {
|
||||
const result: K[] = [];
|
||||
let current = this._head;
|
||||
while (current) {
|
||||
result.push(current.key);
|
||||
current = current.next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* VS Code / Monaco editor runs on es5 which has no Symbol.iterator
|
||||
keys(): IterableIterator<K> {
|
||||
const current = this._head;
|
||||
const iterator: IterableIterator<K> = {
|
||||
[Symbol.iterator]() {
|
||||
return iterator;
|
||||
},
|
||||
next(): IteratorResult<K> {
|
||||
next():IteratorResult<K> {
|
||||
if (current) {
|
||||
const result = { value: current.key, done: false };
|
||||
current = current.next;
|
||||
@@ -724,12 +742,12 @@ export class LinkedMap<K, V> implements Map<K, V>{
|
||||
}
|
||||
|
||||
values(): IterableIterator<V> {
|
||||
let current = this._head;
|
||||
const current = this._head;
|
||||
const iterator: IterableIterator<V> = {
|
||||
[Symbol.iterator]() {
|
||||
return iterator;
|
||||
},
|
||||
next(): IteratorResult<V> {
|
||||
next():IteratorResult<V> {
|
||||
if (current) {
|
||||
const result = { value: current.value, done: false };
|
||||
current = current.next;
|
||||
@@ -741,29 +759,7 @@ export class LinkedMap<K, V> implements Map<K, V>{
|
||||
};
|
||||
return iterator;
|
||||
}
|
||||
|
||||
entries(): IterableIterator<[K, V]> {
|
||||
let current = this._head;
|
||||
const iterator: IterableIterator<[K, V]> = {
|
||||
[Symbol.iterator]() {
|
||||
return iterator;
|
||||
},
|
||||
next(): IteratorResult<[K, V]> {
|
||||
if (current) {
|
||||
const result: IteratorResult<[K, V]> = { value: [current.key, current.value], done: false };
|
||||
current = current.next;
|
||||
return result;
|
||||
} else {
|
||||
return { value: undefined, done: true };
|
||||
}
|
||||
}
|
||||
};
|
||||
return iterator;
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<[K, V]> {
|
||||
return this.entries();
|
||||
}
|
||||
*/
|
||||
|
||||
protected trimOld(newSize: number) {
|
||||
if (newSize >= this.size) {
|
||||
@@ -965,10 +961,9 @@ export class LRUCache<K, V> extends LinkedMap<K, V> {
|
||||
return super.get(key, Touch.None);
|
||||
}
|
||||
|
||||
set(key: K, value: V): this {
|
||||
set(key: K, value: V): void {
|
||||
super.set(key, value, Touch.AsNew);
|
||||
this.checkTrim();
|
||||
return this;
|
||||
}
|
||||
|
||||
private checkTrim() {
|
||||
|
||||
@@ -97,10 +97,14 @@
|
||||
align-self: center;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.quick-input-count .monaco-count-badge {
|
||||
vertical-align: middle;
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.quick-input-action {
|
||||
@@ -108,9 +112,8 @@
|
||||
}
|
||||
|
||||
.quick-input-action .monaco-text-button {
|
||||
font-size: 85%;
|
||||
font-size: 11px;
|
||||
padding: 0 6px;
|
||||
line-height: initial;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ITreeNode, ITreeFilter, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
|
||||
function toSpliceable<T>(arr: T[]): ISpliceable<T> {
|
||||
return {
|
||||
splice(start: number, deleteCount: number, elements: T[]): void {
|
||||
// console.log(`splice (${start}, ${deleteCount}, ${elements.length} [${elements.join(', ')}] )`); // debugging
|
||||
arr.splice(start, deleteCount, ...elements);
|
||||
}
|
||||
};
|
||||
@@ -240,4 +241,35 @@ suite('ObjectTreeModel', function () {
|
||||
model.expandTo(1000);
|
||||
assert.deepEqual(toArray(list), [0, 10, 100, 1000, 11, 12, 1, 2]);
|
||||
});
|
||||
|
||||
test('issue #95641', () => {
|
||||
const list: ITreeNode<string>[] = [];
|
||||
let fn = (_: string) => true;
|
||||
const filter = new class implements ITreeFilter<string> {
|
||||
filter(element: string, parentVisibility: TreeVisibility): TreeVisibility {
|
||||
if (element === 'file') {
|
||||
return TreeVisibility.Recurse;
|
||||
}
|
||||
|
||||
return fn(element) ? TreeVisibility.Visible : parentVisibility;
|
||||
}
|
||||
};
|
||||
const model = new ObjectTreeModel<string>('test', toSpliceable(list), { filter });
|
||||
|
||||
model.setChildren(null, [{ element: 'file', children: [{ element: 'hello' }] }]);
|
||||
assert.deepEqual(toArray(list), ['file', 'hello']);
|
||||
|
||||
fn = (el: string) => el === 'world';
|
||||
model.refilter();
|
||||
assert.deepEqual(toArray(list), []);
|
||||
|
||||
model.setChildren('file', [{ element: 'world' }]);
|
||||
assert.deepEqual(toArray(list), ['file', 'world']);
|
||||
|
||||
model.setChildren('file', [{ element: 'hello' }]);
|
||||
assert.deepEqual(toArray(list), []);
|
||||
|
||||
model.setChildren('file', [{ element: 'world' }]);
|
||||
assert.deepEqual(toArray(list), ['file', 'world']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,8 +13,8 @@ suite('Map', () => {
|
||||
let map = new LinkedMap<string, string>();
|
||||
map.set('ak', 'av');
|
||||
map.set('bk', 'bv');
|
||||
assert.deepStrictEqual([...map.keys()], ['ak', 'bk']);
|
||||
assert.deepStrictEqual([...map.values()], ['av', 'bv']);
|
||||
assert.deepStrictEqual(map.keys(), ['ak', 'bk']);
|
||||
assert.deepStrictEqual(map.values(), ['av', 'bv']);
|
||||
assert.equal(map.first, 'av');
|
||||
assert.equal(map.last, 'bv');
|
||||
});
|
||||
@@ -23,16 +23,16 @@ suite('Map', () => {
|
||||
let map = new LinkedMap<string, string>();
|
||||
map.set('ak', 'av');
|
||||
map.set('ak', 'av', Touch.AsOld);
|
||||
assert.deepStrictEqual([...map.keys()], ['ak']);
|
||||
assert.deepStrictEqual([...map.values()], ['av']);
|
||||
assert.deepStrictEqual(map.keys(), ['ak']);
|
||||
assert.deepStrictEqual(map.values(), ['av']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch New one', () => {
|
||||
let map = new LinkedMap<string, string>();
|
||||
map.set('ak', 'av');
|
||||
map.set('ak', 'av', Touch.AsNew);
|
||||
assert.deepStrictEqual([...map.keys()], ['ak']);
|
||||
assert.deepStrictEqual([...map.values()], ['av']);
|
||||
assert.deepStrictEqual(map.keys(), ['ak']);
|
||||
assert.deepStrictEqual(map.values(), ['av']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch Old two', () => {
|
||||
@@ -40,8 +40,8 @@ suite('Map', () => {
|
||||
map.set('ak', 'av');
|
||||
map.set('bk', 'bv');
|
||||
map.set('bk', 'bv', Touch.AsOld);
|
||||
assert.deepStrictEqual([...map.keys()], ['bk', 'ak']);
|
||||
assert.deepStrictEqual([...map.values()], ['bv', 'av']);
|
||||
assert.deepStrictEqual(map.keys(), ['bk', 'ak']);
|
||||
assert.deepStrictEqual(map.values(), ['bv', 'av']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch New two', () => {
|
||||
@@ -49,8 +49,8 @@ suite('Map', () => {
|
||||
map.set('ak', 'av');
|
||||
map.set('bk', 'bv');
|
||||
map.set('ak', 'av', Touch.AsNew);
|
||||
assert.deepStrictEqual([...map.keys()], ['bk', 'ak']);
|
||||
assert.deepStrictEqual([...map.values()], ['bv', 'av']);
|
||||
assert.deepStrictEqual(map.keys(), ['bk', 'ak']);
|
||||
assert.deepStrictEqual(map.values(), ['bv', 'av']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch Old from middle', () => {
|
||||
@@ -59,8 +59,8 @@ suite('Map', () => {
|
||||
map.set('bk', 'bv');
|
||||
map.set('ck', 'cv');
|
||||
map.set('bk', 'bv', Touch.AsOld);
|
||||
assert.deepStrictEqual([...map.keys()], ['bk', 'ak', 'ck']);
|
||||
assert.deepStrictEqual([...map.values()], ['bv', 'av', 'cv']);
|
||||
assert.deepStrictEqual(map.keys(), ['bk', 'ak', 'ck']);
|
||||
assert.deepStrictEqual(map.values(), ['bv', 'av', 'cv']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch New from middle', () => {
|
||||
@@ -69,8 +69,8 @@ suite('Map', () => {
|
||||
map.set('bk', 'bv');
|
||||
map.set('ck', 'cv');
|
||||
map.set('bk', 'bv', Touch.AsNew);
|
||||
assert.deepStrictEqual([...map.keys()], ['ak', 'ck', 'bk']);
|
||||
assert.deepStrictEqual([...map.values()], ['av', 'cv', 'bv']);
|
||||
assert.deepStrictEqual(map.keys(), ['ak', 'ck', 'bk']);
|
||||
assert.deepStrictEqual(map.values(), ['av', 'cv', 'bv']);
|
||||
});
|
||||
|
||||
test('LinkedMap - basics', function () {
|
||||
@@ -136,15 +136,13 @@ suite('Map', () => {
|
||||
assert.strictEqual(cache.size, 5);
|
||||
cache.set(6, 6);
|
||||
assert.strictEqual(cache.size, 5);
|
||||
assert.deepStrictEqual([...cache.keys()], [2, 3, 4, 5, 6]);
|
||||
assert.deepStrictEqual(cache.keys(), [2, 3, 4, 5, 6]);
|
||||
cache.set(7, 7);
|
||||
assert.strictEqual(cache.size, 5);
|
||||
assert.deepStrictEqual([...cache.keys()], [3, 4, 5, 6, 7]);
|
||||
assert.deepStrictEqual(cache.keys(), [3, 4, 5, 6, 7]);
|
||||
let values: number[] = [];
|
||||
[3, 4, 5, 6, 7].forEach(key => values.push(cache.get(key)!));
|
||||
assert.deepStrictEqual(values, [3, 4, 5, 6, 7]);
|
||||
|
||||
assert.deepEqual([...cache.entries()], [[3, 3], [4, 4], [5, 5], [6, 6], [7, 7]]);
|
||||
});
|
||||
|
||||
test('LinkedMap - LRU Cache get', () => {
|
||||
@@ -152,11 +150,11 @@ suite('Map', () => {
|
||||
|
||||
[1, 2, 3, 4, 5].forEach(value => cache.set(value, value));
|
||||
assert.strictEqual(cache.size, 5);
|
||||
assert.deepStrictEqual([...cache.keys()], [1, 2, 3, 4, 5]);
|
||||
assert.deepStrictEqual(cache.keys(), [1, 2, 3, 4, 5]);
|
||||
cache.get(3);
|
||||
assert.deepStrictEqual([...cache.keys()], [1, 2, 4, 5, 3]);
|
||||
assert.deepStrictEqual(cache.keys(), [1, 2, 4, 5, 3]);
|
||||
cache.peek(4);
|
||||
assert.deepStrictEqual([...cache.keys()], [1, 2, 4, 5, 3]);
|
||||
assert.deepStrictEqual(cache.keys(), [1, 2, 4, 5, 3]);
|
||||
let values: number[] = [];
|
||||
[1, 2, 3, 4, 5].forEach(key => values.push(cache.get(key)!));
|
||||
assert.deepStrictEqual(values, [1, 2, 3, 4, 5]);
|
||||
@@ -171,7 +169,7 @@ suite('Map', () => {
|
||||
assert.strictEqual(cache.size, 10);
|
||||
cache.limit = 5;
|
||||
assert.strictEqual(cache.size, 5);
|
||||
assert.deepStrictEqual([...cache.keys()], [6, 7, 8, 9, 10]);
|
||||
assert.deepStrictEqual(cache.keys(), [6, 7, 8, 9, 10]);
|
||||
cache.limit = 20;
|
||||
assert.strictEqual(cache.size, 5);
|
||||
for (let i = 11; i <= 20; i++) {
|
||||
@@ -183,7 +181,7 @@ suite('Map', () => {
|
||||
values.push(cache.get(i)!);
|
||||
assert.strictEqual(cache.get(i), i);
|
||||
}
|
||||
assert.deepStrictEqual([...cache.values()], values);
|
||||
assert.deepStrictEqual(cache.values(), values);
|
||||
});
|
||||
|
||||
test('LinkedMap - LRU Cache limit with ratio', () => {
|
||||
@@ -195,11 +193,11 @@ suite('Map', () => {
|
||||
assert.strictEqual(cache.size, 10);
|
||||
cache.set(11, 11);
|
||||
assert.strictEqual(cache.size, 5);
|
||||
assert.deepStrictEqual([...cache.keys()], [7, 8, 9, 10, 11]);
|
||||
assert.deepStrictEqual(cache.keys(), [7, 8, 9, 10, 11]);
|
||||
let values: number[] = [];
|
||||
[...cache.keys()].forEach(key => values.push(cache.get(key)!));
|
||||
cache.keys().forEach(key => values.push(cache.get(key)!));
|
||||
assert.deepStrictEqual(values, [7, 8, 9, 10, 11]);
|
||||
assert.deepStrictEqual([...cache.values()], values);
|
||||
assert.deepStrictEqual(cache.values(), values);
|
||||
});
|
||||
|
||||
test('LinkedMap - toJSON / fromJSON', () => {
|
||||
@@ -239,7 +237,7 @@ suite('Map', () => {
|
||||
map.delete('1');
|
||||
assert.equal(map.get('1'), undefined);
|
||||
assert.equal(map.size, 0);
|
||||
assert.equal([...map.keys()].length, 0);
|
||||
assert.equal(map.keys().length, 0);
|
||||
});
|
||||
|
||||
test('LinkedMap - delete Head', function () {
|
||||
@@ -253,8 +251,8 @@ suite('Map', () => {
|
||||
map.delete('1');
|
||||
assert.equal(map.get('2'), 2);
|
||||
assert.equal(map.size, 1);
|
||||
assert.equal([...map.keys()].length, 1);
|
||||
assert.equal([...map.keys()][0], 2);
|
||||
assert.equal(map.keys().length, 1);
|
||||
assert.equal(map.keys()[0], 2);
|
||||
});
|
||||
|
||||
test('LinkedMap - delete Tail', function () {
|
||||
@@ -268,8 +266,8 @@ suite('Map', () => {
|
||||
map.delete('2');
|
||||
assert.equal(map.get('1'), 1);
|
||||
assert.equal(map.size, 1);
|
||||
assert.equal([...map.keys()].length, 1);
|
||||
assert.equal([...map.keys()][0], 1);
|
||||
assert.equal(map.keys().length, 1);
|
||||
assert.equal(map.keys()[0], 1);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -31,84 +31,6 @@ function gestureChangeEventMerger(lastEvent: IThrottledGestureEvent | null, curr
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically IE10 and IE11
|
||||
*/
|
||||
class MsPointerHandler extends MouseHandler implements IDisposable {
|
||||
|
||||
private _lastPointerType: string;
|
||||
private _installGestureHandlerTimeout: number;
|
||||
|
||||
constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {
|
||||
super(context, viewController, viewHelper);
|
||||
|
||||
(this.viewHelper.linesContentDomNode.style as any).msTouchAction = 'none';
|
||||
(this.viewHelper.linesContentDomNode.style as any).msContentZooming = 'none';
|
||||
|
||||
// TODO@Alex -> this expects that the view is added in 100 ms, might not be the case
|
||||
// This handler should be added when the dom node is in the dom tree
|
||||
this._installGestureHandlerTimeout = window.setTimeout(() => {
|
||||
this._installGestureHandlerTimeout = -1;
|
||||
if ((<any>window).MSGesture) {
|
||||
const touchGesture = new MSGesture();
|
||||
const penGesture = new MSGesture();
|
||||
touchGesture.target = this.viewHelper.linesContentDomNode;
|
||||
penGesture.target = this.viewHelper.linesContentDomNode;
|
||||
this.viewHelper.linesContentDomNode.addEventListener(<any>'MSPointerDown', (e: MSPointerEvent) => {
|
||||
// Circumvent IE11 breaking change in e.pointerType & TypeScript's stale definitions
|
||||
const pointerType = <any>e.pointerType;
|
||||
if (pointerType === ((<any>e).MSPOINTER_TYPE_MOUSE || 'mouse')) {
|
||||
this._lastPointerType = 'mouse';
|
||||
return;
|
||||
} else if (pointerType === ((<any>e).MSPOINTER_TYPE_TOUCH || 'touch')) {
|
||||
this._lastPointerType = 'touch';
|
||||
touchGesture.addPointer(e.pointerId);
|
||||
} else {
|
||||
this._lastPointerType = 'pen';
|
||||
penGesture.addPointer(e.pointerId);
|
||||
}
|
||||
});
|
||||
this._register(dom.addDisposableThrottledListener<IThrottledGestureEvent, MSGestureEvent>(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger));
|
||||
this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'MSGestureTap', (e) => this._onCaptureGestureTap(e), true));
|
||||
}
|
||||
}, 100);
|
||||
this._lastPointerType = 'mouse';
|
||||
}
|
||||
|
||||
public _onMouseDown(e: EditorMouseEvent): void {
|
||||
if (this._lastPointerType === 'mouse') {
|
||||
super._onMouseDown(e);
|
||||
}
|
||||
}
|
||||
|
||||
private _onCaptureGestureTap(rawEvent: MSGestureEvent): void {
|
||||
const e = new EditorMouseEvent(<MouseEvent><any>rawEvent, this.viewHelper.viewDomNode);
|
||||
const t = this._createMouseTarget(e, false);
|
||||
if (t.position) {
|
||||
this.viewController.moveTo(t.position);
|
||||
}
|
||||
// IE does not want to focus when coming in from the browser's address bar
|
||||
if ((<any>e.browserEvent).fromElement) {
|
||||
e.preventDefault();
|
||||
this.viewHelper.focusTextArea();
|
||||
} else {
|
||||
// TODO@Alex -> cancel this is focus is lost
|
||||
setTimeout(() => {
|
||||
this.viewHelper.focusTextArea();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _onGestureChange(e: IThrottledGestureEvent): void {
|
||||
this._context.viewLayout.deltaScrollNow(-e.translationX, -e.translationY);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
window.clearTimeout(this._installGestureHandlerTimeout);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically Edge but should be modified to handle any pointerEnabled, even without support of MSGesture
|
||||
*/
|
||||
@@ -302,9 +224,7 @@ export class PointerHandler extends Disposable {
|
||||
|
||||
constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {
|
||||
super();
|
||||
if (window.navigator.msPointerEnabled) {
|
||||
this.handler = this._register(new MsPointerHandler(context, viewController, viewHelper));
|
||||
} else if ((platform.isIOS && BrowserFeatures.pointerEvents)) {
|
||||
if ((platform.isIOS && BrowserFeatures.pointerEvents)) {
|
||||
this.handler = this._register(new PointerEventHandler(context, viewController, viewHelper));
|
||||
} else if ((<any>window).TouchEvent) {
|
||||
this.handler = this._register(new TouchHandler(context, viewController, viewHelper));
|
||||
|
||||
@@ -111,7 +111,7 @@ export class EditorScrollbar extends ViewPart {
|
||||
const minimap = options.get(EditorOption.minimap);
|
||||
const side = minimap.side;
|
||||
if (side === 'right') {
|
||||
this.scrollbarDomNode.setWidth(layoutInfo.contentWidth + layoutInfo.minimapWidth);
|
||||
this.scrollbarDomNode.setWidth(layoutInfo.contentWidth + layoutInfo.minimap.minimapWidth);
|
||||
} else {
|
||||
this.scrollbarDomNode.setWidth(layoutInfo.contentWidth);
|
||||
}
|
||||
|
||||
@@ -104,30 +104,31 @@ class MinimapOptions {
|
||||
const options = configuration.options;
|
||||
const pixelRatio = options.get(EditorOption.pixelRatio);
|
||||
const layoutInfo = options.get(EditorOption.layoutInfo);
|
||||
const minimapLayout = layoutInfo.minimap;
|
||||
const fontInfo = options.get(EditorOption.fontInfo);
|
||||
const minimapOpts = options.get(EditorOption.minimap);
|
||||
|
||||
this.renderMinimap = layoutInfo.renderMinimap | 0;
|
||||
this.renderMinimap = minimapLayout.renderMinimap;
|
||||
this.size = minimapOpts.size;
|
||||
this.minimapHeightIsEditorHeight = layoutInfo.minimapHeightIsEditorHeight;
|
||||
this.minimapHeightIsEditorHeight = minimapLayout.minimapHeightIsEditorHeight;
|
||||
this.scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine);
|
||||
this.showSlider = minimapOpts.showSlider;
|
||||
this.pixelRatio = pixelRatio;
|
||||
this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
|
||||
this.lineHeight = options.get(EditorOption.lineHeight);
|
||||
this.minimapLeft = layoutInfo.minimapLeft;
|
||||
this.minimapWidth = layoutInfo.minimapWidth;
|
||||
this.minimapLeft = minimapLayout.minimapLeft;
|
||||
this.minimapWidth = minimapLayout.minimapWidth;
|
||||
this.minimapHeight = layoutInfo.height;
|
||||
|
||||
this.canvasInnerWidth = layoutInfo.minimapCanvasInnerWidth;
|
||||
this.canvasInnerHeight = layoutInfo.minimapCanvasInnerHeight;
|
||||
this.canvasOuterWidth = layoutInfo.minimapCanvasOuterWidth;
|
||||
this.canvasOuterHeight = layoutInfo.minimapCanvasOuterHeight;
|
||||
this.canvasInnerWidth = minimapLayout.minimapCanvasInnerWidth;
|
||||
this.canvasInnerHeight = minimapLayout.minimapCanvasInnerHeight;
|
||||
this.canvasOuterWidth = minimapLayout.minimapCanvasOuterWidth;
|
||||
this.canvasOuterHeight = minimapLayout.minimapCanvasOuterHeight;
|
||||
|
||||
this.isSampling = layoutInfo.minimapIsSampling;
|
||||
this.isSampling = minimapLayout.minimapIsSampling;
|
||||
this.editorHeight = layoutInfo.height;
|
||||
this.fontScale = layoutInfo.minimapScale;
|
||||
this.minimapLineHeight = layoutInfo.minimapLineHeight;
|
||||
this.fontScale = minimapLayout.minimapScale;
|
||||
this.minimapLineHeight = minimapLayout.minimapLineHeight;
|
||||
this.minimapCharWidth = Constants.BASE_CHAR_WIDTH * this.fontScale;
|
||||
|
||||
this.charRenderer = once(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily));
|
||||
@@ -262,7 +263,8 @@ class MinimapLayout {
|
||||
const computedSliderRatio = (maxMinimapSliderTop) / (scrollHeight - viewportHeight);
|
||||
const sliderTop = (scrollTop * computedSliderRatio);
|
||||
const sliderNeeded = (maxMinimapSliderTop > 0);
|
||||
return new MinimapLayout(scrollTop, scrollHeight, sliderNeeded, computedSliderRatio, sliderTop, sliderHeight, 1, lineCount);
|
||||
const maxLinesFitting = Math.floor(options.canvasInnerHeight / options.minimapLineHeight);
|
||||
return new MinimapLayout(scrollTop, scrollHeight, sliderNeeded, computedSliderRatio, sliderTop, sliderHeight, 1, Math.min(lineCount, maxLinesFitting));
|
||||
}
|
||||
|
||||
// The visible line count in a viewport can change due to a number of reasons:
|
||||
|
||||
@@ -42,7 +42,7 @@ export class ViewOverlayWidgets extends ViewPart {
|
||||
|
||||
this._widgets = {};
|
||||
this._verticalScrollbarWidth = layoutInfo.verticalScrollbarWidth;
|
||||
this._minimapWidth = layoutInfo.minimapWidth;
|
||||
this._minimapWidth = layoutInfo.minimap.minimapWidth;
|
||||
this._horizontalScrollbarHeight = layoutInfo.horizontalScrollbarHeight;
|
||||
this._editorHeight = layoutInfo.height;
|
||||
this._editorWidth = layoutInfo.width;
|
||||
@@ -68,7 +68,7 @@ export class ViewOverlayWidgets extends ViewPart {
|
||||
const layoutInfo = options.get(EditorOption.layoutInfo);
|
||||
|
||||
this._verticalScrollbarWidth = layoutInfo.verticalScrollbarWidth;
|
||||
this._minimapWidth = layoutInfo.minimapWidth;
|
||||
this._minimapWidth = layoutInfo.minimap.minimapWidth;
|
||||
this._horizontalScrollbarHeight = layoutInfo.horizontalScrollbarHeight;
|
||||
this._editorHeight = layoutInfo.height;
|
||||
this._editorWidth = layoutInfo.width;
|
||||
|
||||
@@ -58,10 +58,10 @@ export class ScrollDecorationViewPart extends ViewPart {
|
||||
const options = this._context.configuration.options;
|
||||
const layoutInfo = options.get(EditorOption.layoutInfo);
|
||||
|
||||
if (layoutInfo.renderMinimap === 0 || (layoutInfo.minimapWidth > 0 && layoutInfo.minimapLeft === 0)) {
|
||||
if (layoutInfo.minimap.renderMinimap === 0 || (layoutInfo.minimap.minimapWidth > 0 && layoutInfo.minimap.minimapLeft === 0)) {
|
||||
this._width = layoutInfo.width;
|
||||
} else {
|
||||
this._width = layoutInfo.width - layoutInfo.minimapWidth - layoutInfo.verticalScrollbarWidth;
|
||||
this._width = layoutInfo.width - layoutInfo.minimap.minimapWidth - layoutInfo.verticalScrollbarWidth;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { IEditorOptions, editorOptionsRegistry, ValidatedEditorOptions, IEnvironmentalOptions, IComputedEditorOptions, ConfigurationChangedEvent, EDITOR_MODEL_DEFAULTS, EditorOption, FindComputedEditorOptionValueById } from 'vs/editor/common/config/editorOptions';
|
||||
import { IEditorOptions, editorOptionsRegistry, ValidatedEditorOptions, IEnvironmentalOptions, IComputedEditorOptions, ConfigurationChangedEvent, EDITOR_MODEL_DEFAULTS, EditorOption, FindComputedEditorOptionValueById, ComputeOptionsMemory } from 'vs/editor/common/config/editorOptions';
|
||||
import { EditorZoom } from 'vs/editor/common/config/editorZoom';
|
||||
import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo';
|
||||
import { IConfiguration, IDimension } from 'vs/editor/common/editorCommon';
|
||||
@@ -284,6 +284,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
|
||||
public readonly onDidChange: Event<ConfigurationChangedEvent> = this._onDidChange.event;
|
||||
|
||||
public readonly isSimpleWidget: boolean;
|
||||
private _computeOptionsMemory: ComputeOptionsMemory;
|
||||
public options!: ComputedEditorOptions;
|
||||
|
||||
private _isDominatedByLongLines: boolean;
|
||||
@@ -299,6 +300,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
|
||||
this.isSimpleWidget = isSimpleWidget;
|
||||
|
||||
this._isDominatedByLongLines = false;
|
||||
this._computeOptionsMemory = new ComputeOptionsMemory();
|
||||
this._viewLineCount = 1;
|
||||
this._lineNumbersDigitCount = 1;
|
||||
|
||||
@@ -344,6 +346,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
|
||||
const partialEnv = this._getEnvConfiguration();
|
||||
const bareFontInfo = BareFontInfo.createFromValidatedSettings(this._validatedOptions, partialEnv.zoomLevel, this.isSimpleWidget);
|
||||
const env: IEnvironmentalOptions = {
|
||||
memory: this._computeOptionsMemory,
|
||||
outerWidth: partialEnv.outerWidth,
|
||||
outerHeight: partialEnv.outerHeight,
|
||||
fontInfo: this.readConfiguration(bareFontInfo),
|
||||
|
||||
@@ -693,6 +693,7 @@ export interface IComputedEditorOptions {
|
||||
* @internal
|
||||
*/
|
||||
export interface IEnvironmentalOptions {
|
||||
readonly memory: ComputeOptionsMemory | null;
|
||||
readonly outerWidth: number;
|
||||
readonly outerHeight: number;
|
||||
readonly fontInfo: FontInfo;
|
||||
@@ -706,6 +707,22 @@ export interface IEnvironmentalOptions {
|
||||
readonly accessibilitySupport: AccessibilitySupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class ComputeOptionsMemory {
|
||||
|
||||
public stableMinimapLayoutInput: IMinimapLayoutInput | null;
|
||||
public stableFitMaxMinimapScale: number;
|
||||
public stableFitRemainingWidth: number;
|
||||
|
||||
constructor() {
|
||||
this.stableMinimapLayoutInput = null;
|
||||
this.stableFitMaxMinimapScale = 0;
|
||||
this.stableFitRemainingWidth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IEditorOption<K1 extends EditorOption, V> {
|
||||
readonly id: K1;
|
||||
readonly name: string;
|
||||
@@ -1717,32 +1734,19 @@ export interface EditorLayoutInfo {
|
||||
readonly contentWidth: number;
|
||||
|
||||
/**
|
||||
* The position for the minimap
|
||||
* Layout information for the minimap
|
||||
*/
|
||||
readonly minimapLeft: number;
|
||||
/**
|
||||
* The width of the minimap
|
||||
*/
|
||||
readonly minimapWidth: number;
|
||||
readonly minimapHeightIsEditorHeight: boolean;
|
||||
readonly minimapIsSampling: boolean;
|
||||
readonly minimapScale: number;
|
||||
readonly minimapLineHeight: number;
|
||||
readonly minimapCanvasInnerWidth: number;
|
||||
readonly minimapCanvasInnerHeight: number;
|
||||
readonly minimapCanvasOuterWidth: number;
|
||||
readonly minimapCanvasOuterHeight: number;
|
||||
|
||||
/**
|
||||
* Minimap render type
|
||||
*/
|
||||
readonly renderMinimap: RenderMinimap;
|
||||
readonly minimap: EditorMinimapLayoutInfo;
|
||||
|
||||
/**
|
||||
* The number of columns (of typical characters) fitting on a viewport line.
|
||||
*/
|
||||
readonly viewportColumn: number;
|
||||
|
||||
readonly isWordWrapMinified: boolean;
|
||||
readonly isViewportWrapping: boolean;
|
||||
readonly wrappingColumn: number;
|
||||
|
||||
/**
|
||||
* The width of the vertical scrollbar.
|
||||
*/
|
||||
@@ -1758,18 +1762,80 @@ export interface EditorLayoutInfo {
|
||||
readonly overviewRuler: OverviewRulerPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* The internal layout details of the editor.
|
||||
*/
|
||||
export interface EditorMinimapLayoutInfo {
|
||||
readonly renderMinimap: RenderMinimap;
|
||||
readonly minimapLeft: number;
|
||||
readonly minimapWidth: number;
|
||||
readonly minimapHeightIsEditorHeight: boolean;
|
||||
readonly minimapIsSampling: boolean;
|
||||
readonly minimapScale: number;
|
||||
readonly minimapLineHeight: number;
|
||||
readonly minimapCanvasInnerWidth: number;
|
||||
readonly minimapCanvasInnerHeight: number;
|
||||
readonly minimapCanvasOuterWidth: number;
|
||||
readonly minimapCanvasOuterHeight: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface EditorLayoutInfoComputerEnv {
|
||||
outerWidth: number;
|
||||
outerHeight: number;
|
||||
lineHeight: number;
|
||||
viewLineCount: number;
|
||||
lineNumbersDigitCount: number;
|
||||
typicalHalfwidthCharacterWidth: number;
|
||||
maxDigitWidth: number;
|
||||
pixelRatio: number;
|
||||
readonly memory: ComputeOptionsMemory | null;
|
||||
readonly outerWidth: number;
|
||||
readonly outerHeight: number;
|
||||
readonly isDominatedByLongLines: boolean
|
||||
readonly lineHeight: number;
|
||||
readonly viewLineCount: number;
|
||||
readonly lineNumbersDigitCount: number;
|
||||
readonly typicalHalfwidthCharacterWidth: number;
|
||||
readonly maxDigitWidth: number;
|
||||
readonly pixelRatio: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface IEditorLayoutComputerInput {
|
||||
readonly outerWidth: number;
|
||||
readonly outerHeight: number;
|
||||
readonly isDominatedByLongLines: boolean
|
||||
readonly lineHeight: number;
|
||||
readonly lineNumbersDigitCount: number;
|
||||
readonly typicalHalfwidthCharacterWidth: number;
|
||||
readonly maxDigitWidth: number;
|
||||
readonly pixelRatio: number;
|
||||
readonly glyphMargin: boolean;
|
||||
readonly lineDecorationsWidth: string | number;
|
||||
readonly folding: boolean;
|
||||
readonly minimap: Readonly<Required<IEditorMinimapOptions>>;
|
||||
readonly scrollbar: InternalEditorScrollbarOptions;
|
||||
readonly lineNumbers: InternalEditorRenderLineNumbersOptions;
|
||||
readonly lineNumbersMinChars: number;
|
||||
readonly scrollBeyondLastLine: boolean;
|
||||
readonly wordWrap: 'wordWrapColumn' | 'on' | 'off' | 'bounded';
|
||||
readonly wordWrapColumn: number;
|
||||
readonly wordWrapMinified: boolean;
|
||||
readonly accessibilitySupport: AccessibilitySupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface IMinimapLayoutInput {
|
||||
readonly outerWidth: number;
|
||||
readonly outerHeight: number;
|
||||
readonly lineHeight: number;
|
||||
readonly typicalHalfwidthCharacterWidth: number;
|
||||
readonly pixelRatio: number;
|
||||
readonly scrollBeyondLastLine: boolean;
|
||||
readonly minimap: Readonly<Required<IEditorMinimapOptions>>;
|
||||
readonly verticalScrollbarWidth: number;
|
||||
readonly viewLineCount: number;
|
||||
readonly remainingWidth: number;
|
||||
readonly isViewportWrapping: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1780,14 +1846,22 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
|
||||
constructor() {
|
||||
super(
|
||||
EditorOption.layoutInfo,
|
||||
[EditorOption.glyphMargin, EditorOption.lineDecorationsWidth, EditorOption.folding, EditorOption.minimap, EditorOption.scrollbar, EditorOption.lineNumbers]
|
||||
[
|
||||
EditorOption.glyphMargin, EditorOption.lineDecorationsWidth, EditorOption.folding,
|
||||
EditorOption.minimap, EditorOption.scrollbar, EditorOption.lineNumbers,
|
||||
EditorOption.lineNumbersMinChars, EditorOption.scrollBeyondLastLine,
|
||||
EditorOption.wordWrap, EditorOption.wordWrapColumn, EditorOption.wordWrapMinified,
|
||||
EditorOption.accessibilitySupport
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: EditorLayoutInfo): EditorLayoutInfo {
|
||||
return EditorLayoutInfoComputer.computeLayout(options, {
|
||||
memory: env.memory,
|
||||
outerWidth: env.outerWidth,
|
||||
outerHeight: env.outerHeight,
|
||||
isDominatedByLongLines: env.isDominatedByLongLines,
|
||||
lineHeight: env.fontInfo.lineHeight,
|
||||
viewLineCount: env.viewLineCount,
|
||||
lineNumbersDigitCount: env.lineNumbersDigitCount,
|
||||
@@ -1811,6 +1885,172 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
|
||||
return { typicalViewportLineCount, extraLinesBeyondLastLine, desiredRatio, minimapLineCount };
|
||||
}
|
||||
|
||||
private static _computeMinimapLayout(input: IMinimapLayoutInput, memory: ComputeOptionsMemory): EditorMinimapLayoutInfo {
|
||||
const outerWidth = input.outerWidth;
|
||||
const outerHeight = input.outerHeight;
|
||||
const pixelRatio = input.pixelRatio;
|
||||
|
||||
if (!input.minimap.enabled) {
|
||||
return {
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: Math.floor(pixelRatio * outerHeight),
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: outerHeight,
|
||||
};
|
||||
}
|
||||
|
||||
// Can use memory if only the `viewLineCount` and `remainingWidth` have changed
|
||||
const stableMinimapLayoutInput = memory.stableMinimapLayoutInput;
|
||||
const couldUseMemory = (
|
||||
stableMinimapLayoutInput
|
||||
// && input.outerWidth === lastMinimapLayoutInput.outerWidth !!! INTENTIONAL OMITTED
|
||||
&& input.outerHeight === stableMinimapLayoutInput.outerHeight
|
||||
&& input.lineHeight === stableMinimapLayoutInput.lineHeight
|
||||
&& input.typicalHalfwidthCharacterWidth === stableMinimapLayoutInput.typicalHalfwidthCharacterWidth
|
||||
&& input.pixelRatio === stableMinimapLayoutInput.pixelRatio
|
||||
&& input.scrollBeyondLastLine === stableMinimapLayoutInput.scrollBeyondLastLine
|
||||
&& input.minimap.enabled === stableMinimapLayoutInput.minimap.enabled
|
||||
&& input.minimap.side === stableMinimapLayoutInput.minimap.side
|
||||
&& input.minimap.size === stableMinimapLayoutInput.minimap.size
|
||||
&& input.minimap.showSlider === stableMinimapLayoutInput.minimap.showSlider
|
||||
&& input.minimap.renderCharacters === stableMinimapLayoutInput.minimap.renderCharacters
|
||||
&& input.minimap.maxColumn === stableMinimapLayoutInput.minimap.maxColumn
|
||||
&& input.minimap.scale === stableMinimapLayoutInput.minimap.scale
|
||||
&& input.verticalScrollbarWidth === stableMinimapLayoutInput.verticalScrollbarWidth
|
||||
// && input.viewLineCount === lastMinimapLayoutInput.viewLineCount !!! INTENTIONAL OMITTED
|
||||
// && input.remainingWidth === lastMinimapLayoutInput.remainingWidth !!! INTENTIONAL OMITTED
|
||||
&& input.isViewportWrapping === stableMinimapLayoutInput.isViewportWrapping
|
||||
);
|
||||
|
||||
const lineHeight = input.lineHeight;
|
||||
const typicalHalfwidthCharacterWidth = input.typicalHalfwidthCharacterWidth;
|
||||
const scrollBeyondLastLine = input.scrollBeyondLastLine;
|
||||
const minimapRenderCharacters = input.minimap.renderCharacters;
|
||||
let minimapScale = (pixelRatio >= 2 ? Math.round(input.minimap.scale * 2) : input.minimap.scale);
|
||||
const minimapMaxColumn = input.minimap.maxColumn;
|
||||
const minimapSize = input.minimap.size;
|
||||
const minimapSide = input.minimap.side;
|
||||
const verticalScrollbarWidth = input.verticalScrollbarWidth;
|
||||
const viewLineCount = input.viewLineCount;
|
||||
const remainingWidth = input.remainingWidth;
|
||||
const isViewportWrapping = input.isViewportWrapping;
|
||||
|
||||
const baseCharHeight = minimapRenderCharacters ? 2 : 3;
|
||||
let minimapCanvasInnerHeight = Math.floor(pixelRatio * outerHeight);
|
||||
const minimapCanvasOuterHeight = minimapCanvasInnerHeight / pixelRatio;
|
||||
let minimapHeightIsEditorHeight = false;
|
||||
let minimapIsSampling = false;
|
||||
let minimapLineHeight = baseCharHeight * minimapScale;
|
||||
let minimapCharWidth = minimapScale / pixelRatio;
|
||||
let minimapWidthMultiplier: number = 1;
|
||||
|
||||
if (minimapSize === 'fill' || minimapSize === 'fit') {
|
||||
const { typicalViewportLineCount, extraLinesBeyondLastLine, desiredRatio, minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({
|
||||
viewLineCount: viewLineCount,
|
||||
scrollBeyondLastLine: scrollBeyondLastLine,
|
||||
height: outerHeight,
|
||||
lineHeight: lineHeight,
|
||||
pixelRatio: pixelRatio
|
||||
});
|
||||
// ratio is intentionally not part of the layout to avoid the layout changing all the time
|
||||
// when doing sampling
|
||||
const ratio = viewLineCount / minimapLineCount;
|
||||
|
||||
if (ratio > 1) {
|
||||
minimapHeightIsEditorHeight = true;
|
||||
minimapIsSampling = true;
|
||||
minimapScale = 1;
|
||||
minimapLineHeight = 1;
|
||||
minimapCharWidth = minimapScale / pixelRatio;
|
||||
} else {
|
||||
let fitBecomesFill = false;
|
||||
let maxMinimapScale = minimapScale + 1;
|
||||
|
||||
if (minimapSize === 'fit') {
|
||||
const effectiveMinimapHeight = Math.ceil((viewLineCount + extraLinesBeyondLastLine) * minimapLineHeight);
|
||||
if (isViewportWrapping && couldUseMemory && remainingWidth <= memory.stableFitRemainingWidth) {
|
||||
// There is a loop when using `fit` and viewport wrapping:
|
||||
// - view line count impacts minimap layout
|
||||
// - minimap layout impacts viewport width
|
||||
// - viewport width impacts view line count
|
||||
// To break the loop, once we go to a smaller minimap scale, we try to stick with it.
|
||||
fitBecomesFill = true;
|
||||
maxMinimapScale = memory.stableFitMaxMinimapScale;
|
||||
} else {
|
||||
fitBecomesFill = (effectiveMinimapHeight > minimapCanvasInnerHeight);
|
||||
if (isViewportWrapping && fitBecomesFill) {
|
||||
// remember for next time
|
||||
memory.stableMinimapLayoutInput = input;
|
||||
memory.stableFitRemainingWidth = remainingWidth;
|
||||
} else {
|
||||
memory.stableMinimapLayoutInput = null;
|
||||
memory.stableFitRemainingWidth = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (minimapSize === 'fill' || fitBecomesFill) {
|
||||
minimapHeightIsEditorHeight = true;
|
||||
const configuredMinimapScale = minimapScale;
|
||||
minimapLineHeight = Math.min(lineHeight * pixelRatio, Math.max(1, Math.floor(1 / desiredRatio)));
|
||||
minimapScale = Math.min(maxMinimapScale, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight)));
|
||||
if (minimapScale > configuredMinimapScale) {
|
||||
minimapWidthMultiplier = Math.min(2, minimapScale / configuredMinimapScale);
|
||||
}
|
||||
minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier;
|
||||
minimapCanvasInnerHeight = Math.ceil((Math.max(typicalViewportLineCount, viewLineCount + extraLinesBeyondLastLine)) * minimapLineHeight);
|
||||
if (isViewportWrapping && fitBecomesFill) {
|
||||
memory.stableFitMaxMinimapScale = minimapScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Given:
|
||||
// (leaving 2px for the cursor to have space after the last character)
|
||||
// viewportColumn = (contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth
|
||||
// minimapWidth = viewportColumn * minimapCharWidth
|
||||
// contentWidth = remainingWidth - minimapWidth
|
||||
// What are good values for contentWidth and minimapWidth ?
|
||||
|
||||
// minimapWidth = ((contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth) * minimapCharWidth
|
||||
// typicalHalfwidthCharacterWidth * minimapWidth = (contentWidth - verticalScrollbarWidth - 2) * minimapCharWidth
|
||||
// typicalHalfwidthCharacterWidth * minimapWidth = (remainingWidth - minimapWidth - verticalScrollbarWidth - 2) * minimapCharWidth
|
||||
// (typicalHalfwidthCharacterWidth + minimapCharWidth) * minimapWidth = (remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth
|
||||
// minimapWidth = ((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)
|
||||
|
||||
const minimapMaxWidth = Math.floor(minimapMaxColumn * minimapCharWidth);
|
||||
const minimapWidth = Math.min(minimapMaxWidth, Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth))) + MINIMAP_GUTTER_WIDTH);
|
||||
|
||||
let minimapCanvasInnerWidth = Math.floor(pixelRatio * minimapWidth);
|
||||
const minimapCanvasOuterWidth = minimapCanvasInnerWidth / pixelRatio;
|
||||
minimapCanvasInnerWidth = Math.floor(minimapCanvasInnerWidth * minimapWidthMultiplier);
|
||||
|
||||
const renderMinimap = (minimapRenderCharacters ? RenderMinimap.Text : RenderMinimap.Blocks);
|
||||
const minimapLeft = (minimapSide === 'left' ? 0 : (outerWidth - minimapWidth - verticalScrollbarWidth));
|
||||
|
||||
return {
|
||||
renderMinimap,
|
||||
minimapLeft,
|
||||
minimapWidth,
|
||||
minimapHeightIsEditorHeight,
|
||||
minimapIsSampling,
|
||||
minimapScale,
|
||||
minimapLineHeight,
|
||||
minimapCanvasInnerWidth,
|
||||
minimapCanvasInnerHeight,
|
||||
minimapCanvasOuterWidth,
|
||||
minimapCanvasOuterHeight,
|
||||
};
|
||||
}
|
||||
|
||||
public static computeLayout(options: IComputedEditorOptions, env: EditorLayoutInfoComputerEnv): EditorLayoutInfo {
|
||||
const outerWidth = env.outerWidth | 0;
|
||||
const outerHeight = env.outerHeight | 0;
|
||||
@@ -1819,24 +2059,25 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
|
||||
const typicalHalfwidthCharacterWidth = env.typicalHalfwidthCharacterWidth;
|
||||
const maxDigitWidth = env.maxDigitWidth;
|
||||
const pixelRatio = env.pixelRatio;
|
||||
const viewLineCount = env.viewLineCount;
|
||||
|
||||
const wordWrap = options.get(EditorOption.wordWrap);
|
||||
const wordWrapColumn = options.get(EditorOption.wordWrapColumn);
|
||||
const wordWrapMinified = options.get(EditorOption.wordWrapMinified);
|
||||
const accessibilitySupport = options.get(EditorOption.accessibilitySupport);
|
||||
const isDominatedByLongLines = env.isDominatedByLongLines;
|
||||
|
||||
const showGlyphMargin = options.get(EditorOption.glyphMargin);
|
||||
const showLineNumbers = (options.get(EditorOption.lineNumbers).renderType !== RenderLineNumbersType.Off);
|
||||
const lineNumbersMinChars = options.get(EditorOption.lineNumbersMinChars) | 0;
|
||||
const lineNumbersMinChars = options.get(EditorOption.lineNumbersMinChars);
|
||||
const scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine);
|
||||
const minimap = options.get(EditorOption.minimap);
|
||||
const minimapEnabled = minimap.enabled;
|
||||
const minimapSide = minimap.side;
|
||||
const minimapRenderCharacters = minimap.renderCharacters;
|
||||
let minimapScale = (pixelRatio >= 2 ? Math.round(minimap.scale * 2) : minimap.scale);
|
||||
const minimapMaxColumn = minimap.maxColumn | 0;
|
||||
const minimapSize = minimap.size;
|
||||
|
||||
const scrollbar = options.get(EditorOption.scrollbar);
|
||||
const verticalScrollbarWidth = scrollbar.verticalScrollbarSize | 0;
|
||||
const verticalScrollbarWidth = scrollbar.verticalScrollbarSize;
|
||||
const verticalScrollbarHasArrows = scrollbar.verticalHasArrows;
|
||||
const scrollbarArrowSize = scrollbar.arrowSize | 0;
|
||||
const horizontalScrollbarHeight = scrollbar.horizontalScrollbarSize | 0;
|
||||
const scrollbarArrowSize = scrollbar.arrowSize;
|
||||
const horizontalScrollbarHeight = scrollbar.horizontalScrollbarSize;
|
||||
|
||||
const rawLineDecorationsWidth = options.get(EditorOption.lineDecorationsWidth);
|
||||
const folding = options.get(EditorOption.folding);
|
||||
@@ -1870,107 +2111,62 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
|
||||
|
||||
const remainingWidth = outerWidth - glyphMarginWidth - lineNumbersWidth - lineDecorationsWidth;
|
||||
|
||||
const baseCharHeight = minimapRenderCharacters ? 2 : 3;
|
||||
let renderMinimap: RenderMinimap;
|
||||
let minimapLeft: number;
|
||||
let minimapWidth: number;
|
||||
let minimapCanvasInnerWidth: number;
|
||||
let minimapCanvasInnerHeight = Math.floor(pixelRatio * outerHeight);
|
||||
let minimapCanvasOuterWidth: number;
|
||||
const minimapCanvasOuterHeight = minimapCanvasInnerHeight / pixelRatio;
|
||||
let minimapHeightIsEditorHeight = false;
|
||||
let minimapIsSampling = false;
|
||||
let minimapLineHeight = baseCharHeight * minimapScale;
|
||||
let contentWidth: number;
|
||||
if (!minimapEnabled) {
|
||||
minimapLeft = 0;
|
||||
minimapWidth = 0;
|
||||
minimapCanvasInnerWidth = 0;
|
||||
minimapCanvasOuterWidth = 0;
|
||||
minimapLineHeight = 1;
|
||||
renderMinimap = RenderMinimap.None;
|
||||
contentWidth = remainingWidth;
|
||||
} else {
|
||||
let minimapCharWidth = minimapScale / pixelRatio;
|
||||
let minimapWidthMultiplier: number = 1;
|
||||
let isWordWrapMinified = false;
|
||||
let isViewportWrapping = false;
|
||||
let wrappingColumn = -1;
|
||||
|
||||
if (minimapSize === 'fill' || minimapSize === 'fit') {
|
||||
const viewLineCount = env.viewLineCount;
|
||||
const { typicalViewportLineCount, extraLinesBeyondLastLine, desiredRatio, minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({
|
||||
viewLineCount: viewLineCount,
|
||||
scrollBeyondLastLine: scrollBeyondLastLine,
|
||||
height: outerHeight,
|
||||
lineHeight: lineHeight,
|
||||
pixelRatio: pixelRatio
|
||||
});
|
||||
// ratio is intentionally not part of the layout to avoid the layout changing all the time
|
||||
// when doing sampling
|
||||
const ratio = viewLineCount / minimapLineCount;
|
||||
|
||||
if (ratio > 1) {
|
||||
minimapHeightIsEditorHeight = true;
|
||||
minimapIsSampling = true;
|
||||
minimapScale = 1;
|
||||
minimapLineHeight = 1;
|
||||
minimapCharWidth = minimapScale / pixelRatio;
|
||||
} else {
|
||||
const effectiveMinimapHeight = Math.ceil((viewLineCount + extraLinesBeyondLastLine) * minimapLineHeight);
|
||||
if (minimapSize === 'fill' || effectiveMinimapHeight > minimapCanvasInnerHeight) {
|
||||
minimapHeightIsEditorHeight = true;
|
||||
const configuredFontScale = minimapScale;
|
||||
minimapLineHeight = Math.min(lineHeight * pixelRatio, Math.max(1, Math.floor(1 / desiredRatio)));
|
||||
minimapScale = Math.min(configuredFontScale + 1, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight)));
|
||||
if (minimapScale > configuredFontScale) {
|
||||
minimapWidthMultiplier = Math.min(2, minimapScale / configuredFontScale);
|
||||
}
|
||||
minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier;
|
||||
minimapCanvasInnerHeight = Math.ceil((Math.max(typicalViewportLineCount, viewLineCount + extraLinesBeyondLastLine)) * minimapLineHeight);
|
||||
}
|
||||
}
|
||||
if (accessibilitySupport !== AccessibilitySupport.Enabled) {
|
||||
// See https://github.com/Microsoft/vscode/issues/27766
|
||||
// Never enable wrapping when a screen reader is attached
|
||||
// because arrow down etc. will not move the cursor in the way
|
||||
// a screen reader expects.
|
||||
if (wordWrapMinified && isDominatedByLongLines) {
|
||||
// Force viewport width wrapping if model is dominated by long lines
|
||||
isWordWrapMinified = true;
|
||||
isViewportWrapping = true;
|
||||
} else if (wordWrap === 'on' || wordWrap === 'bounded') {
|
||||
isViewportWrapping = true;
|
||||
} else if (wordWrap === 'wordWrapColumn') {
|
||||
wrappingColumn = wordWrapColumn;
|
||||
}
|
||||
|
||||
renderMinimap = minimapRenderCharacters ? RenderMinimap.Text : RenderMinimap.Blocks;
|
||||
|
||||
// Given:
|
||||
// (leaving 2px for the cursor to have space after the last character)
|
||||
// viewportColumn = (contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth
|
||||
// minimapWidth = viewportColumn * minimapCharWidth
|
||||
// contentWidth = remainingWidth - minimapWidth
|
||||
// What are good values for contentWidth and minimapWidth ?
|
||||
|
||||
// minimapWidth = ((contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth) * minimapCharWidth
|
||||
// typicalHalfwidthCharacterWidth * minimapWidth = (contentWidth - verticalScrollbarWidth - 2) * minimapCharWidth
|
||||
// typicalHalfwidthCharacterWidth * minimapWidth = (remainingWidth - minimapWidth - verticalScrollbarWidth - 2) * minimapCharWidth
|
||||
// (typicalHalfwidthCharacterWidth + minimapCharWidth) * minimapWidth = (remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth
|
||||
// minimapWidth = ((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)
|
||||
|
||||
minimapWidth = Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth))) + MINIMAP_GUTTER_WIDTH;
|
||||
let minimapColumns = minimapWidth / minimapCharWidth;
|
||||
if (minimapColumns > minimapMaxColumn) {
|
||||
minimapWidth = Math.floor(minimapMaxColumn * minimapCharWidth);
|
||||
}
|
||||
contentWidth = remainingWidth - minimapWidth;
|
||||
|
||||
if (minimapSide === 'left') {
|
||||
minimapLeft = 0;
|
||||
glyphMarginLeft += minimapWidth;
|
||||
lineNumbersLeft += minimapWidth;
|
||||
decorationsLeft += minimapWidth;
|
||||
contentLeft += minimapWidth;
|
||||
} else {
|
||||
minimapLeft = outerWidth - minimapWidth - verticalScrollbarWidth;
|
||||
}
|
||||
|
||||
minimapCanvasInnerWidth = Math.floor(pixelRatio * minimapWidth);
|
||||
minimapCanvasOuterWidth = minimapCanvasInnerWidth / pixelRatio;
|
||||
minimapCanvasInnerWidth = Math.floor(minimapCanvasInnerWidth * minimapWidthMultiplier);
|
||||
}
|
||||
|
||||
const minimapLayout = EditorLayoutInfoComputer._computeMinimapLayout({
|
||||
outerWidth: outerWidth,
|
||||
outerHeight: outerHeight,
|
||||
lineHeight: lineHeight,
|
||||
typicalHalfwidthCharacterWidth: typicalHalfwidthCharacterWidth,
|
||||
pixelRatio: pixelRatio,
|
||||
scrollBeyondLastLine: scrollBeyondLastLine,
|
||||
minimap: minimap,
|
||||
verticalScrollbarWidth: verticalScrollbarWidth,
|
||||
viewLineCount: viewLineCount,
|
||||
remainingWidth: remainingWidth,
|
||||
isViewportWrapping: isViewportWrapping,
|
||||
}, env.memory || new ComputeOptionsMemory());
|
||||
|
||||
if (minimapLayout.renderMinimap !== RenderMinimap.None && minimapLayout.minimapLeft === 0) {
|
||||
// the minimap is rendered to the left, so move everything to the right
|
||||
glyphMarginLeft += minimapLayout.minimapWidth;
|
||||
lineNumbersLeft += minimapLayout.minimapWidth;
|
||||
decorationsLeft += minimapLayout.minimapWidth;
|
||||
contentLeft += minimapLayout.minimapWidth;
|
||||
}
|
||||
const contentWidth = remainingWidth - minimapLayout.minimapWidth;
|
||||
|
||||
// (leaving 2px for the cursor to have space after the last character)
|
||||
const viewportColumn = Math.max(1, Math.floor((contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth));
|
||||
|
||||
const verticalArrowSize = (verticalScrollbarHasArrows ? scrollbarArrowSize : 0);
|
||||
|
||||
if (isViewportWrapping) {
|
||||
// compute the actual wrappingColumn
|
||||
wrappingColumn = Math.max(1, viewportColumn);
|
||||
if (wordWrap === 'bounded') {
|
||||
wrappingColumn = Math.min(wrappingColumn, wordWrapColumn);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
width: outerWidth,
|
||||
height: outerHeight,
|
||||
@@ -1987,20 +2183,14 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
|
||||
contentLeft: contentLeft,
|
||||
contentWidth: contentWidth,
|
||||
|
||||
renderMinimap: renderMinimap,
|
||||
minimapLeft: minimapLeft,
|
||||
minimapWidth: minimapWidth,
|
||||
minimapHeightIsEditorHeight: minimapHeightIsEditorHeight,
|
||||
minimapIsSampling: minimapIsSampling,
|
||||
minimapScale: minimapScale,
|
||||
minimapLineHeight: minimapLineHeight,
|
||||
minimapCanvasInnerWidth: minimapCanvasInnerWidth,
|
||||
minimapCanvasInnerHeight: minimapCanvasInnerHeight,
|
||||
minimapCanvasOuterWidth: minimapCanvasOuterWidth,
|
||||
minimapCanvasOuterHeight: minimapCanvasOuterHeight,
|
||||
minimap: minimapLayout,
|
||||
|
||||
viewportColumn: viewportColumn,
|
||||
|
||||
isWordWrapMinified: isWordWrapMinified,
|
||||
isViewportWrapping: isViewportWrapping,
|
||||
wrappingColumn: wrappingColumn,
|
||||
|
||||
verticalScrollbarWidth: verticalScrollbarWidth,
|
||||
horizontalScrollbarHeight: horizontalScrollbarHeight,
|
||||
|
||||
@@ -3231,67 +3421,17 @@ export interface EditorWrappingInfo {
|
||||
class EditorWrappingInfoComputer extends ComputedEditorOption<EditorOption.wrappingInfo, EditorWrappingInfo> {
|
||||
|
||||
constructor() {
|
||||
super(EditorOption.wrappingInfo, [EditorOption.wordWrap, EditorOption.wordWrapColumn, EditorOption.wordWrapMinified, EditorOption.layoutInfo, EditorOption.accessibilitySupport]);
|
||||
super(EditorOption.wrappingInfo, [EditorOption.layoutInfo]);
|
||||
}
|
||||
|
||||
public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: EditorWrappingInfo): EditorWrappingInfo {
|
||||
const wordWrap = options.get(EditorOption.wordWrap);
|
||||
const wordWrapColumn = options.get(EditorOption.wordWrapColumn);
|
||||
const wordWrapMinified = options.get(EditorOption.wordWrapMinified);
|
||||
const layoutInfo = options.get(EditorOption.layoutInfo);
|
||||
const accessibilitySupport = options.get(EditorOption.accessibilitySupport);
|
||||
|
||||
let bareWrappingInfo: { isWordWrapMinified: boolean; isViewportWrapping: boolean; wrappingColumn: number; } | null = null;
|
||||
{
|
||||
if (accessibilitySupport === AccessibilitySupport.Enabled) {
|
||||
// See https://github.com/Microsoft/vscode/issues/27766
|
||||
// Never enable wrapping when a screen reader is attached
|
||||
// because arrow down etc. will not move the cursor in the way
|
||||
// a screen reader expects.
|
||||
bareWrappingInfo = {
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1
|
||||
};
|
||||
} else if (wordWrapMinified && env.isDominatedByLongLines) {
|
||||
// Force viewport width wrapping if model is dominated by long lines
|
||||
bareWrappingInfo = {
|
||||
isWordWrapMinified: true,
|
||||
isViewportWrapping: true,
|
||||
wrappingColumn: Math.max(1, layoutInfo.viewportColumn)
|
||||
};
|
||||
} else if (wordWrap === 'on') {
|
||||
bareWrappingInfo = {
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: true,
|
||||
wrappingColumn: Math.max(1, layoutInfo.viewportColumn)
|
||||
};
|
||||
} else if (wordWrap === 'bounded') {
|
||||
bareWrappingInfo = {
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: true,
|
||||
wrappingColumn: Math.min(Math.max(1, layoutInfo.viewportColumn), wordWrapColumn)
|
||||
};
|
||||
} else if (wordWrap === 'wordWrapColumn') {
|
||||
bareWrappingInfo = {
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: wordWrapColumn
|
||||
};
|
||||
} else {
|
||||
bareWrappingInfo = {
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isDominatedByLongLines: env.isDominatedByLongLines,
|
||||
isWordWrapMinified: bareWrappingInfo.isWordWrapMinified,
|
||||
isViewportWrapping: bareWrappingInfo.isViewportWrapping,
|
||||
wrappingColumn: bareWrappingInfo.wrappingColumn,
|
||||
isWordWrapMinified: layoutInfo.isWordWrapMinified,
|
||||
isViewportWrapping: layoutInfo.isViewportWrapping,
|
||||
wrappingColumn: layoutInfo.wrappingColumn,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,7 +701,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
}
|
||||
|
||||
const editorWidth = layoutInfo.width;
|
||||
const minimapWidth = layoutInfo.minimapWidth;
|
||||
const minimapWidth = layoutInfo.minimap.minimapWidth;
|
||||
let collapsedFindWidget = false;
|
||||
let reducedFindWidget = false;
|
||||
let narrowFindWidget = false;
|
||||
@@ -1218,7 +1218,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
// 1. never resized before, double click should maximizes it
|
||||
// 2. users resized it already but its width is the same as default
|
||||
const layoutInfo = this._codeEditor.getLayoutInfo();
|
||||
width = layoutInfo.width - 28 - layoutInfo.minimapWidth - 15;
|
||||
width = layoutInfo.width - 28 - layoutInfo.minimap.minimapWidth - 15;
|
||||
this._resized = true;
|
||||
} else {
|
||||
/**
|
||||
|
||||
@@ -254,13 +254,13 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider {
|
||||
}
|
||||
|
||||
private _getWidth(info: EditorLayoutInfo): number {
|
||||
return info.width - info.minimapWidth - info.verticalScrollbarWidth;
|
||||
return info.width - info.minimap.minimapWidth - info.verticalScrollbarWidth;
|
||||
}
|
||||
|
||||
private _getLeft(info: EditorLayoutInfo): number {
|
||||
// If minimap is to the left, we move beyond it
|
||||
if (info.minimapWidth > 0 && info.minimapLeft === 0) {
|
||||
return info.minimapWidth;
|
||||
if (info.minimap.minimapWidth > 0 && info.minimap.minimapLeft === 0) {
|
||||
return info.minimap.minimapWidth;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -526,6 +526,6 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider {
|
||||
|
||||
getHorizontalSashWidth() {
|
||||
const layoutInfo = this.editor.getLayoutInfo();
|
||||
return layoutInfo.width - layoutInfo.minimapWidth;
|
||||
return layoutInfo.width - layoutInfo.minimap.minimapWidth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,9 +76,16 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
};
|
||||
options._write(EditorOption.lineNumbers, lineNumbersOptions);
|
||||
|
||||
options._write(EditorOption.wordWrap, 'off');
|
||||
options._write(EditorOption.wordWrapColumn, 80);
|
||||
options._write(EditorOption.wordWrapMinified, true);
|
||||
options._write(EditorOption.accessibilitySupport, 'auto');
|
||||
|
||||
const actual = EditorLayoutInfoComputer.computeLayout(options, {
|
||||
memory: null,
|
||||
outerWidth: input.outerWidth,
|
||||
outerHeight: input.outerHeight,
|
||||
isDominatedByLongLines: false,
|
||||
lineHeight: input.lineHeight,
|
||||
viewLineCount: input.maxLineNumber || Math.pow(10, input.lineNumbersDigitCount) - 1,
|
||||
lineNumbersDigitCount: input.lineNumbersDigitCount,
|
||||
@@ -126,18 +133,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 10,
|
||||
contentWidth: 990,
|
||||
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 800,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 800,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
},
|
||||
|
||||
viewportColumn: 98,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -188,18 +201,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 10,
|
||||
contentWidth: 990,
|
||||
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 800,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 800,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
},
|
||||
|
||||
viewportColumn: 97,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 11,
|
||||
horizontalScrollbarHeight: 12,
|
||||
@@ -250,18 +269,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 10,
|
||||
contentWidth: 890,
|
||||
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 800,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 800,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
},
|
||||
|
||||
viewportColumn: 88,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -312,18 +337,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 10,
|
||||
contentWidth: 890,
|
||||
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 900,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 900,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 900,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 900,
|
||||
},
|
||||
|
||||
viewportColumn: 88,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -374,18 +405,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 10,
|
||||
contentWidth: 890,
|
||||
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 900,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 900,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 900,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 900,
|
||||
},
|
||||
|
||||
viewportColumn: 88,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -436,18 +473,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 60,
|
||||
contentWidth: 840,
|
||||
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 900,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 900,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 900,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 900,
|
||||
},
|
||||
|
||||
viewportColumn: 83,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -498,18 +541,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 60,
|
||||
contentWidth: 840,
|
||||
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 900,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 900,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 900,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 900,
|
||||
},
|
||||
|
||||
viewportColumn: 83,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -560,18 +609,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 70,
|
||||
contentWidth: 830,
|
||||
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 900,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 900,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 900,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 900,
|
||||
},
|
||||
|
||||
viewportColumn: 82,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -622,18 +677,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 40,
|
||||
contentWidth: 860,
|
||||
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 900,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 900,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 900,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 900,
|
||||
},
|
||||
|
||||
viewportColumn: 171,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -684,18 +745,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 40,
|
||||
contentWidth: 860,
|
||||
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 900,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 900,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.None,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 0,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 0,
|
||||
minimapCanvasInnerHeight: 900,
|
||||
minimapCanvasOuterWidth: 0,
|
||||
minimapCanvasOuterHeight: 900,
|
||||
},
|
||||
|
||||
viewportColumn: 169,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -746,18 +813,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 10,
|
||||
contentWidth: 893,
|
||||
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 903,
|
||||
minimapWidth: 97,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 2,
|
||||
minimapCanvasInnerWidth: 97,
|
||||
minimapCanvasInnerHeight: 800,
|
||||
minimapCanvasOuterWidth: 97,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 903,
|
||||
minimapWidth: 97,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 2,
|
||||
minimapCanvasInnerWidth: 97,
|
||||
minimapCanvasInnerHeight: 800,
|
||||
minimapCanvasOuterWidth: 97,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
},
|
||||
|
||||
viewportColumn: 89,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -808,18 +881,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 10,
|
||||
contentWidth: 893,
|
||||
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 903,
|
||||
minimapWidth: 97,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 2,
|
||||
minimapLineHeight: 4,
|
||||
minimapCanvasInnerWidth: 194,
|
||||
minimapCanvasInnerHeight: 1600,
|
||||
minimapCanvasOuterWidth: 97,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 903,
|
||||
minimapWidth: 97,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 2,
|
||||
minimapLineHeight: 4,
|
||||
minimapCanvasInnerWidth: 194,
|
||||
minimapCanvasInnerHeight: 1600,
|
||||
minimapCanvasOuterWidth: 97,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
},
|
||||
|
||||
viewportColumn: 89,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -870,18 +949,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 10,
|
||||
contentWidth: 935,
|
||||
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 945,
|
||||
minimapWidth: 55,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 2,
|
||||
minimapLineHeight: 4,
|
||||
minimapCanvasInnerWidth: 220,
|
||||
minimapCanvasInnerHeight: 3200,
|
||||
minimapCanvasOuterWidth: 55,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 945,
|
||||
minimapWidth: 55,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 2,
|
||||
minimapLineHeight: 4,
|
||||
minimapCanvasInnerWidth: 220,
|
||||
minimapCanvasInnerHeight: 3200,
|
||||
minimapCanvasOuterWidth: 55,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
},
|
||||
|
||||
viewportColumn: 93,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -932,18 +1017,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 65,
|
||||
contentWidth: 935,
|
||||
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 55,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 2,
|
||||
minimapLineHeight: 4,
|
||||
minimapCanvasInnerWidth: 220,
|
||||
minimapCanvasInnerHeight: 3200,
|
||||
minimapCanvasOuterWidth: 55,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 0,
|
||||
minimapWidth: 55,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 2,
|
||||
minimapLineHeight: 4,
|
||||
minimapCanvasInnerWidth: 220,
|
||||
minimapCanvasInnerHeight: 3200,
|
||||
minimapCanvasOuterWidth: 55,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
},
|
||||
|
||||
viewportColumn: 93,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -996,18 +1087,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 10,
|
||||
contentWidth: 893,
|
||||
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 903,
|
||||
minimapWidth: 97,
|
||||
minimapHeightIsEditorHeight: true,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 3,
|
||||
minimapLineHeight: 13,
|
||||
minimapCanvasInnerWidth: 291,
|
||||
minimapCanvasInnerHeight: 1560,
|
||||
minimapCanvasOuterWidth: 97,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 903,
|
||||
minimapWidth: 97,
|
||||
minimapHeightIsEditorHeight: true,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 3,
|
||||
minimapLineHeight: 13,
|
||||
minimapCanvasInnerWidth: 291,
|
||||
minimapCanvasInnerHeight: 1560,
|
||||
minimapCanvasOuterWidth: 97,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
},
|
||||
|
||||
viewportColumn: 89,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -1060,18 +1157,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 10,
|
||||
contentWidth: 935,
|
||||
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 945,
|
||||
minimapWidth: 55,
|
||||
minimapHeightIsEditorHeight: true,
|
||||
minimapIsSampling: true,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 110,
|
||||
minimapCanvasInnerHeight: 1600,
|
||||
minimapCanvasOuterWidth: 55,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 945,
|
||||
minimapWidth: 55,
|
||||
minimapHeightIsEditorHeight: true,
|
||||
minimapIsSampling: true,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 110,
|
||||
minimapCanvasInnerHeight: 1600,
|
||||
minimapCanvasOuterWidth: 55,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
},
|
||||
|
||||
viewportColumn: 93,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -1124,18 +1227,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 10,
|
||||
contentWidth: 893,
|
||||
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 903,
|
||||
minimapWidth: 97,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 2,
|
||||
minimapLineHeight: 4,
|
||||
minimapCanvasInnerWidth: 194,
|
||||
minimapCanvasInnerHeight: 1600,
|
||||
minimapCanvasOuterWidth: 97,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 903,
|
||||
minimapWidth: 97,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 2,
|
||||
minimapLineHeight: 4,
|
||||
minimapCanvasInnerWidth: 194,
|
||||
minimapCanvasInnerHeight: 1600,
|
||||
minimapCanvasOuterWidth: 97,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
},
|
||||
|
||||
viewportColumn: 89,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -1188,18 +1297,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 10,
|
||||
contentWidth: 935,
|
||||
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 945,
|
||||
minimapWidth: 55,
|
||||
minimapHeightIsEditorHeight: true,
|
||||
minimapIsSampling: true,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 110,
|
||||
minimapCanvasInnerHeight: 1600,
|
||||
minimapCanvasOuterWidth: 55,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 945,
|
||||
minimapWidth: 55,
|
||||
minimapHeightIsEditorHeight: true,
|
||||
minimapIsSampling: true,
|
||||
minimapScale: 1,
|
||||
minimapLineHeight: 1,
|
||||
minimapCanvasInnerWidth: 110,
|
||||
minimapCanvasInnerHeight: 1600,
|
||||
minimapCanvasOuterWidth: 55,
|
||||
minimapCanvasOuterHeight: 800,
|
||||
},
|
||||
|
||||
viewportColumn: 93,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 0,
|
||||
horizontalScrollbarHeight: 0,
|
||||
@@ -1250,18 +1365,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
contentLeft: 92,
|
||||
contentWidth: 1018,
|
||||
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 1096,
|
||||
minimapWidth: 91,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 2,
|
||||
minimapLineHeight: 4,
|
||||
minimapCanvasInnerWidth: 182,
|
||||
minimapCanvasInnerHeight: 844,
|
||||
minimapCanvasOuterWidth: 91,
|
||||
minimapCanvasOuterHeight: 422,
|
||||
minimap: {
|
||||
renderMinimap: RenderMinimap.Text,
|
||||
minimapLeft: 1096,
|
||||
minimapWidth: 91,
|
||||
minimapHeightIsEditorHeight: false,
|
||||
minimapIsSampling: false,
|
||||
minimapScale: 2,
|
||||
minimapLineHeight: 4,
|
||||
minimapCanvasInnerWidth: 182,
|
||||
minimapCanvasInnerHeight: 844,
|
||||
minimapCanvasOuterWidth: 91,
|
||||
minimapCanvasOuterHeight: 422,
|
||||
},
|
||||
|
||||
viewportColumn: 83,
|
||||
isWordWrapMinified: false,
|
||||
isViewportWrapping: false,
|
||||
wrappingColumn: -1,
|
||||
|
||||
verticalScrollbarWidth: 14,
|
||||
horizontalScrollbarHeight: 10,
|
||||
|
||||
Vendored
+22
-18
@@ -3404,29 +3404,16 @@ declare namespace monaco.editor {
|
||||
*/
|
||||
readonly contentWidth: number;
|
||||
/**
|
||||
* The position for the minimap
|
||||
* Layout information for the minimap
|
||||
*/
|
||||
readonly minimapLeft: number;
|
||||
/**
|
||||
* The width of the minimap
|
||||
*/
|
||||
readonly minimapWidth: number;
|
||||
readonly minimapHeightIsEditorHeight: boolean;
|
||||
readonly minimapIsSampling: boolean;
|
||||
readonly minimapScale: number;
|
||||
readonly minimapLineHeight: number;
|
||||
readonly minimapCanvasInnerWidth: number;
|
||||
readonly minimapCanvasInnerHeight: number;
|
||||
readonly minimapCanvasOuterWidth: number;
|
||||
readonly minimapCanvasOuterHeight: number;
|
||||
/**
|
||||
* Minimap render type
|
||||
*/
|
||||
readonly renderMinimap: RenderMinimap;
|
||||
readonly minimap: EditorMinimapLayoutInfo;
|
||||
/**
|
||||
* The number of columns (of typical characters) fitting on a viewport line.
|
||||
*/
|
||||
readonly viewportColumn: number;
|
||||
readonly isWordWrapMinified: boolean;
|
||||
readonly isViewportWrapping: boolean;
|
||||
readonly wrappingColumn: number;
|
||||
/**
|
||||
* The width of the vertical scrollbar.
|
||||
*/
|
||||
@@ -3441,6 +3428,23 @@ declare namespace monaco.editor {
|
||||
readonly overviewRuler: OverviewRulerPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* The internal layout details of the editor.
|
||||
*/
|
||||
export interface EditorMinimapLayoutInfo {
|
||||
readonly renderMinimap: RenderMinimap;
|
||||
readonly minimapLeft: number;
|
||||
readonly minimapWidth: number;
|
||||
readonly minimapHeightIsEditorHeight: boolean;
|
||||
readonly minimapIsSampling: boolean;
|
||||
readonly minimapScale: number;
|
||||
readonly minimapLineHeight: number;
|
||||
readonly minimapCanvasInnerWidth: number;
|
||||
readonly minimapCanvasInnerHeight: number;
|
||||
readonly minimapCanvasOuterWidth: number;
|
||||
readonly minimapCanvasOuterHeight: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options for editor lightbulb
|
||||
*/
|
||||
|
||||
@@ -324,7 +324,8 @@ export class ConfigurationModelParser {
|
||||
result[key] = this.filterByScope(properties[key], configurationProperties, false, scopes);
|
||||
} else {
|
||||
const scope = this.getScope(key, configurationProperties);
|
||||
if (scopes.indexOf(scope) !== -1) {
|
||||
// Load unregistered configurations always.
|
||||
if (scope === undefined || scopes.indexOf(scope) !== -1) {
|
||||
result[key] = properties[key];
|
||||
}
|
||||
}
|
||||
@@ -332,9 +333,9 @@ export class ConfigurationModelParser {
|
||||
return result;
|
||||
}
|
||||
|
||||
private getScope(key: string, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }): ConfigurationScope {
|
||||
private getScope(key: string, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }): ConfigurationScope | undefined {
|
||||
const propertySchema = configurationProperties[key];
|
||||
return propertySchema && typeof propertySchema.scope !== 'undefined' ? propertySchema.scope : ConfigurationScope.WINDOW;
|
||||
return propertySchema ? typeof propertySchema.scope !== 'undefined' ? propertySchema.scope : ConfigurationScope.WINDOW : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import * as osLib from 'os';
|
||||
import { virtualMachineHint } from 'vs/base/node/id';
|
||||
import { IMachineInfo, WorkspaceStats, WorkspaceStatItem, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError, IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { readdir, stat, exists, readFile } from 'fs';
|
||||
import { readdir, exists, readFile } from 'fs';
|
||||
import { join, basename } from 'vs/base/common/path';
|
||||
import { parse, ParseError, getNodeType } from 'vs/base/common/json';
|
||||
import { listProcesses } from 'vs/base/node/ps';
|
||||
@@ -17,6 +17,7 @@ import { ProcessItem } from 'vs/base/common/processes';
|
||||
import { IMainProcessInfo } from 'vs/platform/launch/common/launch';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
|
||||
export const ID = 'diagnosticsService';
|
||||
export const IDiagnosticsService = createDecorator<IDiagnosticsService>(ID);
|
||||
@@ -42,24 +43,31 @@ export interface ProcessInfo {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function collectWorkspaceStats(folder: string, filter: string[]): Promise<WorkspaceStats> {
|
||||
const configFilePatterns = [
|
||||
{ 'tag': 'grunt.js', 'pattern': /^gruntfile\.js$/i },
|
||||
{ 'tag': 'gulp.js', 'pattern': /^gulpfile\.js$/i },
|
||||
{ 'tag': 'tsconfig.json', 'pattern': /^tsconfig\.json$/i },
|
||||
{ 'tag': 'package.json', 'pattern': /^package\.json$/i },
|
||||
{ 'tag': 'jsconfig.json', 'pattern': /^jsconfig\.json$/i },
|
||||
{ 'tag': 'tslint.json', 'pattern': /^tslint\.json$/i },
|
||||
{ 'tag': 'eslint.json', 'pattern': /^eslint\.json$/i },
|
||||
{ 'tag': 'tasks.json', 'pattern': /^tasks\.json$/i },
|
||||
{ 'tag': 'launch.json', 'pattern': /^launch\.json$/i },
|
||||
{ 'tag': 'settings.json', 'pattern': /^settings\.json$/i },
|
||||
{ 'tag': 'webpack.config.js', 'pattern': /^webpack\.config\.js$/i },
|
||||
{ 'tag': 'project.json', 'pattern': /^project\.json$/i },
|
||||
{ 'tag': 'makefile', 'pattern': /^makefile$/i },
|
||||
{ 'tag': 'sln', 'pattern': /^.+\.sln$/i },
|
||||
{ 'tag': 'csproj', 'pattern': /^.+\.csproj$/i },
|
||||
{ 'tag': 'cmake', 'pattern': /^.+\.cmake$/i }
|
||||
interface ConfigFilePatterns {
|
||||
tag: string;
|
||||
filePattern: RegExp;
|
||||
relativePathPattern?: RegExp;
|
||||
}
|
||||
|
||||
export async function collectWorkspaceStats(folder: string, filter: string[]): Promise<WorkspaceStats> {
|
||||
const configFilePatterns: ConfigFilePatterns[] = [
|
||||
{ tag: 'grunt.js', filePattern: /^gruntfile\.js$/i },
|
||||
{ tag: 'gulp.js', filePattern: /^gulpfile\.js$/i },
|
||||
{ tag: 'tsconfig.json', filePattern: /^tsconfig\.json$/i },
|
||||
{ tag: 'package.json', filePattern: /^package\.json$/i },
|
||||
{ tag: 'jsconfig.json', filePattern: /^jsconfig\.json$/i },
|
||||
{ tag: 'tslint.json', filePattern: /^tslint\.json$/i },
|
||||
{ tag: 'eslint.json', filePattern: /^eslint\.json$/i },
|
||||
{ tag: 'tasks.json', filePattern: /^tasks\.json$/i },
|
||||
{ tag: 'launch.json', filePattern: /^launch\.json$/i },
|
||||
{ tag: 'settings.json', filePattern: /^settings\.json$/i },
|
||||
{ tag: 'webpack.config.js', filePattern: /^webpack\.config\.js$/i },
|
||||
{ tag: 'project.json', filePattern: /^project\.json$/i },
|
||||
{ tag: 'makefile', filePattern: /^makefile$/i },
|
||||
{ tag: 'sln', filePattern: /^.+\.sln$/i },
|
||||
{ tag: 'csproj', filePattern: /^.+\.csproj$/i },
|
||||
{ tag: 'cmake', filePattern: /^.+\.cmake$/i },
|
||||
{ tag: 'github-actions', filePattern: /^.+\.yml$/i, relativePathPattern: /^\.github(?:\/|\\)workflows$/i }
|
||||
];
|
||||
|
||||
const fileTypes = new Map<string, number>();
|
||||
@@ -67,123 +75,91 @@ export function collectWorkspaceStats(folder: string, filter: string[]): Promise
|
||||
|
||||
const MAX_FILES = 20000;
|
||||
|
||||
function walk(dir: string, filter: string[], token: { count: number, maxReached: boolean }, done: (allFiles: string[]) => void): void {
|
||||
let results: string[] = [];
|
||||
readdir(dir, async (err, files) => {
|
||||
// Ignore folders that can't be read
|
||||
if (err) {
|
||||
return done(results);
|
||||
}
|
||||
function collect(root: string, dir: string, filter: string[], token: { count: number, maxReached: boolean }): Promise<void> {
|
||||
const relativePath = dir.substring(root.length + 1);
|
||||
|
||||
return new Promise(resolve => {
|
||||
readdir(dir, { withFileTypes: true }, async (err, files) => {
|
||||
// Ignore folders that can't be read
|
||||
if (err) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.count > MAX_FILES) {
|
||||
token.count += files.length;
|
||||
token.maxReached = true;
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
let pending = files.length;
|
||||
if (pending === 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
let filesToRead = files;
|
||||
if (token.count + files.length > MAX_FILES) {
|
||||
token.maxReached = true;
|
||||
pending = MAX_FILES - token.count;
|
||||
filesToRead = files.slice(0, pending);
|
||||
}
|
||||
|
||||
if (token.count > MAX_FILES) {
|
||||
token.count += files.length;
|
||||
token.maxReached = true;
|
||||
return done(results);
|
||||
}
|
||||
|
||||
let pending = files.length;
|
||||
if (pending === 0) {
|
||||
return done(results);
|
||||
}
|
||||
for (const file of filesToRead) {
|
||||
if (file.isDirectory()) {
|
||||
if (!filter.includes(file.name)) {
|
||||
await collect(root, join(dir, file.name), filter, token);
|
||||
}
|
||||
|
||||
let filesToRead = files;
|
||||
if (token.count + files.length > MAX_FILES) {
|
||||
token.maxReached = true;
|
||||
pending = MAX_FILES - token.count;
|
||||
filesToRead = files.slice(0, pending);
|
||||
}
|
||||
|
||||
token.count += files.length;
|
||||
|
||||
for (const file of filesToRead) {
|
||||
stat(join(dir, file), (err, stats) => {
|
||||
// Ignore files that can't be read
|
||||
if (err) {
|
||||
if (--pending === 0) {
|
||||
return done(results);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (stats.isDirectory()) {
|
||||
if (filter.indexOf(file) === -1) {
|
||||
walk(join(dir, file), filter, token, (res: string[]) => {
|
||||
results = results.concat(res);
|
||||
|
||||
if (--pending === 0) {
|
||||
return done(results);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (--pending === 0) {
|
||||
done(results);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
results.push(file);
|
||||
|
||||
if (--pending === 0) {
|
||||
done(results);
|
||||
const index = file.name.lastIndexOf('.');
|
||||
if (index >= 0) {
|
||||
const fileType = file.name.substring(index + 1);
|
||||
if (fileType) {
|
||||
fileTypes.set(fileType, (fileTypes.get(fileType) ?? 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (const configFile of configFilePatterns) {
|
||||
if (configFile.relativePathPattern?.test(relativePath) !== false && configFile.filePattern.test(file.name)) {
|
||||
configFiles.set(configFile.tag, (configFiles.get(configFile.tag) ?? 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (--pending === 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const addFileType = (fileType: string) => {
|
||||
if (fileTypes.has(fileType)) {
|
||||
fileTypes.set(fileType, fileTypes.get(fileType)! + 1);
|
||||
}
|
||||
else {
|
||||
fileTypes.set(fileType, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const addConfigFiles = (fileName: string) => {
|
||||
for (const each of configFilePatterns) {
|
||||
if (each.pattern.test(fileName)) {
|
||||
if (configFiles.has(each.tag)) {
|
||||
configFiles.set(each.tag, configFiles.get(each.tag)! + 1);
|
||||
} else {
|
||||
configFiles.set(each.tag, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const acceptFile = (name: string) => {
|
||||
if (name.lastIndexOf('.') >= 0) {
|
||||
const suffix: string | undefined = name.split('.').pop();
|
||||
if (suffix) {
|
||||
addFileType(suffix);
|
||||
}
|
||||
}
|
||||
addConfigFiles(name);
|
||||
};
|
||||
|
||||
const token: { count: number, maxReached: boolean } = { count: 0, maxReached: false };
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
walk(folder, filter, token, async (files) => {
|
||||
files.forEach(acceptFile);
|
||||
|
||||
const launchConfigs = await collectLaunchConfigs(folder);
|
||||
|
||||
resolve({
|
||||
configFiles: asSortedItems(configFiles),
|
||||
fileTypes: asSortedItems(fileTypes),
|
||||
fileCount: token.count,
|
||||
maxFilesReached: token.maxReached,
|
||||
launchConfigFiles: launchConfigs
|
||||
});
|
||||
});
|
||||
});
|
||||
await collect(folder, folder, filter, token);
|
||||
const launchConfigs = await collectLaunchConfigs(folder);
|
||||
return {
|
||||
configFiles: asSortedItems(configFiles),
|
||||
fileTypes: asSortedItems(fileTypes),
|
||||
fileCount: token.count,
|
||||
maxFilesReached: token.maxReached,
|
||||
launchConfigFiles: launchConfigs
|
||||
};
|
||||
}
|
||||
|
||||
function asSortedItems(map: Map<string, number>): WorkspaceStatItem[] {
|
||||
const a: WorkspaceStatItem[] = [];
|
||||
map.forEach((value, index) => a.push({ name: index, count: value }));
|
||||
return a.sort((a, b) => b.count - a.count);
|
||||
function asSortedItems(items: Map<string, number>): WorkspaceStatItem[] {
|
||||
return [
|
||||
...Iterable.map(items.entries(), ([name, count]) => ({ name: name, count: count }))
|
||||
].sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
export function getMachineInfo(): IMachineInfo {
|
||||
@@ -540,58 +516,61 @@ export class DiagnosticsService implements IDiagnosticsService {
|
||||
}
|
||||
|
||||
public async reportWorkspaceStats(workspace: IWorkspaceInformation): Promise<void> {
|
||||
workspace.folders.forEach(folder => {
|
||||
const folderUri = URI.revive(folder.uri);
|
||||
if (folderUri.scheme === 'file') {
|
||||
const folder = folderUri.fsPath;
|
||||
collectWorkspaceStats(folder, ['node_modules', '.git']).then(stats => {
|
||||
type WorkspaceStatsClassification = {
|
||||
'workspace.id': { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
type WorkspaceStatsEvent = {
|
||||
'workspace.id': string | undefined;
|
||||
rendererSessionId: string;
|
||||
};
|
||||
this.telemetryService.publicLog2<WorkspaceStatsEvent, WorkspaceStatsClassification>('workspace.stats', {
|
||||
'workspace.id': workspace.telemetryId,
|
||||
rendererSessionId: workspace.rendererSessionId
|
||||
});
|
||||
type WorkspaceStatsFileClassification = {
|
||||
rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
type: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
count: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
type WorkspaceStatsFileEvent = {
|
||||
rendererSessionId: string;
|
||||
type: string;
|
||||
count: number;
|
||||
};
|
||||
stats.fileTypes.forEach(e => {
|
||||
this.telemetryService.publicLog2<WorkspaceStatsFileEvent, WorkspaceStatsFileClassification>('workspace.stats.file', {
|
||||
rendererSessionId: workspace.rendererSessionId,
|
||||
type: e.name,
|
||||
count: e.count
|
||||
});
|
||||
});
|
||||
stats.launchConfigFiles.forEach(e => {
|
||||
this.telemetryService.publicLog2<WorkspaceStatsFileEvent, WorkspaceStatsFileClassification>('workspace.stats.launchConfigFile', {
|
||||
rendererSessionId: workspace.rendererSessionId,
|
||||
type: e.name,
|
||||
count: e.count
|
||||
});
|
||||
});
|
||||
stats.configFiles.forEach(e => {
|
||||
this.telemetryService.publicLog2<WorkspaceStatsFileEvent, WorkspaceStatsFileClassification>('workspace.stats.configFiles', {
|
||||
rendererSessionId: workspace.rendererSessionId,
|
||||
type: e.name,
|
||||
count: e.count
|
||||
});
|
||||
});
|
||||
}).catch(_ => {
|
||||
// Report nothing if collecting metadata fails.
|
||||
});
|
||||
for (const { uri } of workspace.folders) {
|
||||
const folderUri = URI.revive(uri);
|
||||
if (folderUri.scheme !== 'file') {
|
||||
continue;
|
||||
}
|
||||
});
|
||||
|
||||
const folder = folderUri.fsPath;
|
||||
try {
|
||||
const stats = await collectWorkspaceStats(folder, ['node_modules', '.git']);
|
||||
type WorkspaceStatsClassification = {
|
||||
'workspace.id': { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
type WorkspaceStatsEvent = {
|
||||
'workspace.id': string | undefined;
|
||||
rendererSessionId: string;
|
||||
};
|
||||
this.telemetryService.publicLog2<WorkspaceStatsEvent, WorkspaceStatsClassification>('workspace.stats', {
|
||||
'workspace.id': workspace.telemetryId,
|
||||
rendererSessionId: workspace.rendererSessionId
|
||||
});
|
||||
type WorkspaceStatsFileClassification = {
|
||||
rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
type: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
count: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
type WorkspaceStatsFileEvent = {
|
||||
rendererSessionId: string;
|
||||
type: string;
|
||||
count: number;
|
||||
};
|
||||
stats.fileTypes.forEach(e => {
|
||||
this.telemetryService.publicLog2<WorkspaceStatsFileEvent, WorkspaceStatsFileClassification>('workspace.stats.file', {
|
||||
rendererSessionId: workspace.rendererSessionId,
|
||||
type: e.name,
|
||||
count: e.count
|
||||
});
|
||||
});
|
||||
stats.launchConfigFiles.forEach(e => {
|
||||
this.telemetryService.publicLog2<WorkspaceStatsFileEvent, WorkspaceStatsFileClassification>('workspace.stats.launchConfigFile', {
|
||||
rendererSessionId: workspace.rendererSessionId,
|
||||
type: e.name,
|
||||
count: e.count
|
||||
});
|
||||
});
|
||||
stats.configFiles.forEach(e => {
|
||||
this.telemetryService.publicLog2<WorkspaceStatsFileEvent, WorkspaceStatsFileClassification>('workspace.stats.configFiles', {
|
||||
rendererSessionId: workspace.rendererSessionId,
|
||||
type: e.name,
|
||||
count: e.count
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
// Report nothing if collecting metadata fails.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +170,12 @@ export interface IEditorOptions {
|
||||
*/
|
||||
readonly pinned?: boolean;
|
||||
|
||||
/**
|
||||
* An editor that is sticky moves to the beginning of the editors list within the group and will remain
|
||||
* there unless explicitly closed. Operations such as "Close All" will not close sticky editors.
|
||||
*/
|
||||
readonly sticky?: boolean;
|
||||
|
||||
/**
|
||||
* The index in the document stack where to insert the editor into when opening.
|
||||
*/
|
||||
|
||||
@@ -62,6 +62,7 @@ export interface ParsedArgs {
|
||||
'install-source'?: string;
|
||||
'disable-updates'?: boolean;
|
||||
'disable-crash-reporter'?: boolean;
|
||||
'crash-reporter-directory'?: string;
|
||||
'skip-add-to-recently-opened'?: boolean;
|
||||
'max-memory'?: string;
|
||||
'file-write'?: boolean;
|
||||
@@ -186,6 +187,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
|
||||
'disable-telemetry': { type: 'boolean' },
|
||||
'disable-updates': { type: 'boolean' },
|
||||
'disable-crash-reporter': { type: 'boolean' },
|
||||
'crash-reporter-directory': { type: 'string' },
|
||||
'disable-user-env-probe': { type: 'boolean' },
|
||||
'skip-add-to-recently-opened': { type: 'boolean' },
|
||||
'unity-launch': { type: 'boolean' },
|
||||
|
||||
@@ -253,6 +253,7 @@ export class EnvironmentService implements INativeEnvironmentService {
|
||||
|
||||
get disableUpdates(): boolean { return !!this._args['disable-updates']; }
|
||||
get disableCrashReporter(): boolean { return !!this._args['disable-crash-reporter']; }
|
||||
get crashReporterDirectory(): string | undefined { return this._args['crash-reporter-directory']; }
|
||||
|
||||
get driverHandle(): string | undefined { return this._args['driver']; }
|
||||
get driverVerbose(): boolean { return !!this._args['driver-verbose']; }
|
||||
|
||||
Vendored
+113
@@ -5452,6 +5452,12 @@ declare module 'vscode' {
|
||||
*/
|
||||
readonly extensionPath: string;
|
||||
|
||||
/**
|
||||
* Gets the extension's environment variable collection for this workspace, enabling changes
|
||||
* to be applied to terminal environment variables.
|
||||
*/
|
||||
readonly environmentVariableCollection: EnvironmentVariableCollection;
|
||||
|
||||
/**
|
||||
* Get the absolute path of a resource contained in the extension.
|
||||
*
|
||||
@@ -8257,6 +8263,113 @@ declare module 'vscode' {
|
||||
readonly code: number | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* A type of mutation that can be applied to an environment variable.
|
||||
*/
|
||||
export enum EnvironmentVariableMutatorType {
|
||||
/**
|
||||
* Replace the variable's existing value.
|
||||
*/
|
||||
Replace = 1,
|
||||
/**
|
||||
* Append to the end of the variable's existing value.
|
||||
*/
|
||||
Append = 2,
|
||||
/**
|
||||
* Prepend to the start of the variable's existing value.
|
||||
*/
|
||||
Prepend = 3
|
||||
}
|
||||
|
||||
/**
|
||||
* A type of mutation and its value to be applied to an environment variable.
|
||||
*/
|
||||
export interface EnvironmentVariableMutator {
|
||||
/**
|
||||
* The type of mutation that will occur to the variable.
|
||||
*/
|
||||
readonly type: EnvironmentVariableMutatorType;
|
||||
|
||||
/**
|
||||
* The value to use for the variable.
|
||||
*/
|
||||
readonly value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of mutations that an extension can apply to a process environment.
|
||||
*/
|
||||
export interface EnvironmentVariableCollection {
|
||||
/**
|
||||
* Whether the collection should be cached for the workspace and applied to the terminal
|
||||
* across window reloads. When true the collection will be active immediately such when the
|
||||
* window reloads. Additionally, this API will return the cached version if it exists. The
|
||||
* collection will be invalidated when the extension is uninstalled or when the collection
|
||||
* is cleared. Defaults to true.
|
||||
*/
|
||||
persistent: boolean;
|
||||
|
||||
/**
|
||||
* Replace an environment variable with a value.
|
||||
*
|
||||
* Note that an extension can only make a single change to any one variable, so this will
|
||||
* overwrite any previous calls to replace, append or prepend.
|
||||
*
|
||||
* @param variable The variable to replace.
|
||||
* @param value The value to replace the variable with.
|
||||
*/
|
||||
replace(variable: string, value: string): void;
|
||||
|
||||
/**
|
||||
* Append a value to an environment variable.
|
||||
*
|
||||
* Note that an extension can only make a single change to any one variable, so this will
|
||||
* overwrite any previous calls to replace, append or prepend.
|
||||
*
|
||||
* @param variable The variable to append to.
|
||||
* @param value The value to append to the variable.
|
||||
*/
|
||||
append(variable: string, value: string): void;
|
||||
|
||||
/**
|
||||
* Prepend a value to an environment variable.
|
||||
*
|
||||
* Note that an extension can only make a single change to any one variable, so this will
|
||||
* overwrite any previous calls to replace, append or prepend.
|
||||
*
|
||||
* @param variable The variable to prepend.
|
||||
* @param value The value to prepend to the variable.
|
||||
*/
|
||||
prepend(variable: string, value: string): void;
|
||||
|
||||
/**
|
||||
* Gets the mutator that this collection applies to a variable, if any.
|
||||
*
|
||||
* @param variable The variable to get the mutator for.
|
||||
*/
|
||||
get(variable: string): EnvironmentVariableMutator | undefined;
|
||||
|
||||
/**
|
||||
* Iterate over each mutator in this collection.
|
||||
*
|
||||
* @param callback Function to execute for each entry.
|
||||
* @param thisArg The `this` context used when invoking the handler function.
|
||||
*/
|
||||
forEach(callback: (variable: string, mutator: EnvironmentVariableMutator, collection: EnvironmentVariableCollection) => any, thisArg?: any): void;
|
||||
|
||||
/**
|
||||
* Deletes this collection's mutator for a variable.
|
||||
*
|
||||
* @param variable The variable to delete the mutator for.
|
||||
*/
|
||||
delete(variable: string): void;
|
||||
|
||||
/**
|
||||
* Clears all mutators from this collection.
|
||||
*/
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A location in the editor at which progress information can be shown. It depends on the
|
||||
* location how progress is visually represented.
|
||||
|
||||
Vendored
+37
-108
@@ -930,103 +930,6 @@ declare module 'vscode' {
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Contribute to terminal environment https://github.com/microsoft/vscode/issues/46696
|
||||
|
||||
export enum EnvironmentVariableMutatorType {
|
||||
/**
|
||||
* Replace the variable's existing value.
|
||||
*/
|
||||
Replace = 1,
|
||||
/**
|
||||
* Append to the end of the variable's existing value.
|
||||
*/
|
||||
Append = 2,
|
||||
/**
|
||||
* Prepend to the start of the variable's existing value.
|
||||
*/
|
||||
Prepend = 3
|
||||
}
|
||||
|
||||
export interface EnvironmentVariableMutator {
|
||||
/**
|
||||
* The type of mutation that will occur to the variable.
|
||||
*/
|
||||
readonly type: EnvironmentVariableMutatorType;
|
||||
|
||||
/**
|
||||
* The value to use for the variable.
|
||||
*/
|
||||
readonly value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of mutations that an extension can apply to a process environment.
|
||||
*/
|
||||
export interface EnvironmentVariableCollection {
|
||||
/**
|
||||
* Whether the collection should be cached for the workspace and applied to the terminal
|
||||
* across window reloads. When true the collection will be active immediately such when the
|
||||
* window reloads. Additionally, this API will return the cached version if it exists. The
|
||||
* collection will be invalidated when the extension is uninstalled or when the collection
|
||||
* is cleared. Defaults to true.
|
||||
*/
|
||||
persistent: boolean;
|
||||
|
||||
/**
|
||||
* Replace an environment variable with a value.
|
||||
*
|
||||
* Note that an extension can only make a single change to any one variable, so this will
|
||||
* overwrite any previous calls to replace, append or prepend.
|
||||
*/
|
||||
replace(variable: string, value: string): void;
|
||||
|
||||
/**
|
||||
* Append a value to an environment variable.
|
||||
*
|
||||
* Note that an extension can only make a single change to any one variable, so this will
|
||||
* overwrite any previous calls to replace, append or prepend.
|
||||
*/
|
||||
append(variable: string, value: string): void;
|
||||
|
||||
/**
|
||||
* Prepend a value to an environment variable.
|
||||
*
|
||||
* Note that an extension can only make a single change to any one variable, so this will
|
||||
* overwrite any previous calls to replace, append or prepend.
|
||||
*/
|
||||
prepend(variable: string, value: string): void;
|
||||
|
||||
/**
|
||||
* Gets the mutator that this collection applies to a variable, if any.
|
||||
*/
|
||||
get(variable: string): EnvironmentVariableMutator | undefined;
|
||||
|
||||
/**
|
||||
* Iterate over each mutator in this collection.
|
||||
*/
|
||||
forEach(callback: (variable: string, mutator: EnvironmentVariableMutator, collection: EnvironmentVariableCollection) => any, thisArg?: any): void;
|
||||
|
||||
/**
|
||||
* Deletes this collection's mutator for a variable.
|
||||
*/
|
||||
delete(variable: string): void;
|
||||
|
||||
/**
|
||||
* Clears all mutators from this collection.
|
||||
*/
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
export interface ExtensionContext {
|
||||
/**
|
||||
* Gets the extension's environment variable collection for this workspace, enabling changes
|
||||
* to be applied to terminal environment variables.
|
||||
*/
|
||||
readonly environmentVariableCollection: EnvironmentVariableCollection;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region @jrieken -> exclusive document filters
|
||||
|
||||
export interface DocumentFilter {
|
||||
@@ -1737,12 +1640,6 @@ declare module 'vscode' {
|
||||
edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable<boolean>;
|
||||
}
|
||||
|
||||
export interface NotebookProvider {
|
||||
resolveNotebook(editor: NotebookEditor): Promise<void>;
|
||||
executeCell(document: NotebookDocument, cell: NotebookCell | undefined, token: CancellationToken): Promise<void>;
|
||||
save(document: NotebookDocument): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface NotebookOutputSelector {
|
||||
type: string;
|
||||
subTypes?: string[];
|
||||
@@ -1805,11 +1702,6 @@ declare module 'vscode' {
|
||||
provider: NotebookContentProvider
|
||||
): Disposable;
|
||||
|
||||
export function registerNotebookProvider(
|
||||
notebookType: string,
|
||||
provider: NotebookProvider
|
||||
): Disposable;
|
||||
|
||||
export function registerNotebookOutputRenderer(
|
||||
type: string,
|
||||
outputSelector: NotebookOutputSelector,
|
||||
@@ -1839,6 +1731,43 @@ declare module 'vscode' {
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region @connor4312 extension mode: https://github.com/microsoft/vscode/issues/95926
|
||||
|
||||
/**
|
||||
* The ExtensionMode is provided on the `ExtensionContext` and indicates the
|
||||
* mode the specific extension is running in.
|
||||
*/
|
||||
export enum ExtensionMode {
|
||||
/**
|
||||
* The extension is installed normally (for example, from the marketplace
|
||||
* or VSIX) in VS Code.
|
||||
*/
|
||||
Release = 1,
|
||||
|
||||
/**
|
||||
* The extension is running from an `--extensionDevelopmentPath` provided
|
||||
* when launching VS Code.
|
||||
*/
|
||||
Development = 2,
|
||||
|
||||
/**
|
||||
* The extension is running from an `--extensionDevelopmentPath` and
|
||||
* the extension host is running unit tests.
|
||||
*/
|
||||
Test = 3,
|
||||
}
|
||||
|
||||
export interface ExtensionContext {
|
||||
/**
|
||||
* The mode the extension is running in. This is specific to the current
|
||||
* extension. One extension may be in `ExtensionMode.Development` while
|
||||
* other extensions in the host run in `ExtensionMode.Release`.
|
||||
*/
|
||||
readonly extensionMode: ExtensionMode;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region https://github.com/microsoft/vscode/issues/39441
|
||||
|
||||
export interface CompletionItem {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IEx
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService';
|
||||
import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, CellKind, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -84,7 +84,9 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
|
||||
registerListeners() {
|
||||
this._register(this._notebookService.onDidChangeActiveEditor(e => {
|
||||
this._proxy.$updateActiveEditor(e.viewType, e.uri);
|
||||
this._proxy.$acceptDocumentAndEditorsDelta({
|
||||
newActiveEditor: e.uri
|
||||
});
|
||||
}));
|
||||
|
||||
const updateOrder = () => {
|
||||
@@ -129,16 +131,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
return;
|
||||
}
|
||||
|
||||
async $createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise<void> {
|
||||
let controller = this._notebookProviders.get(viewType);
|
||||
|
||||
if (controller) {
|
||||
controller.createNotebookDocument(handle, viewType, resource);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void> {
|
||||
let controller = this._notebookProviders.get(viewType);
|
||||
|
||||
@@ -163,11 +155,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
}
|
||||
}
|
||||
|
||||
async resolveNotebook(viewType: string, uri: URI): Promise<number | undefined> {
|
||||
let handle = await this._proxy.$resolveNotebook(viewType, uri);
|
||||
return handle;
|
||||
}
|
||||
|
||||
async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise<void> {
|
||||
let controller = this._notebookProviders.get(viewType);
|
||||
controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers);
|
||||
@@ -195,6 +182,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
|
||||
export class MainThreadNotebookController implements IMainNotebookController {
|
||||
private _mapping: Map<string, MainThreadNotebookDocument> = new Map();
|
||||
static documentHandle: number = 0;
|
||||
|
||||
constructor(
|
||||
private readonly _proxy: ExtHostNotebookShape,
|
||||
@@ -203,26 +191,44 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
) {
|
||||
}
|
||||
|
||||
async resolveNotebook(viewType: string, uri: URI): Promise<NotebookTextModel | undefined> {
|
||||
// TODO: resolve notebook should wait for all notebook document destory operations to finish.
|
||||
async createNotebook(viewType: string, uri: URI, forBackup: boolean, forceReload: boolean): Promise<NotebookTextModel | undefined> {
|
||||
let mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
|
||||
|
||||
if (mainthreadNotebook) {
|
||||
if (forceReload) {
|
||||
const data = await this._proxy.$resolveNotebookData(viewType, uri);
|
||||
if (!data) {
|
||||
return undefined; // {{SQL CARBON EDIT}}
|
||||
}
|
||||
|
||||
mainthreadNotebook.textModel.languages = data.languages;
|
||||
mainthreadNotebook.textModel.metadata = data.metadata;
|
||||
mainthreadNotebook.textModel.applyEdit(mainthreadNotebook.textModel.versionId, [
|
||||
{ editType: CellEditType.Delete, count: mainthreadNotebook.textModel.cells.length, index: 0 },
|
||||
{ editType: CellEditType.Insert, index: 0, cells: data.cells }
|
||||
]);
|
||||
}
|
||||
return mainthreadNotebook.textModel;
|
||||
}
|
||||
|
||||
let notebookHandle = await this._mainThreadNotebook.resolveNotebook(viewType, uri);
|
||||
if (notebookHandle !== undefined) {
|
||||
mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
|
||||
if (mainthreadNotebook && mainthreadNotebook.textModel.cells.length === 0) {
|
||||
// it's empty, we should create an empty template one
|
||||
const mainCell = mainthreadNotebook.textModel.createCellTextModel([''], mainthreadNotebook.textModel.languages.length ? mainthreadNotebook.textModel.languages[0] : '', CellKind.Code, [], undefined);
|
||||
mainthreadNotebook.textModel.insertTemplateCell(mainCell);
|
||||
}
|
||||
return mainthreadNotebook?.textModel;
|
||||
let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri);
|
||||
await this.createNotebookDocument(document);
|
||||
|
||||
if (forBackup) {
|
||||
return document.textModel;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
// open notebook document
|
||||
const data = await this._proxy.$resolveNotebookData(viewType, uri);
|
||||
if (!data) {
|
||||
return undefined; // {{SQL CARBON EDIT}}
|
||||
}
|
||||
|
||||
document.textModel.languages = data.languages;
|
||||
document.textModel.metadata = data.metadata;
|
||||
document.textModel.initialize(data!.cells);
|
||||
|
||||
return document.textModel;
|
||||
}
|
||||
|
||||
async tryApplyEdits(resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise<boolean> {
|
||||
@@ -250,12 +256,32 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
this._proxy.$onDidReceiveMessage(uri, message);
|
||||
}
|
||||
|
||||
// Methods for ExtHost
|
||||
async createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise<void> {
|
||||
let document = new MainThreadNotebookDocument(this._proxy, handle, viewType, URI.revive(resource));
|
||||
this._mapping.set(URI.revive(resource).toString(), document);
|
||||
async createNotebookDocument(document: MainThreadNotebookDocument): Promise<void> {
|
||||
this._mapping.set(document.uri.toString(), document);
|
||||
|
||||
await this._proxy.$acceptDocumentAndEditorsDelta({
|
||||
addedDocuments: [{
|
||||
viewType: document.viewType,
|
||||
handle: document.handle,
|
||||
uri: document.uri
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
async removeNotebookDocument(notebook: INotebookTextModel): Promise<void> {
|
||||
let document = this._mapping.get(URI.from(notebook.uri).toString());
|
||||
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [notebook.uri] });
|
||||
document.dispose();
|
||||
this._mapping.delete(URI.from(notebook.uri).toString());
|
||||
}
|
||||
|
||||
// Methods for ExtHost
|
||||
|
||||
updateLanguages(resource: UriComponents, languages: string[]) {
|
||||
let document = this._mapping.get(URI.from(resource).toString());
|
||||
document?.textModel.updateLanguages(languages);
|
||||
@@ -280,21 +306,12 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
return this._proxy.$executeNotebook(this._viewType, uri, handle, token);
|
||||
}
|
||||
|
||||
async destoryNotebookDocument(notebook: INotebookTextModel): Promise<void> {
|
||||
let document = this._mapping.get(URI.from(notebook.uri).toString());
|
||||
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
|
||||
let removeFromExtHost = await this._proxy.$destoryNotebookDocument(this._viewType, notebook.uri);
|
||||
if (removeFromExtHost) {
|
||||
document.dispose();
|
||||
this._mapping.delete(URI.from(notebook.uri).toString());
|
||||
}
|
||||
}
|
||||
|
||||
async save(uri: URI, token: CancellationToken): Promise<boolean> {
|
||||
return this._proxy.$saveNotebook(this._viewType, uri, token);
|
||||
}
|
||||
|
||||
async saveAs(uri: URI, target: URI, token: CancellationToken): Promise<boolean> {
|
||||
return this._proxy.$saveNotebookAs(this._viewType, uri, target, token);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,6 +368,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
}
|
||||
|
||||
private addViews(extensions: readonly IExtensionPointUser<ViewExtensionPointType>[]): void {
|
||||
const viewIds: Set<string> = new Set<string>();
|
||||
const allViewDescriptors: { views: IViewDescriptor[], viewContainer: ViewContainer }[] = [];
|
||||
|
||||
for (const extension of extensions) {
|
||||
const { value, collector } = extension;
|
||||
|
||||
@@ -386,10 +389,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Explorer'.", entry.key));
|
||||
}
|
||||
const container = viewContainer || this.getDefaultViewContainer();
|
||||
const viewIds: string[] = [];
|
||||
const viewDescriptors = coalesce(entry.value.map((item, index) => {
|
||||
// validate
|
||||
if (viewIds.indexOf(item.id) !== -1) {
|
||||
if (viewIds.has(item.id)) {
|
||||
collector.error(localize('duplicateView1', "Cannot register multiple views with same id `{0}`", item.id));
|
||||
return null;
|
||||
}
|
||||
@@ -421,12 +423,16 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
remoteAuthority: item.remoteName || (<any>item).remoteAuthority // TODO@roblou - delete after remote extensions are updated
|
||||
};
|
||||
|
||||
viewIds.push(viewDescriptor.id);
|
||||
viewIds.add(viewDescriptor.id);
|
||||
return viewDescriptor;
|
||||
}));
|
||||
this.viewsRegistry.registerViews(viewDescriptors, container);
|
||||
|
||||
allViewDescriptors.push({ viewContainer: container, views: viewDescriptors });
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
this.viewsRegistry.registerViews2(allViewDescriptors);
|
||||
}
|
||||
|
||||
private getDefaultViewContainer(): ViewContainer {
|
||||
|
||||
@@ -931,10 +931,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidCloseNotebookDocument;
|
||||
},
|
||||
registerNotebookProvider: (viewType: string, provider: vscode.NotebookProvider) => {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.registerNotebookProvider(extension, viewType, provider);
|
||||
},
|
||||
registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider) => {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider);
|
||||
@@ -1015,6 +1011,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
EvaluatableExpression: extHostTypes.EvaluatableExpression,
|
||||
EventEmitter: Emitter,
|
||||
ExtensionKind: extHostTypes.ExtensionKind,
|
||||
ExtensionMode: extHostTypes.ExtensionMode,
|
||||
CustomExecution: extHostTypes.CustomExecution,
|
||||
CustomExecution2: extHostTypes.CustomExecution,
|
||||
FileChangeType: extHostTypes.FileChangeType,
|
||||
|
||||
@@ -51,7 +51,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
|
||||
import { Dto } from 'vs/base/common/types';
|
||||
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
@@ -695,7 +695,6 @@ export interface MainThreadNotebookShape extends IDisposable {
|
||||
$unregisterNotebookProvider(viewType: string): Promise<void>;
|
||||
$registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise<void>;
|
||||
$unregisterNotebookRenderer(handle: number): Promise<void>;
|
||||
$createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise<void>;
|
||||
$tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise<boolean>;
|
||||
$updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void>;
|
||||
$updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise<void>;
|
||||
@@ -1542,16 +1541,31 @@ export interface INotebookEditorPropertiesChangeData {
|
||||
selections: INotebookSelectionChangeEvent | null;
|
||||
}
|
||||
|
||||
export interface INotebookModelAddedData {
|
||||
uri: UriComponents;
|
||||
handle: number;
|
||||
// versionId: number;
|
||||
viewType: string;
|
||||
}
|
||||
|
||||
export interface INotebookDocumentsAndEditorsDelta {
|
||||
removedDocuments?: UriComponents[];
|
||||
addedDocuments?: INotebookModelAddedData[];
|
||||
// removedEditors?: string[];
|
||||
// addedEditors?: ITextEditorAddData[];
|
||||
newActiveEditor?: UriComponents | null;
|
||||
}
|
||||
|
||||
export interface ExtHostNotebookShape {
|
||||
$resolveNotebook(viewType: string, uri: UriComponents): Promise<number | undefined>;
|
||||
$resolveNotebookData(viewType: string, uri: UriComponents): Promise<NotebookDataDto | undefined>;
|
||||
$executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void>;
|
||||
$saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean>;
|
||||
$updateActiveEditor(viewType: string, uri: UriComponents): Promise<void>;
|
||||
$destoryNotebookDocument(viewType: string, uri: UriComponents): Promise<boolean>;
|
||||
$saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean>;
|
||||
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void;
|
||||
$onDidReceiveMessage(uri: UriComponents, message: any): void;
|
||||
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void;
|
||||
$acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void;
|
||||
$acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostStorageShape {
|
||||
|
||||
@@ -25,7 +25,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento';
|
||||
import { RemoteAuthorityResolverError } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { RemoteAuthorityResolverError, ExtensionMode } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
||||
@@ -358,8 +358,12 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
|
||||
|
||||
const globalState = new ExtensionMemento(extensionDescription.identifier.value, true, this._storage);
|
||||
const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage);
|
||||
const extensionMode = extensionDescription.isUnderDevelopment
|
||||
? (this._initData.environment.extensionTestsLocationURI ? ExtensionMode.Test : ExtensionMode.Development)
|
||||
: ExtensionMode.Release;
|
||||
|
||||
this._logService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.identifier.value}`);
|
||||
|
||||
return Promise.all([
|
||||
globalState.whenReady,
|
||||
workspaceState.whenReady,
|
||||
@@ -376,10 +380,11 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
|
||||
get globalStoragePath() { return that._storagePath.globalValue(extensionDescription); },
|
||||
asAbsolutePath(relativePath: string) { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); },
|
||||
get logPath() { return path.join(that._initData.logsLocation.fsPath, extensionDescription.identifier.value); },
|
||||
get environmentVariableCollection() {
|
||||
get extensionMode() {
|
||||
checkProposedApiEnabled(extensionDescription);
|
||||
return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription);
|
||||
}
|
||||
return extensionMode;
|
||||
},
|
||||
get environmentVariableCollection() { return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription); }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecyc
|
||||
import { ISplice } from 'vs/base/common/sequence';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { Disposable as VSCodeDisposable } from './extHostTypes';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
|
||||
@@ -345,8 +345,6 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
|
||||
|
||||
transformMimeTypes(output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto {
|
||||
let mimeTypes = Object.keys(output.data);
|
||||
|
||||
// TODO@rebornix, the document display order might be assigned a bit later. We need to postpone sending the outputs to the core side.
|
||||
let coreDisplayOrder = this.renderingHandler.outputDisplayOrder;
|
||||
const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], this._displayOrder, coreDisplayOrder?.defaultOrder || []);
|
||||
|
||||
@@ -415,7 +413,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
|
||||
}
|
||||
}
|
||||
|
||||
export class NotebookEditorCellEdit {
|
||||
export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellEdit {
|
||||
private _finalized: boolean = false;
|
||||
private readonly _documentVersionId: number;
|
||||
private _collectedEdits: ICellEditOperation[] = [];
|
||||
@@ -526,13 +524,13 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
|
||||
}));
|
||||
}
|
||||
|
||||
edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable<boolean> {
|
||||
const edit = new NotebookEditorCellEdit(this);
|
||||
edit(callback: (editBuilder: NotebookEditorCellEditBuilder) => void): Thenable<boolean> {
|
||||
const edit = new NotebookEditorCellEditBuilder(this);
|
||||
callback(edit);
|
||||
return this._applyEdit(edit);
|
||||
}
|
||||
|
||||
private _applyEdit(editBuilder: NotebookEditorCellEdit): Promise<boolean> {
|
||||
private _applyEdit(editBuilder: NotebookEditorCellEditBuilder): Promise<boolean> {
|
||||
const editData = editBuilder.finalize();
|
||||
|
||||
// return when there is nothing to do
|
||||
@@ -625,7 +623,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
private static _handlePool: number = 0;
|
||||
|
||||
private readonly _proxy: MainThreadNotebookShape;
|
||||
private readonly _notebookProviders = new Map<string, { readonly provider: vscode.NotebookProvider, readonly extension: IExtensionDescription; }>();
|
||||
private readonly _notebookContentProviders = new Map<string, { readonly provider: vscode.NotebookContentProvider, readonly extension: IExtensionDescription; }>();
|
||||
private readonly _documents = new Map<string, ExtHostNotebookDocument>();
|
||||
private readonly _editors = new Map<string, { editor: ExtHostNotebookEditor, onDidReceiveMessage: Emitter<any>; }>();
|
||||
@@ -706,31 +703,13 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
return matches;
|
||||
}
|
||||
|
||||
registerNotebookProvider(
|
||||
extension: IExtensionDescription,
|
||||
viewType: string,
|
||||
provider: vscode.NotebookProvider,
|
||||
): vscode.Disposable {
|
||||
|
||||
if (this._notebookProviders.has(viewType)) {
|
||||
throw new Error(`Notebook provider for '${viewType}' already registered`);
|
||||
}
|
||||
|
||||
this._notebookProviders.set(viewType, { extension, provider });
|
||||
this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType);
|
||||
return new VSCodeDisposable(() => {
|
||||
this._notebookProviders.delete(viewType);
|
||||
this._proxy.$unregisterNotebookProvider(viewType);
|
||||
});
|
||||
}
|
||||
|
||||
registerNotebookContentProvider(
|
||||
extension: IExtensionDescription,
|
||||
viewType: string,
|
||||
provider: vscode.NotebookContentProvider,
|
||||
): vscode.Disposable {
|
||||
|
||||
if (this._notebookProviders.has(viewType)) {
|
||||
if (this._notebookContentProviders.has(viewType)) {
|
||||
throw new Error(`Notebook provider for '${viewType}' already registered`);
|
||||
}
|
||||
|
||||
@@ -742,97 +721,99 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
});
|
||||
}
|
||||
|
||||
async _resolveNotebookFromContentProvider(viewType: string, uri: UriComponents): Promise<number | undefined> {
|
||||
async $resolveNotebookData(viewType: string, uri: UriComponents): Promise<NotebookDataDto | undefined> {
|
||||
let provider = this._notebookContentProviders.get(viewType);
|
||||
let document = this._documents.get(URI.revive(uri).toString());
|
||||
|
||||
if (provider) {
|
||||
const revivedUri = URI.revive(uri);
|
||||
if (!this._documents.has(revivedUri.toString())) {
|
||||
let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this);
|
||||
await this._proxy.$createNotebookDocument(
|
||||
document.handle,
|
||||
viewType,
|
||||
uri
|
||||
);
|
||||
if (provider && document) {
|
||||
const rawCells = await provider.provider.openNotebook(URI.revive(uri));
|
||||
const renderers = new Set<number>();
|
||||
const dto = {
|
||||
metadata: {
|
||||
...notebookDocumentMetadataDefaults,
|
||||
...rawCells.metadata
|
||||
},
|
||||
languages: rawCells.languages,
|
||||
cells: rawCells.cells.map(cell => {
|
||||
let transformedOutputs = cell.outputs.map(output => {
|
||||
if (output.outputKind === CellOutputKind.Rich) {
|
||||
// TODO display string[]
|
||||
const ret = this._transformMimeTypes(document!, (rawCells.metadata.displayOrder as string[]) || [], output);
|
||||
|
||||
this._documents.set(revivedUri.toString(), document);
|
||||
}
|
||||
if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) {
|
||||
renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return output as IStreamOutput | IErrorOutput;
|
||||
}
|
||||
});
|
||||
|
||||
const onDidReceiveMessage = new Emitter<any>();
|
||||
|
||||
let editor = new ExtHostNotebookEditor(
|
||||
viewType,
|
||||
`${ExtHostNotebookController._handlePool++}`,
|
||||
revivedUri,
|
||||
this._proxy,
|
||||
onDidReceiveMessage,
|
||||
this._documents.get(revivedUri.toString())!,
|
||||
this._documentsAndEditors
|
||||
);
|
||||
|
||||
this._editors.set(revivedUri.toString(), { editor, onDidReceiveMessage });
|
||||
|
||||
const data = await provider.provider.openNotebook(revivedUri);
|
||||
editor.document.languages = data.languages;
|
||||
editor.document.metadata = {
|
||||
...notebookDocumentMetadataDefaults,
|
||||
...data.metadata
|
||||
return {
|
||||
language: cell.language,
|
||||
cellKind: cell.cellKind,
|
||||
metadata: cell.metadata,
|
||||
source: cell.source,
|
||||
outputs: transformedOutputs
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
await editor.edit(editBuilder => {
|
||||
for (let i = 0; i < data.cells.length; i++) {
|
||||
const cell = data.cells[i];
|
||||
editBuilder.insert(0, cell.source, cell.language, cell.cellKind, cell.outputs, cell.metadata);
|
||||
}
|
||||
});
|
||||
|
||||
this._onDidOpenNotebookDocument.fire(editor.document);
|
||||
return editor.document.handle;
|
||||
} else {
|
||||
return Promise.resolve(undefined);
|
||||
return dto;
|
||||
}
|
||||
|
||||
return undefined; // {{SQL CARBON EDIT}}
|
||||
}
|
||||
|
||||
async $resolveNotebook(viewType: string, uri: UriComponents): Promise<number | undefined> {
|
||||
let notebookFromNotebookContentProvider = await this._resolveNotebookFromContentProvider(viewType, uri);
|
||||
private _transformMimeTypes(document: ExtHostNotebookDocument, displayOrder: string[], output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto {
|
||||
let mimeTypes = Object.keys(output.data);
|
||||
let coreDisplayOrder = this.outputDisplayOrder;
|
||||
const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], displayOrder, coreDisplayOrder?.defaultOrder || []);
|
||||
|
||||
if (notebookFromNotebookContentProvider !== undefined) {
|
||||
return notebookFromNotebookContentProvider;
|
||||
}
|
||||
let orderMimeTypes: IOrderedMimeType[] = [];
|
||||
|
||||
let provider = this._notebookProviders.get(viewType);
|
||||
sorted.forEach(mimeType => {
|
||||
let handlers = this.findBestMatchedRenderer(mimeType);
|
||||
|
||||
if (provider) {
|
||||
if (!this._documents.has(URI.revive(uri).toString())) {
|
||||
let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, URI.revive(uri), this);
|
||||
await this._proxy.$createNotebookDocument(
|
||||
document.handle,
|
||||
viewType,
|
||||
uri
|
||||
);
|
||||
if (handlers.length) {
|
||||
let renderedOutput = handlers[0].render(document, output, mimeType);
|
||||
|
||||
this._documents.set(URI.revive(uri).toString(), document);
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: true,
|
||||
rendererId: handlers[0].handle,
|
||||
output: renderedOutput
|
||||
});
|
||||
|
||||
for (let i = 1; i < handlers.length; i++) {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false,
|
||||
rendererId: handlers[i].handle
|
||||
});
|
||||
}
|
||||
|
||||
if (mimeTypeSupportedByCore(mimeType)) {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false,
|
||||
rendererId: -1
|
||||
});
|
||||
}
|
||||
} else {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const onDidReceiveMessage = new Emitter<any>();
|
||||
|
||||
let editor = new ExtHostNotebookEditor(
|
||||
viewType,
|
||||
`${ExtHostNotebookController._handlePool++}`,
|
||||
URI.revive(uri),
|
||||
this._proxy,
|
||||
onDidReceiveMessage,
|
||||
this._documents.get(URI.revive(uri).toString())!,
|
||||
this._documentsAndEditors
|
||||
);
|
||||
|
||||
this._editors.set(URI.revive(uri).toString(), { editor, onDidReceiveMessage });
|
||||
await provider.provider.resolveNotebook(editor);
|
||||
// await editor.document.$updateCells();
|
||||
return editor.document.handle;
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
return {
|
||||
outputKind: output.outputKind,
|
||||
data: output.data,
|
||||
orderedMimeTypes: orderMimeTypes,
|
||||
pickedMimeTypeIndex: 0
|
||||
};
|
||||
}
|
||||
|
||||
async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void> {
|
||||
@@ -847,15 +828,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
|
||||
return this._notebookContentProviders.get(viewType)!.provider.executeCell(document, cell, token);
|
||||
}
|
||||
|
||||
let provider = this._notebookProviders.get(viewType);
|
||||
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
|
||||
return provider.provider.executeCell(document!, cell, token);
|
||||
}
|
||||
|
||||
async $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean> {
|
||||
@@ -874,44 +846,26 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
return true;
|
||||
}
|
||||
|
||||
let provider = this._notebookProviders.get(viewType);
|
||||
|
||||
if (provider && document) {
|
||||
return await provider.provider.save(document);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async $updateActiveEditor(viewType: string, uri: UriComponents): Promise<void> {
|
||||
this._activeNotebookDocument = this._documents.get(URI.revive(uri).toString());
|
||||
this._activeNotebookEditor = this._editors.get(URI.revive(uri).toString())?.editor;
|
||||
}
|
||||
|
||||
async $destoryNotebookDocument(viewType: string, uri: UriComponents): Promise<boolean> {
|
||||
let provider = this._notebookProviders.get(viewType);
|
||||
|
||||
if (!provider) {
|
||||
async $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean> {
|
||||
let document = this._documents.get(URI.revive(uri).toString());
|
||||
if (!document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let document = this._documents.get(URI.revive(uri).toString());
|
||||
if (this._notebookContentProviders.has(viewType)) {
|
||||
try {
|
||||
await this._notebookContentProviders.get(viewType)!.provider.saveNotebookAs(URI.revive(target), document, token);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document) {
|
||||
document.dispose();
|
||||
this._documents.delete(URI.revive(uri).toString());
|
||||
this._onDidCloseNotebookDocument.fire(document);
|
||||
return true;
|
||||
}
|
||||
|
||||
let editor = this._editors.get(URI.revive(uri).toString());
|
||||
|
||||
if (editor) {
|
||||
editor.editor.dispose();
|
||||
editor.onDidReceiveMessage.dispose();
|
||||
this._editors.delete(URI.revive(uri).toString());
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void {
|
||||
@@ -957,4 +911,60 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta) {
|
||||
if (delta.removedDocuments) {
|
||||
delta.removedDocuments.forEach((uri) => {
|
||||
let document = this._documents.get(URI.revive(uri).toString());
|
||||
|
||||
if (document) {
|
||||
document.dispose();
|
||||
this._documents.delete(URI.revive(uri).toString());
|
||||
this._onDidCloseNotebookDocument.fire(document);
|
||||
}
|
||||
|
||||
let editor = this._editors.get(URI.revive(uri).toString());
|
||||
|
||||
if (editor) {
|
||||
editor.editor.dispose();
|
||||
editor.onDidReceiveMessage.dispose();
|
||||
this._editors.delete(URI.revive(uri).toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (delta.addedDocuments) {
|
||||
delta.addedDocuments.forEach(modelData => {
|
||||
const revivedUri = URI.revive(modelData.uri);
|
||||
const viewType = modelData.viewType;
|
||||
if (!this._documents.has(revivedUri.toString())) {
|
||||
let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this);
|
||||
this._documents.set(revivedUri.toString(), document);
|
||||
}
|
||||
|
||||
const onDidReceiveMessage = new Emitter<any>();
|
||||
const document = this._documents.get(revivedUri.toString())!;
|
||||
|
||||
let editor = new ExtHostNotebookEditor(
|
||||
viewType,
|
||||
`${ExtHostNotebookController._handlePool++}`,
|
||||
revivedUri,
|
||||
this._proxy,
|
||||
onDidReceiveMessage,
|
||||
document,
|
||||
this._documentsAndEditors
|
||||
);
|
||||
|
||||
this._onDidOpenNotebookDocument.fire(document);
|
||||
|
||||
// TODO, does it already exist?
|
||||
this._editors.set(revivedUri.toString(), { editor, onDidReceiveMessage });
|
||||
});
|
||||
}
|
||||
|
||||
if (delta.newActiveEditor) {
|
||||
this._activeNotebookDocument = this._documents.get(URI.revive(delta.newActiveEditor).toString());
|
||||
this._activeNotebookEditor = this._editors.get(URI.revive(delta.newActiveEditor).toString())?.editor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2742,3 +2742,27 @@ export class TimelineItem implements vscode.TimelineItem {
|
||||
}
|
||||
|
||||
//#endregion Timeline
|
||||
|
||||
//#region ExtensionContext
|
||||
|
||||
export enum ExtensionMode {
|
||||
/**
|
||||
* The extension is installed normally (for example, from the marketplace
|
||||
* or VSIX) in VS Code.
|
||||
*/
|
||||
Release = 1,
|
||||
|
||||
/**
|
||||
* The extension is running from an `--extensionDevelopmentPath` provided
|
||||
* when launching VS Code.
|
||||
*/
|
||||
Development = 2,
|
||||
|
||||
/**
|
||||
* The extension is running from an `--extensionDevelopmentPath` and
|
||||
* the extension host is running unit tests.
|
||||
*/
|
||||
Test = 3,
|
||||
}
|
||||
|
||||
//#endregion ExtensionContext
|
||||
|
||||
@@ -37,7 +37,6 @@ namespace schema {
|
||||
case 'debug/callstack/context': return MenuId.DebugCallStackContext;
|
||||
case 'debug/toolbar': return MenuId.DebugToolBar;
|
||||
case 'debug/toolBar': return MenuId.DebugToolBar;
|
||||
case 'menuBar/file': return MenuId.MenubarFileMenu;
|
||||
case 'menuBar/webNavigation': return MenuId.MenubarWebNavigationMenu;
|
||||
case 'scm/title': return MenuId.SCMTitle;
|
||||
case 'scm/sourceControl': return MenuId.SCMSourceControl;
|
||||
@@ -70,7 +69,6 @@ namespace schema {
|
||||
export function isProposedAPI(menuId: MenuId): boolean {
|
||||
switch (menuId) {
|
||||
case MenuId.StatusBarWindowIndicatorMenu:
|
||||
case MenuId.MenubarFileMenu:
|
||||
case MenuId.MenubarWebNavigationMenu:
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -230,13 +230,10 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
|
||||
public getEnvironmentVariableCollection(extension: IExtensionDescription): vscode.EnvironmentVariableCollection {
|
||||
let collection = this._environmentVariableCollections.get(extension.identifier.value);
|
||||
|
||||
if (!collection) {
|
||||
// TODO: Disable dispose
|
||||
collection = new EnvironmentVariableCollection();
|
||||
this._setEnvironmentVariableCollection(extension.identifier.value, collection);
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
|
||||
@@ -202,8 +202,9 @@ class NavigateDownAction extends BaseNavigationAction {
|
||||
}
|
||||
|
||||
function findVisibleNeighbour(layoutService: IWorkbenchLayoutService, part: Parts, next: boolean): Parts {
|
||||
const neighbour = part === Parts.EDITOR_PART ? (next ? Parts.STATUSBAR_PART : Parts.PANEL_PART) : part === Parts.STATUSBAR_PART ? (next ? Parts.SIDEBAR_PART : Parts.EDITOR_PART) :
|
||||
part === Parts.SIDEBAR_PART ? (next ? Parts.PANEL_PART : Parts.STATUSBAR_PART) : part === Parts.PANEL_PART ? (next ? Parts.EDITOR_PART : Parts.SIDEBAR_PART) : Parts.EDITOR_PART;
|
||||
const neighbour = part === Parts.EDITOR_PART ? (next ? Parts.STATUSBAR_PART : Parts.PANEL_PART) : part === Parts.STATUSBAR_PART ? (next ? Parts.ACTIVITYBAR_PART : Parts.EDITOR_PART) :
|
||||
part === Parts.ACTIVITYBAR_PART ? (next ? Parts.SIDEBAR_PART : Parts.STATUSBAR_PART) : part === Parts.SIDEBAR_PART ? (next ? Parts.PANEL_PART : Parts.ACTIVITYBAR_PART) :
|
||||
part === Parts.PANEL_PART ? (next ? Parts.EDITOR_PART : Parts.SIDEBAR_PART) : Parts.EDITOR_PART;
|
||||
if (layoutService.isVisible(neighbour) || neighbour === Parts.EDITOR_PART) {
|
||||
return neighbour;
|
||||
}
|
||||
@@ -212,8 +213,8 @@ function findVisibleNeighbour(layoutService: IWorkbenchLayoutService, part: Part
|
||||
}
|
||||
|
||||
function focusNextOrPreviousPart(layoutService: IWorkbenchLayoutService, next: boolean): void {
|
||||
const currentlyFocusedPart = layoutService.hasFocus(Parts.EDITOR_PART) ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.STATUSBAR_PART) ? Parts.STATUSBAR_PART :
|
||||
layoutService.hasFocus(Parts.SIDEBAR_PART) ? Parts.SIDEBAR_PART : layoutService.hasFocus(Parts.PANEL_PART) ? Parts.PANEL_PART : undefined;
|
||||
const currentlyFocusedPart = layoutService.hasFocus(Parts.EDITOR_PART) ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.ACTIVITYBAR_PART) ? Parts.ACTIVITYBAR_PART :
|
||||
layoutService.hasFocus(Parts.STATUSBAR_PART) ? Parts.STATUSBAR_PART : layoutService.hasFocus(Parts.SIDEBAR_PART) ? Parts.SIDEBAR_PART : layoutService.hasFocus(Parts.PANEL_PART) ? Parts.PANEL_PART : undefined;
|
||||
let partToFocus = Parts.EDITOR_PART;
|
||||
if (currentlyFocusedPart) {
|
||||
partToFocus = findVisibleNeighbour(layoutService, currentlyFocusedPart, next);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IWindowOpenable } from 'vs/platform/windows/common/windows';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITextFileService, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IEditorViewState } from 'vs/editor/common/editorCommon';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
import { DragMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { normalizeDriveLetter } from 'vs/base/common/labels';
|
||||
@@ -58,7 +58,7 @@ export interface IDraggedEditor extends IDraggedResource {
|
||||
content?: string;
|
||||
encoding?: string;
|
||||
mode?: string;
|
||||
viewState?: IEditorViewState;
|
||||
options?: ITextEditorOptions;
|
||||
}
|
||||
|
||||
export interface ISerializedDraggedEditor {
|
||||
@@ -66,7 +66,7 @@ export interface ISerializedDraggedEditor {
|
||||
content?: string;
|
||||
encoding?: string;
|
||||
mode?: string;
|
||||
viewState?: IEditorViewState;
|
||||
options?: ITextEditorOptions;
|
||||
}
|
||||
|
||||
export const CodeDataTransfers = {
|
||||
@@ -90,7 +90,7 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): Array<ID
|
||||
resources.push({
|
||||
resource: URI.parse(draggedEditor.resource),
|
||||
content: draggedEditor.content,
|
||||
viewState: draggedEditor.viewState,
|
||||
options: draggedEditor.options,
|
||||
encoding: draggedEditor.encoding,
|
||||
mode: draggedEditor.mode,
|
||||
isExternal: false
|
||||
@@ -202,9 +202,9 @@ export class ResourcesDropHandler {
|
||||
encoding: (untitledOrFileResource as IDraggedEditor).encoding,
|
||||
mode: (untitledOrFileResource as IDraggedEditor).mode,
|
||||
options: {
|
||||
...(untitledOrFileResource as IDraggedEditor).options,
|
||||
pinned: true,
|
||||
index: targetIndex,
|
||||
viewState: (untitledOrFileResource as IDraggedEditor).viewState
|
||||
index: targetIndex
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -311,7 +311,7 @@ export class ResourcesDropHandler {
|
||||
}
|
||||
}
|
||||
|
||||
export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: (URI | { resource: URI, isDirectory: boolean })[], event: DragMouseEvent | DragEvent): void {
|
||||
export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: (URI | { resource: URI, isDirectory: boolean })[], optionsCallback: ((resource: URI) => ITextEditorOptions) | undefined, event: DragMouseEvent | DragEvent): void {
|
||||
if (resources.length === 0 || !event.dataTransfer) {
|
||||
return;
|
||||
}
|
||||
@@ -346,18 +346,30 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
|
||||
|
||||
const draggedEditors: ISerializedDraggedEditor[] = [];
|
||||
files.forEach(file => {
|
||||
let options: ITextEditorOptions | undefined = undefined;
|
||||
|
||||
// Try to find editor view state from the visible editors that match given resource
|
||||
let viewState: IEditorViewState | undefined = undefined;
|
||||
const textEditorControls = editorService.visibleTextEditorControls;
|
||||
for (const textEditorControl of textEditorControls) {
|
||||
if (isCodeEditor(textEditorControl)) {
|
||||
const model = textEditorControl.getModel();
|
||||
if (model?.uri?.toString() === file.resource.toString()) {
|
||||
viewState = withNullAsUndefined(textEditorControl.saveViewState());
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Use provided callback for editor options
|
||||
if (typeof optionsCallback === 'function') {
|
||||
options = optionsCallback(file.resource);
|
||||
}
|
||||
|
||||
// Otherwise try to figure out the view state from opened editors that match
|
||||
else {
|
||||
options = {
|
||||
viewState: (() => {
|
||||
const textEditorControls = editorService.visibleTextEditorControls;
|
||||
for (const textEditorControl of textEditorControls) {
|
||||
if (isCodeEditor(textEditorControl)) {
|
||||
const model = textEditorControl.getModel();
|
||||
if (model?.uri?.toString() === file.resource.toString()) {
|
||||
return withNullAsUndefined(textEditorControl.saveViewState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
})()
|
||||
};
|
||||
}
|
||||
|
||||
// Try to find encoding and mode from text model
|
||||
@@ -378,7 +390,7 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
|
||||
}
|
||||
|
||||
// Add as dragged editor
|
||||
draggedEditors.push({ resource: file.resource.toString(), content, viewState, encoding, mode });
|
||||
draggedEditors.push({ resource: file.resource.toString(), content, options, encoding, mode });
|
||||
});
|
||||
|
||||
if (draggedEditors.length) {
|
||||
|
||||
@@ -42,8 +42,21 @@ function toResource(props: IResourceLabelProps | undefined): URI | undefined {
|
||||
}
|
||||
|
||||
export interface IResourceLabelOptions extends IIconLabelValueOptions {
|
||||
|
||||
/**
|
||||
* A hint to the file kind of the resource.
|
||||
*/
|
||||
fileKind?: FileKind;
|
||||
|
||||
/**
|
||||
* File decorations to use for the label.
|
||||
*/
|
||||
fileDecorations?: { colors: boolean, badges: boolean };
|
||||
|
||||
/**
|
||||
* Will take the provided label as is and e.g. not override it for untitled files.
|
||||
*/
|
||||
forceLabel?: boolean;
|
||||
}
|
||||
|
||||
export interface IFileLabelOptions extends IResourceLabelOptions {
|
||||
@@ -368,7 +381,7 @@ class ResourceLabelWidget extends IconLabel {
|
||||
/*const resource = toResource(label); {{SQL CARBON EDIT}} we don't want to special case untitled files
|
||||
const isMasterDetail = label?.resource && !URI.isUri(label.resource);
|
||||
|
||||
if (!isMasterDetail && resource?.scheme === Schemas.untitled) {
|
||||
if (!options.forceLabel && !isMasterDetail && resource?.scheme === Schemas.untitled) {
|
||||
// Untitled labels are very dynamic because they may change
|
||||
// whenever the content changes (unless a path is associated).
|
||||
// As such we always ask the actual editor for it's name and
|
||||
@@ -528,7 +541,6 @@ class ResourceLabelWidget extends IconLabel {
|
||||
);
|
||||
|
||||
if (deco) {
|
||||
|
||||
this.renderDisposables.add(deco);
|
||||
|
||||
if (deco.tooltip) {
|
||||
|
||||
@@ -177,6 +177,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
private backupFileService!: IBackupFileService;
|
||||
private notificationService!: INotificationService;
|
||||
private themeService!: IThemeService;
|
||||
private activityBarService!: IActivityBarService;
|
||||
|
||||
protected readonly state = {
|
||||
fullscreen: false,
|
||||
@@ -260,8 +261,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
this.viewDescriptorService = accessor.get(IViewDescriptorService);
|
||||
this.titleService = accessor.get(ITitleService);
|
||||
this.notificationService = accessor.get(INotificationService);
|
||||
this.activityBarService = accessor.get(IActivityBarService);
|
||||
accessor.get(IStatusbarService); // not used, but called to ensure instantiated
|
||||
accessor.get(IActivityBarService); // not used, but called to ensure instantiated
|
||||
|
||||
// Listeners
|
||||
this.registerLayoutListeners();
|
||||
@@ -846,6 +847,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
activeViewlet.focus();
|
||||
}
|
||||
break;
|
||||
case Parts.ACTIVITYBAR_PART:
|
||||
this.activityBarService.focusActivityBar();
|
||||
break;
|
||||
default:
|
||||
// Status Bar, Activity Bar and Title Bar simply pass focus to container
|
||||
const container = this.getContainer(part);
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
.monaco-workbench .part > .drop-block-overlay.visible {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench .part > .drop-block-overlay {
|
||||
@@ -18,10 +17,8 @@
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
backdrop-filter: brightness(97%) blur(2px);
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity .5s, visibility .5s;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
|
||||
@@ -168,6 +168,10 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
focusActivityBar(): void {
|
||||
this.compositeBar.focus();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// View Container Changes
|
||||
|
||||
@@ -66,8 +66,6 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
||||
if (targetCompositeId) {
|
||||
this.moveComposite(currentContainer.id, targetCompositeId, before);
|
||||
}
|
||||
|
||||
this.openComposite(currentContainer.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,6 +253,12 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
return actionBarDiv;
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
if (this.compositeSwitcherBar) {
|
||||
this.compositeSwitcherBar.focus();
|
||||
}
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
this.dimension = dimension;
|
||||
if (dimension.height === 0 || dimension.width === 0) {
|
||||
@@ -275,8 +279,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
// Add to the model
|
||||
if (this.model.add(id, name, order)) {
|
||||
this.computeSizes([this.model.findItem(id)]);
|
||||
// Set timeout helps prevent flicker
|
||||
setTimeout(() => this.updateCompositeSwitcher(), 0);
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -548,11 +548,8 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
if (e.eventData.dataTransfer) {
|
||||
e.eventData.dataTransfer.effectAllowed = 'move';
|
||||
}
|
||||
|
||||
// Trigger the action even on drag start to prevent clicks from failing that started a drag
|
||||
if (!this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
// Remove focus indicator when dragging
|
||||
this.blur();
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { MementoObject } from 'vs/workbench/common/memento';
|
||||
import { isEqualOrParent, joinPath } from 'vs/base/common/resources';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { indexOfPath } from 'vs/base/common/extpath';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
/**
|
||||
* The base class of editors in the workbench. Editors register themselves for specific editor inputs.
|
||||
@@ -171,6 +172,7 @@ interface MapGroupToMemento<T> {
|
||||
export class EditorMemento<T> implements IEditorMemento<T> {
|
||||
private cache: LRUCache<string, MapGroupToMemento<T>> | undefined;
|
||||
private cleanedUp = false;
|
||||
private editorDisposables: Map<EditorInput, IDisposable> | undefined;
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
@@ -200,9 +202,18 @@ export class EditorMemento<T> implements IEditorMemento<T> {
|
||||
|
||||
// Automatically clear when editor input gets disposed if any
|
||||
if (resourceOrEditor instanceof EditorInput) {
|
||||
Event.once(resourceOrEditor.onDispose)(() => {
|
||||
this.clearEditorState(resource);
|
||||
});
|
||||
const editor = resourceOrEditor;
|
||||
|
||||
if (!this.editorDisposables) {
|
||||
this.editorDisposables = new Map<EditorInput, IDisposable>();
|
||||
}
|
||||
|
||||
if (!this.editorDisposables.has(editor)) {
|
||||
this.editorDisposables.set(editor, Event.once(resourceOrEditor.onDispose)(() => {
|
||||
this.clearEditorState(resource);
|
||||
this.editorDisposables?.delete(editor);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import * as nls from 'vs/nls';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||
import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, EditorPinnedContext, EditorGroupEditorsCountContext } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, EditorPinnedContext, EditorGroupEditorsCountContext, EditorStickyContext } from 'vs/workbench/common/editor';
|
||||
import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
|
||||
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
@@ -433,6 +433,8 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCo
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '1_close', order: 40 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '1_close', order: 50 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepOpen', "Keep Open"), precondition: EditorPinnedContext.toNegated() }, group: '3_preview', order: 10, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.PIN_EDITOR_COMMAND_ID, title: nls.localize('pin', "Pin") }, group: '3_preview', order: 20, when: ContextKeyExpr.and(EditorStickyContext.toNegated(), ContextKeyExpr.has('config.workbench.editor.showTabs')) });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.UNPIN_EDITOR_COMMAND_ID, title: nls.localize('unpin', "Unpin") }, group: '3_preview', order: 20, when: ContextKeyExpr.and(EditorStickyContext, ContextKeyExpr.has('config.workbench.editor.showTabs')) });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_UP, title: nls.localize('splitUp', "Split Up") }, group: '5_split', order: 10 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitDown', "Split Down") }, group: '5_split', order: 20 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_LEFT, title: nls.localize('splitLeft', "Split Left") }, group: '5_split', order: 30 });
|
||||
@@ -579,6 +581,8 @@ appendEditorToolItem(
|
||||
// Editor Commands for Command Palette
|
||||
const viewCategory = { value: nls.localize('view', "View"), original: 'View' };
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: { value: nls.localize('keepEditor', "Keep Editor"), original: 'Keep Editor' }, category: viewCategory }, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.PIN_EDITOR_COMMAND_ID, title: { value: nls.localize('pinEditor', "Pin Editor"), original: 'Pin Editor' }, category: viewCategory }, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.UNPIN_EDITOR_COMMAND_ID, title: { value: nls.localize('unpinEditor', "Unpin Editor"), original: 'Unpin Editor' }, category: viewCategory }, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: { value: nls.localize('closeEditorsInGroup', "Close All Editors in Group"), original: 'Close All Editors in Group' }, category: viewCategory } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: { value: nls.localize('closeSavedEditors', "Close Saved Editors in Group"), original: 'Close Saved Editors in Group' }, category: viewCategory } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: { value: nls.localize('closeOtherEditors', "Close Other Editors in Group"), original: 'Close Other Editors in Group' }, category: viewCategory } });
|
||||
|
||||
@@ -500,7 +500,7 @@ export class CloseLeftEditorsInGroupAction extends Action {
|
||||
async run(context?: IEditorIdentifier): Promise<void> {
|
||||
const { group, editor } = getTarget(this.editorService, this.editorGroupService, context);
|
||||
if (group && editor) {
|
||||
return group.closeEditors({ direction: CloseDirection.LEFT, except: editor });
|
||||
return group.closeEditors({ direction: CloseDirection.LEFT, except: editor, excludeSticky: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -514,7 +514,7 @@ function getTarget(editorService: IEditorService, editorGroupService: IEditorGro
|
||||
return { group: editorGroupService.activeGroup, editor: editorGroupService.activeGroup.activeEditor };
|
||||
}
|
||||
|
||||
export abstract class BaseCloseAllAction extends Action {
|
||||
abstract class BaseCloseAllAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -554,7 +554,7 @@ export abstract class BaseCloseAllAction extends Action {
|
||||
// to bring each dirty editor to the front so that the user
|
||||
// can review if the files should be changed or not.
|
||||
await Promise.all(this.groupsToClose.map(async groupToClose => {
|
||||
for (const editor of groupToClose.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) {
|
||||
for (const editor of groupToClose.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, { excludeSticky: this.excludeSticky })) {
|
||||
if (editor.isDirty() && !editor.isSaving() /* ignore editors that are being saved */) {
|
||||
return groupToClose.openEditor(editor);
|
||||
}
|
||||
@@ -566,7 +566,7 @@ export abstract class BaseCloseAllAction extends Action {
|
||||
const dirtyEditorsToConfirm = new Set<string>();
|
||||
const dirtyEditorsToAutoSave = new Set<IEditorInput>();
|
||||
|
||||
for (const editor of this.editorService.editors) {
|
||||
for (const editor of this.editorService.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: this.excludeSticky }).map(({ editor }) => editor)) {
|
||||
if (!editor.isDirty() || editor.isSaving()) {
|
||||
continue; // only interested in dirty editors (unless in the process of saving)
|
||||
}
|
||||
@@ -601,21 +601,29 @@ export abstract class BaseCloseAllAction extends Action {
|
||||
confirmation = ConfirmResult.DONT_SAVE;
|
||||
}
|
||||
|
||||
if (confirmation === ConfirmResult.CANCEL) {
|
||||
return;
|
||||
// Handle result from asking user
|
||||
let result: boolean | undefined = undefined;
|
||||
switch (confirmation) {
|
||||
case ConfirmResult.CANCEL:
|
||||
return;
|
||||
case ConfirmResult.DONT_SAVE:
|
||||
result = await this.editorService.revertAll({ soft: true, includeUntitled: true, excludeSticky: this.excludeSticky });
|
||||
break;
|
||||
case ConfirmResult.SAVE:
|
||||
result = await this.editorService.saveAll({ reason: saveReason, includeUntitled: true, excludeSticky: this.excludeSticky });
|
||||
break;
|
||||
}
|
||||
|
||||
if (confirmation === ConfirmResult.DONT_SAVE) {
|
||||
await this.editorService.revertAll({ soft: true, includeUntitled: true });
|
||||
} else {
|
||||
await this.editorService.saveAll({ reason: saveReason, includeUntitled: true });
|
||||
}
|
||||
|
||||
if (!this.workingCopyService.hasDirty) {
|
||||
// Only continue to close editors if we either have no more dirty
|
||||
// editors or the result from the save/revert was successful
|
||||
if (!this.workingCopyService.hasDirty || result) {
|
||||
return this.doCloseAll();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract get excludeSticky(): boolean;
|
||||
|
||||
protected abstract doCloseAll(): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -636,8 +644,12 @@ export class CloseAllEditorsAction extends BaseCloseAllAction {
|
||||
super(id, label, Codicon.closeAll.classNames, workingCopyService, fileDialogService, editorGroupService, editorService, filesConfigurationService);
|
||||
}
|
||||
|
||||
protected get excludeSticky(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async doCloseAll(): Promise<void> {
|
||||
await Promise.all(this.groupsToClose.map(g => g.closeAllEditors()));
|
||||
await Promise.all(this.groupsToClose.map(group => group.closeAllEditors({ excludeSticky: true })));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,6 +670,10 @@ export class CloseAllEditorGroupsAction extends BaseCloseAllAction {
|
||||
super(id, label, undefined, workingCopyService, fileDialogService, editorGroupService, editorService, filesConfigurationService);
|
||||
}
|
||||
|
||||
protected get excludeSticky(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected async doCloseAll(): Promise<void> {
|
||||
await Promise.all(this.groupsToClose.map(group => group.closeAllEditors()));
|
||||
|
||||
@@ -680,12 +696,12 @@ export class CloseEditorsInOtherGroupsAction extends Action {
|
||||
|
||||
async run(context?: IEditorIdentifier): Promise<void> {
|
||||
const groupToSkip = context ? this.editorGroupService.getGroup(context.groupId) : this.editorGroupService.activeGroup;
|
||||
await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(async g => {
|
||||
if (groupToSkip && g.id === groupToSkip.id) {
|
||||
await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(async group => {
|
||||
if (groupToSkip && group.id === groupToSkip.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
return g.closeAllEditors();
|
||||
return group.closeAllEditors({ excludeSticky: true });
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -707,7 +723,7 @@ export class CloseEditorInAllGroupsAction extends Action {
|
||||
async run(): Promise<void> {
|
||||
const activeEditor = this.editorService.activeEditor;
|
||||
if (activeEditor) {
|
||||
await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => g.closeEditor(activeEditor)));
|
||||
await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(group => group.closeEditor(activeEditor)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditorInput, IVisibleEditorPane } from 'vs/workbench/common/editor';
|
||||
import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditorInput, IVisibleEditorPane, EditorStickyContext, EditorsOrder } from 'vs/workbench/common/editor';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
|
||||
@@ -37,6 +37,9 @@ export const LAYOUT_EDITOR_GROUPS_COMMAND_ID = 'layoutEditorGroups';
|
||||
export const KEEP_EDITOR_COMMAND_ID = 'workbench.action.keepEditor';
|
||||
export const SHOW_EDITORS_IN_GROUP = 'workbench.action.showEditorsInGroup';
|
||||
|
||||
export const PIN_EDITOR_COMMAND_ID = 'workbench.action.pinEditor';
|
||||
export const UNPIN_EDITOR_COMMAND_ID = 'workbench.action.unpinEditor';
|
||||
|
||||
export const TOGGLE_DIFF_SIDE_BY_SIDE = 'toggle.diff.renderSideBySide';
|
||||
export const GOTO_NEXT_CHANGE = 'workbench.action.compareEditor.nextChange';
|
||||
export const GOTO_PREVIOUS_CHANGE = 'workbench.action.compareEditor.previousChange';
|
||||
@@ -258,7 +261,7 @@ function registerDiffEditorCommands(): void {
|
||||
|
||||
function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const candidates = [editorService.activeEditorPane, ...editorService.visibleEditorPanes].filter(e => e instanceof TextDiffEditor);
|
||||
const candidates = [editorService.activeEditorPane, ...editorService.visibleEditorPanes].filter(editor => editor instanceof TextDiffEditor);
|
||||
|
||||
if (candidates.length > 0) {
|
||||
const navigator = (<TextDiffEditor>candidates[0]).getDiffNavigator();
|
||||
@@ -491,7 +494,7 @@ function registerCloseEditorCommands() {
|
||||
return Promise.all(distinct(contexts.map(c => c.groupId)).map(async groupId => {
|
||||
const group = editorGroupService.getGroup(groupId);
|
||||
if (group) {
|
||||
return group.closeEditors({ savedOnly: true });
|
||||
return group.closeEditors({ savedOnly: true, excludeSticky: true });
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -514,7 +517,7 @@ function registerCloseEditorCommands() {
|
||||
return Promise.all(distinctGroupIds.map(async groupId => {
|
||||
const group = editorGroupService.getGroup(groupId);
|
||||
if (group) {
|
||||
return group.closeAllEditors();
|
||||
return group.closeAllEditors({ excludeSticky: true });
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -596,7 +599,8 @@ function registerCloseEditorCommands() {
|
||||
const editors = contexts
|
||||
.filter(context => context.groupId === groupId)
|
||||
.map(context => typeof context.editorIndex === 'number' ? group.getEditorByIndex(context.editorIndex) : group.activeEditor);
|
||||
const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1);
|
||||
|
||||
const editorsToClose = group.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: true }).filter(editor => editors.indexOf(editor) === -1);
|
||||
|
||||
if (group.activeEditor) {
|
||||
group.pinEditor(group.activeEditor);
|
||||
@@ -622,7 +626,7 @@ function registerCloseEditorCommands() {
|
||||
group.pinEditor(group.activeEditor);
|
||||
}
|
||||
|
||||
return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
|
||||
return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor, excludeSticky: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -642,6 +646,36 @@ function registerCloseEditorCommands() {
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: PIN_EDITOR_COMMAND_ID,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(EditorStickyContext.toNegated(), ContextKeyExpr.has('config.workbench.editor.showTabs')),
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.Enter),
|
||||
handler: async (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
|
||||
const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
|
||||
if (group && editor) {
|
||||
return group.stickEditor(editor);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: UNPIN_EDITOR_COMMAND_ID,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(EditorStickyContext, ContextKeyExpr.has('config.workbench.editor.showTabs')),
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.Enter),
|
||||
handler: async (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
|
||||
const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
|
||||
if (group && editor) {
|
||||
return group.unstickEditor(editor);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: SHOW_EDITORS_IN_GROUP,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
|
||||
@@ -266,7 +266,10 @@ class DropOverlay extends Themable {
|
||||
}
|
||||
|
||||
// Open in target group
|
||||
const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true }));
|
||||
const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({
|
||||
pinned: true, // always pin dropped editor
|
||||
sticky: sourceGroup.isSticky(draggedEditor.editor) // preserve sticky state
|
||||
}));
|
||||
targetGroup.openEditor(draggedEditor.editor, options);
|
||||
|
||||
// Ensure target has focus
|
||||
|
||||
@@ -17,7 +17,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService';
|
||||
import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER, EDITOR_GROUP_HEADER_BORDER } from 'vs/workbench/common/theme';
|
||||
import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl';
|
||||
import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl';
|
||||
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||
@@ -441,6 +441,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}
|
||||
|
||||
options.pinned = this._group.isPinned(activeEditor); // preserve pinned state
|
||||
options.sticky = this._group.isSticky(activeEditor); // preserve sticky state
|
||||
options.preserveFocus = true; // handle focus after editor is opened
|
||||
|
||||
const activeElement = document.activeElement;
|
||||
@@ -732,6 +733,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
return this._group.count;
|
||||
}
|
||||
|
||||
get stickyCount(): number {
|
||||
return this._group.stickyCount;
|
||||
}
|
||||
|
||||
get activeEditorPane(): IVisibleEditorPane | undefined {
|
||||
return this.editorControl ? withNullAsUndefined(this.editorControl.activeEditorPane) : undefined;
|
||||
}
|
||||
@@ -748,12 +753,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
return this._group.isPinned(editor);
|
||||
}
|
||||
|
||||
isSticky(editorOrIndex: EditorInput | number): boolean {
|
||||
return this._group.isSticky(editorOrIndex);
|
||||
}
|
||||
|
||||
isActive(editor: EditorInput): boolean {
|
||||
return this._group.isActive(editor);
|
||||
}
|
||||
|
||||
getEditors(order: EditorsOrder): EditorInput[] {
|
||||
return this._group.getEditors(order);
|
||||
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): EditorInput[] {
|
||||
return this._group.getEditors(order, options);
|
||||
}
|
||||
|
||||
getEditorByIndex(index: number): EditorInput | undefined {
|
||||
@@ -794,6 +803,43 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}
|
||||
}
|
||||
|
||||
stickEditor(candidate: EditorInput | undefined = this.activeEditor || undefined): void {
|
||||
this.doStickEditor(candidate, true);
|
||||
}
|
||||
|
||||
unstickEditor(candidate: EditorInput | undefined = this.activeEditor || undefined): void {
|
||||
this.doStickEditor(candidate, false);
|
||||
}
|
||||
|
||||
private doStickEditor(candidate: EditorInput | undefined, sticky: boolean): void {
|
||||
if (candidate && this._group.isSticky(candidate) !== sticky) {
|
||||
const oldIndexOfEditor = this.getIndexOfEditor(candidate);
|
||||
|
||||
// Update model
|
||||
const editor = sticky ? this._group.stick(candidate) : this._group.unstick(candidate);
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the index of the editor changed, we need to forward this to
|
||||
// title control and also make sure to emit this as an event
|
||||
const newIndexOfEditor = this.getIndexOfEditor(editor);
|
||||
if (newIndexOfEditor !== oldIndexOfEditor) {
|
||||
this.titleAreaControl.moveEditor(editor, oldIndexOfEditor, newIndexOfEditor);
|
||||
|
||||
// Event
|
||||
this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_MOVE, editor });
|
||||
}
|
||||
|
||||
// Forward sticky state to title control
|
||||
if (sticky) {
|
||||
this.titleAreaControl.stickEditor(editor);
|
||||
} else {
|
||||
this.titleAreaControl.unstickEditor(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
invokeWithinContext<T>(fn: (accessor: ServicesAccessor) => T): T {
|
||||
return this.scopedInstantiationService.invokeFunction(fn);
|
||||
}
|
||||
@@ -833,11 +879,19 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
// Determine options
|
||||
const openEditorOptions: IEditorOpenOptions = {
|
||||
index: options ? options.index : undefined,
|
||||
pinned: !this.accessor.partOptions.enablePreview || editor.isDirty() || (options?.pinned ?? typeof options?.index === 'number'), // unless specified, prefer to pin when opening with index
|
||||
pinned: options?.sticky || !this.accessor.partOptions.enablePreview || editor.isDirty() || (options?.pinned ?? typeof options?.index === 'number' /* unless specified, prefer to pin when opening with index */) || (typeof options?.index === 'number' && this._group.isSticky(options.index)),
|
||||
sticky: options?.sticky || (typeof options?.index === 'number' && this._group.isSticky(options.index)),
|
||||
active: this._group.count === 0 || !options || !options.inactive
|
||||
};
|
||||
|
||||
if (!openEditorOptions.active && !openEditorOptions.pinned && this._group.activeEditor && this._group.isPreview(this._group.activeEditor)) {
|
||||
if (options?.sticky && typeof options?.index === 'number' && !this._group.isSticky(options.index)) {
|
||||
// Special case: we are to open an editor sticky but at an index that is not sticky
|
||||
// In that case we prefer to open the editor at the index but not sticky. This enables
|
||||
// to drag a sticky editor to an index that is not sticky to unstick it.
|
||||
openEditorOptions.sticky = false;
|
||||
}
|
||||
|
||||
if (!openEditorOptions.active && !openEditorOptions.pinned && this._group.activeEditor && !this._group.isPinned(this._group.activeEditor)) {
|
||||
// Special case: we are to open an editor inactive and not pinned, but the current active
|
||||
// editor is also not pinned, which means it will get replaced with this one. As such,
|
||||
// the editor can only be active.
|
||||
@@ -1095,8 +1149,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
// When moving an editor, try to preserve as much view state as possible by checking
|
||||
// for the editor to be a text editor and creating the options accordingly if so
|
||||
const options = getActiveTextEditorOptions(this, editor, EditorOptions.create(moveOptions));
|
||||
options.pinned = true; // always pin moved editor
|
||||
const options = getActiveTextEditorOptions(this, editor, EditorOptions.create({
|
||||
...moveOptions,
|
||||
pinned: true, // always pin moved editor
|
||||
sticky: this._group.isSticky(editor) // preserve sticky state
|
||||
}));
|
||||
|
||||
// A move to another group is an open first...
|
||||
target.openEditor(editor, options);
|
||||
@@ -1377,7 +1434,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
return;
|
||||
}
|
||||
|
||||
const editors = this.getEditorsToClose(args);
|
||||
const editors = this.doGetEditorsToClose(args);
|
||||
|
||||
// Check for dirty and veto
|
||||
const veto = await this.handleDirtyClosing(editors.slice(0));
|
||||
@@ -1389,31 +1446,31 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
this.doCloseEditors(editors, options);
|
||||
}
|
||||
|
||||
private getEditorsToClose(editors: EditorInput[] | ICloseEditorsFilter): EditorInput[] {
|
||||
if (Array.isArray(editors)) {
|
||||
return editors;
|
||||
private doGetEditorsToClose(args: EditorInput[] | ICloseEditorsFilter): EditorInput[] {
|
||||
if (Array.isArray(args)) {
|
||||
return args;
|
||||
}
|
||||
|
||||
const filter = editors;
|
||||
const filter = args;
|
||||
const hasDirection = typeof filter.direction === 'number';
|
||||
|
||||
let editorsToClose = this._group.getEditors(hasDirection ? EditorsOrder.SEQUENTIAL : EditorsOrder.MOST_RECENTLY_ACTIVE); // in MRU order only if direction is not specified
|
||||
let editorsToClose = this._group.getEditors(hasDirection ? EditorsOrder.SEQUENTIAL : EditorsOrder.MOST_RECENTLY_ACTIVE, filter); // in MRU order only if direction is not specified
|
||||
|
||||
// Filter: saved or saving only
|
||||
if (filter.savedOnly) {
|
||||
editorsToClose = editorsToClose.filter(e => !e.isDirty() || e.isSaving());
|
||||
editorsToClose = editorsToClose.filter(editor => !editor.isDirty() || editor.isSaving());
|
||||
}
|
||||
|
||||
// Filter: direction (left / right)
|
||||
else if (hasDirection && filter.except) {
|
||||
editorsToClose = (filter.direction === CloseDirection.LEFT) ?
|
||||
editorsToClose.slice(0, this._group.indexOf(filter.except)) :
|
||||
editorsToClose.slice(this._group.indexOf(filter.except) + 1);
|
||||
editorsToClose.slice(0, this._group.indexOf(filter.except, editorsToClose)) :
|
||||
editorsToClose.slice(this._group.indexOf(filter.except, editorsToClose) + 1);
|
||||
}
|
||||
|
||||
// Filter: except
|
||||
else if (filter.except) {
|
||||
editorsToClose = editorsToClose.filter(e => !e.matches(filter.except));
|
||||
editorsToClose = editorsToClose.filter(editor => !editor.matches(filter.except));
|
||||
}
|
||||
|
||||
return editorsToClose;
|
||||
@@ -1444,7 +1501,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
//#region closeAllEditors()
|
||||
|
||||
async closeAllEditors(): Promise<void> {
|
||||
async closeAllEditors(options?: ICloseAllEditorsOptions): Promise<void> {
|
||||
if (this.isEmpty) {
|
||||
|
||||
// If the group is empty and the request is to close all editors, we still close
|
||||
@@ -1458,30 +1515,34 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}
|
||||
|
||||
// Check for dirty and veto
|
||||
const editors = this._group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE);
|
||||
const veto = await this.handleDirtyClosing(editors.slice(0));
|
||||
const veto = await this.handleDirtyClosing(this._group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, options));
|
||||
if (veto) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do close
|
||||
this.doCloseAllEditors();
|
||||
this.doCloseAllEditors(options);
|
||||
}
|
||||
|
||||
private doCloseAllEditors(): void {
|
||||
private doCloseAllEditors(options?: ICloseAllEditorsOptions): void {
|
||||
|
||||
// Close all inactive editors first
|
||||
this.editors.forEach(editor => {
|
||||
const editorsToClose: EditorInput[] = [];
|
||||
this._group.getEditors(EditorsOrder.SEQUENTIAL, options).forEach(editor => {
|
||||
if (!this.isActive(editor)) {
|
||||
this.doCloseInactiveEditor(editor);
|
||||
}
|
||||
|
||||
editorsToClose.push(editor);
|
||||
});
|
||||
|
||||
// Close active editor last
|
||||
this.doCloseActiveEditor();
|
||||
// Close active editor last (unless we skip it, e.g. because it is sticky)
|
||||
if (this.activeEditor && editorsToClose.includes(this.activeEditor)) {
|
||||
this.doCloseActiveEditor();
|
||||
}
|
||||
|
||||
// Forward to title control
|
||||
this.titleAreaControl.closeAllEditors();
|
||||
this.titleAreaControl.closeEditors(editorsToClose);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -48,7 +48,7 @@ export class EditorsObserver extends Disposable {
|
||||
}
|
||||
|
||||
get editors(): IEditorIdentifier[] {
|
||||
return [...this.mostRecentEditorsMap.values()];
|
||||
return this.mostRecentEditorsMap.values();
|
||||
}
|
||||
|
||||
hasEditor(resource: URI): boolean {
|
||||
@@ -283,7 +283,7 @@ export class EditorsObserver extends Disposable {
|
||||
|
||||
// Across all editor groups
|
||||
else {
|
||||
await this.doEnsureOpenedEditorsLimit(limit, [...this.mostRecentEditorsMap.values()], exclude);
|
||||
await this.doEnsureOpenedEditorsLimit(limit, this.mostRecentEditorsMap.values(), exclude);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,6 +302,10 @@ export class EditorsObserver extends Disposable {
|
||||
return false; // never the editor that should be excluded
|
||||
}
|
||||
|
||||
if (this.editorGroupsService.getGroup(groupId)?.isSticky(editor)) {
|
||||
return false; // never sticky editors
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -342,7 +346,7 @@ export class EditorsObserver extends Disposable {
|
||||
private serialize(): ISerializedEditorsList {
|
||||
const registry = Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories);
|
||||
|
||||
const entries = [...this.mostRecentEditorsMap.values()];
|
||||
const entries = this.mostRecentEditorsMap.values();
|
||||
const mapGroupToSerializableEditorsOfGroup = new Map<IEditorGroup, IEditorInput[]>();
|
||||
|
||||
return {
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-right,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-off {
|
||||
padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab close button is not left */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-off:not(.sticky) {
|
||||
padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab close button is not left (unless sticky) */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit {
|
||||
@@ -82,6 +82,30 @@
|
||||
max-width: -moz-fit-content;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit.sticky,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.sticky {
|
||||
|
||||
/** Sticky tabs do not scroll in case of overflow and are always above unsticky tabs which scroll under */
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
|
||||
/** Sticky tabs are even and never grow */
|
||||
flex-basis: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
/** Sticky tabs have a fixed width of 38px */
|
||||
width: 38px;
|
||||
min-width: 38px;
|
||||
max-width: 38px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-fit.sticky,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky {
|
||||
|
||||
/** Disable sticky positions for sticky tabs if the available space is too little */
|
||||
position: static;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-left .action-label {
|
||||
margin-right: 4px !important;
|
||||
}
|
||||
@@ -174,6 +198,10 @@
|
||||
opacity: 0; /* when tab has the focus this shade breaks the tab border (fixes https://github.com/Microsoft/vscode/issues/57819) */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky:not(.has-icon-theme) .monaco-icon-label {
|
||||
text-align: center; /* ensure that sticky tabs without icon have label centered */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label > .monaco-icon-label-container {
|
||||
overflow: visible; /* fixes https://github.com/Microsoft/vscode/issues/20182 */
|
||||
@@ -210,14 +238,15 @@
|
||||
overflow: visible; /* ...but still show the close button on hover, focus and when dirty */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off:not(.dirty) > .tab-close {
|
||||
display: none; /* hide the close action bar when we are configured to hide it */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off:not(.dirty) > .tab-close,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.sticky > .tab-close {
|
||||
display: none; /* hide the close action bar when we are configured to hide it (unless dirty, but always when sticky) */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active > .tab-close .action-label, /* always show it for active tab */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-close .action-label:focus, /* always show it on focus */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab:hover > .tab-close .action-label, /* always show it on hover */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* always show it on hover */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* always show it on hover */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-close .action-label { /* always show it for dirty tabs */
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -233,10 +262,10 @@
|
||||
content: "\ea71"; /* use `circle-filled` icon unicode */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-close .action-label, /* show dimmed for inactive group */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-close .action-label, /* show dimmed for inactive group */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* show dimmed for inactive group */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-close .action-label, /* show dimmed for inactive group */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-close .action-label { /* show dimmed for inactive group */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-close .action-label { /* show dimmed for inactive group */
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -257,8 +286,8 @@
|
||||
padding-right: 10px; /* give a little bit more room if close button is off */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-off {
|
||||
padding-right: 5px; /* we need less room when sizing is shrink */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-off:not(.sticky) {
|
||||
padding-right: 5px; /* we need less room when sizing is shrink (unless tab is sticky) */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close,
|
||||
|
||||
@@ -136,10 +136,6 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
this.ifActiveEditorChanged(() => this.redraw());
|
||||
}
|
||||
|
||||
closeAllEditors(): void {
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
moveEditor(editor: IEditorInput, fromIndex: number, targetIndex: number): void {
|
||||
this.ifActiveEditorChanged(() => this.redraw());
|
||||
}
|
||||
@@ -148,6 +144,14 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
this.ifEditorIsActive(editor, () => this.redraw());
|
||||
}
|
||||
|
||||
stickEditor(editor: IEditorInput): void {
|
||||
// Sticky editors are not presented any different with tabs disabled
|
||||
}
|
||||
|
||||
unstickEditor(editor: IEditorInput): void {
|
||||
// Sticky editors are not presented any different with tabs disabled
|
||||
}
|
||||
|
||||
setActive(isActive: boolean): void {
|
||||
this.redraw();
|
||||
}
|
||||
@@ -219,7 +223,6 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ifEditorIsActive(editor: IEditorInput, fn: () => void): void {
|
||||
if (this.group.isActive(editor)) {
|
||||
fn(); // only run if editor is current active
|
||||
|
||||
@@ -27,7 +27,7 @@ import { getOrSet } from 'vs/base/common/map';
|
||||
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
|
||||
import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ResourcesDropHandler, fillResourceDataTransfers, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||
import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
@@ -67,6 +67,11 @@ export class TabsTitleControl extends TitleControl {
|
||||
large: 10
|
||||
};
|
||||
|
||||
private static readonly TAB_SIZES = {
|
||||
sticky: 38,
|
||||
fit: 120
|
||||
};
|
||||
|
||||
private titleContainer: HTMLElement | undefined;
|
||||
private tabsAndActionsContainer: HTMLElement | undefined;
|
||||
private tabsContainer: HTMLElement | undefined;
|
||||
@@ -392,10 +397,6 @@ export class TabsTitleControl extends TitleControl {
|
||||
this.handleClosedEditors();
|
||||
}
|
||||
|
||||
closeAllEditors(): void {
|
||||
this.handleClosedEditors();
|
||||
}
|
||||
|
||||
private handleClosedEditors(): void {
|
||||
|
||||
// There are tabs to show
|
||||
@@ -453,7 +454,24 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
|
||||
pinEditor(editor: IEditorInput): void {
|
||||
this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel) => this.redrawLabel(editor, tabContainer, tabLabelWidget, tabLabel));
|
||||
this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel) => this.redrawLabel(editor, index, tabContainer, tabLabelWidget, tabLabel));
|
||||
}
|
||||
|
||||
stickEditor(editor: IEditorInput): void {
|
||||
this.doHandleStickyEditorChange(editor);
|
||||
}
|
||||
|
||||
unstickEditor(editor: IEditorInput): void {
|
||||
this.doHandleStickyEditorChange(editor);
|
||||
}
|
||||
|
||||
private doHandleStickyEditorChange(editor: IEditorInput): void {
|
||||
|
||||
// Update tab
|
||||
this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel) => this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel));
|
||||
|
||||
// A change to the sticky state requires a layout to keep the active editor visible
|
||||
this.layout(this.dimension);
|
||||
}
|
||||
|
||||
setActive(isGroupActive: boolean): void {
|
||||
@@ -487,7 +505,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
// As such we need to redraw each label
|
||||
this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel) => {
|
||||
this.redrawLabel(editor, tabContainer, tabLabelWidget, tabLabel);
|
||||
this.redrawLabel(editor, index, tabContainer, tabLabelWidget, tabLabel);
|
||||
});
|
||||
|
||||
// A change to a label requires a layout to keep the active editor visible
|
||||
@@ -745,10 +763,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
|
||||
// Apply some datatransfer types to allow for dragging the element outside of the application
|
||||
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
if (resource) {
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e);
|
||||
}
|
||||
this.doFillResourceDataTransfers(editor, e);
|
||||
|
||||
// Fixes https://github.com/Microsoft/vscode/issues/18733
|
||||
addClass(tab, 'dragged');
|
||||
@@ -1010,7 +1025,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
private redrawTab(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel): void {
|
||||
|
||||
// Label
|
||||
this.redrawLabel(editor, tabContainer, tabLabelWidget, tabLabel);
|
||||
this.redrawLabel(editor, index, tabContainer, tabLabelWidget, tabLabel);
|
||||
|
||||
// Borders / Outline
|
||||
const borderRightColor = (this.getColor(TAB_BORDER) || this.getColor(contrastBorder));
|
||||
@@ -1018,10 +1033,12 @@ export class TabsTitleControl extends TitleControl {
|
||||
tabContainer.style.outlineColor = this.getColor(activeContrastBorder) || '';
|
||||
|
||||
// Settings
|
||||
const isTabSticky = this.group.isSticky(index);
|
||||
const options = this.accessor.partOptions;
|
||||
|
||||
const tabCloseButton = isTabSticky ? 'off' /* treat sticky tabs as tabCloseButton: 'off' */ : options.tabCloseButton;
|
||||
['off', 'left', 'right'].forEach(option => {
|
||||
const domAction = options.tabCloseButton === option ? addClass : removeClass;
|
||||
const domAction = tabCloseButton === option ? addClass : removeClass;
|
||||
domAction(tabContainer, `close-button-${option}`);
|
||||
});
|
||||
|
||||
@@ -1036,13 +1053,37 @@ export class TabsTitleControl extends TitleControl {
|
||||
removeClass(tabContainer, 'has-icon-theme');
|
||||
}
|
||||
|
||||
// Sticky Tabs need a position to remain at their location
|
||||
// when scrolling to stay in view (requirement for position: sticky)
|
||||
if (isTabSticky) {
|
||||
addClass(tabContainer, 'sticky');
|
||||
tabContainer.style.left = `${index * TabsTitleControl.TAB_SIZES.sticky}px`;
|
||||
} else {
|
||||
removeClass(tabContainer, 'sticky');
|
||||
tabContainer.style.left = 'auto';
|
||||
}
|
||||
|
||||
// Active / dirty state
|
||||
this.redrawEditorActiveAndDirty(this.accessor.activeGroup === this.group, editor, tabContainer, tabLabelWidget);
|
||||
}
|
||||
|
||||
private redrawLabel(editor: IEditorInput, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel): void {
|
||||
const name = tabLabel.name;
|
||||
const description = tabLabel.description || '';
|
||||
private redrawLabel(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel): void {
|
||||
const isTabSticky = this.group.isSticky(index);
|
||||
|
||||
// Unless tabs are sticky, show the full label and description
|
||||
// Sticky tabs will only show an icon if icons are enabled
|
||||
// or their first character of the name otherwise
|
||||
let name: string | undefined;
|
||||
let description: string;
|
||||
if (isTabSticky) {
|
||||
const isShowingIcons = this.accessor.partOptions.showIcons && !!this.accessor.partOptions.iconTheme;
|
||||
name = isShowingIcons ? '' : tabLabel.name?.charAt(0).toUpperCase();
|
||||
description = '';
|
||||
} else {
|
||||
name = tabLabel.name;
|
||||
description = tabLabel.description || '';
|
||||
}
|
||||
|
||||
const title = tabLabel.title || '';
|
||||
|
||||
if (tabLabel.ariaLabel) {
|
||||
@@ -1056,7 +1097,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
// Label
|
||||
tabLabelWidget.setResource(
|
||||
{ name, description, resource: toResource(editor, { supportSideBySide: SideBySideEditor.BOTH }) },
|
||||
{ title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) }
|
||||
{ title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor), forceLabel: isTabSticky }
|
||||
);
|
||||
|
||||
this.setEditorTabColor(editor, tabContainer, this.group.isActive(editor)); // {{SQL CARBON EDIT}} -- Display the editor's tab color
|
||||
@@ -1173,8 +1214,8 @@ export class TabsTitleControl extends TitleControl {
|
||||
layout(dimension: Dimension | undefined): void {
|
||||
this.dimension = dimension;
|
||||
|
||||
const activeTab = this.group.activeEditor ? this.getTab(this.group.activeEditor) : undefined;
|
||||
if (!activeTab || !this.dimension) {
|
||||
const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined;
|
||||
if (!activeTabAndIndex || !this.dimension) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1192,20 +1233,66 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
|
||||
private doLayout(dimension: Dimension): void {
|
||||
const activeTab = this.group.activeEditor ? this.getTab(this.group.activeEditor) : undefined;
|
||||
if (!activeTab) {
|
||||
return;
|
||||
const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined;
|
||||
if (!activeTabAndIndex) {
|
||||
return; // nothing to do if not editor opened
|
||||
}
|
||||
|
||||
const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar);
|
||||
// Breadcrumbs
|
||||
this.doLayoutBreadcrumbs(dimension);
|
||||
|
||||
// Tabs
|
||||
const [activeTab, activeIndex] = activeTabAndIndex;
|
||||
this.doLayoutTabs(activeTab, activeIndex);
|
||||
}
|
||||
|
||||
private doLayoutBreadcrumbs(dimension: Dimension): void {
|
||||
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
|
||||
const tabsScrollbar = assertIsDefined(this.tabsScrollbar);
|
||||
|
||||
this.breadcrumbsControl.layout({ width: dimension.width, height: BreadcrumbsControl.HEIGHT });
|
||||
tabsScrollbar.getDomNode().style.height = `${dimension.height - BreadcrumbsControl.HEIGHT}px`;
|
||||
}
|
||||
}
|
||||
|
||||
const visibleContainerWidth = tabsContainer.offsetWidth;
|
||||
const totalContainerWidth = tabsContainer.scrollWidth;
|
||||
private doLayoutTabs(activeTab: HTMLElement, activeIndex: number): void {
|
||||
const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar);
|
||||
|
||||
//
|
||||
// Synopsis
|
||||
// - allTabsWidth: sum of all tab widths
|
||||
// - stickyTabsWidth: sum of all sticky tab widths
|
||||
// - visibleContainerWidth: size of tab container
|
||||
// - availableContainerWidth: size of tab container minus size of sticky tabs
|
||||
//
|
||||
// [------------------------------ All tabs width ---------------------------------------]
|
||||
// [------------------- Visible container width -------------------]
|
||||
// [------ Available container width ------]
|
||||
// [ Sticky A ][ Sticky B ][ Tab C ][ Tab D ][ Tab E ][ Tab F ][ Tab G ][ Tab H ][ Tab I ]
|
||||
// Active Tab Width [-------]
|
||||
// [------- Active Tab Pos X -------]
|
||||
// [-- Sticky Tabs Width --]
|
||||
//
|
||||
|
||||
const visibleTabsContainerWidth = tabsContainer.offsetWidth;
|
||||
const allTabsWidth = tabsContainer.scrollWidth;
|
||||
|
||||
let stickyTabsWidth = this.group.stickyCount * TabsTitleControl.TAB_SIZES.sticky;
|
||||
let activeTabSticky = this.group.isSticky(activeIndex);
|
||||
let availableTabsContainerWidth = visibleTabsContainerWidth - stickyTabsWidth;
|
||||
|
||||
// Special case: we have sticky tabs but the available space for showing tabs
|
||||
// is little enough that we need to disable sticky tabs sticky positioning
|
||||
// so that tabs can be scrolled at naturally.
|
||||
if (this.group.stickyCount > 0 && availableTabsContainerWidth < TabsTitleControl.TAB_SIZES.fit) {
|
||||
addClass(tabsContainer, 'disable-sticky-tabs');
|
||||
|
||||
availableTabsContainerWidth = visibleTabsContainerWidth;
|
||||
stickyTabsWidth = 0;
|
||||
activeTabSticky = false;
|
||||
} else {
|
||||
removeClass(tabsContainer, 'disable-sticky-tabs');
|
||||
}
|
||||
|
||||
let activeTabPosX: number | undefined;
|
||||
let activeTabWidth: number | undefined;
|
||||
@@ -1217,42 +1304,78 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
// Update scrollbar
|
||||
tabsScrollbar.setScrollDimensions({
|
||||
width: visibleContainerWidth,
|
||||
scrollWidth: totalContainerWidth
|
||||
width: visibleTabsContainerWidth,
|
||||
scrollWidth: allTabsWidth
|
||||
});
|
||||
|
||||
// Return now if we are blocked to reveal the active tab and clear flag
|
||||
if (this.blockRevealActiveTab || typeof activeTabPosX !== 'number' || typeof activeTabWidth !== 'number') {
|
||||
// We also return if the active tab is sticky because this means it is
|
||||
// always visible anyway.
|
||||
if (this.blockRevealActiveTab || typeof activeTabPosX !== 'number' || typeof activeTabWidth !== 'number' || activeTabSticky) {
|
||||
this.blockRevealActiveTab = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Reveal the active one
|
||||
const containerScrollPosX = tabsScrollbar.getScrollPosition().scrollLeft;
|
||||
const activeTabFits = activeTabWidth <= visibleContainerWidth;
|
||||
const tabsContainerScrollPosX = tabsScrollbar.getScrollPosition().scrollLeft;
|
||||
const activeTabFits = activeTabWidth <= availableTabsContainerWidth;
|
||||
const adjustedActiveTabPosX = activeTabPosX - stickyTabsWidth;
|
||||
|
||||
//
|
||||
// Synopsis
|
||||
// - adjustedActiveTabPosX: the adjusted tabPosX takes the width of sticky tabs into account
|
||||
// conceptually the scrolling only begins after sticky tabs so in order to reveal a tab fully
|
||||
// the actual position needs to be adjusted for sticky tabs.
|
||||
//
|
||||
// Tab is overflowing to the right: Scroll minimally until the element is fully visible to the right
|
||||
// Note: only try to do this if we actually have enough width to give to show the tab fully!
|
||||
if (activeTabFits && containerScrollPosX + visibleContainerWidth < activeTabPosX + activeTabWidth) {
|
||||
//
|
||||
// Example: Tab G should be made active and needs to be fully revealed as such.
|
||||
//
|
||||
// [-------------------------------- All tabs width -----------------------------------------]
|
||||
// [-------------------- Visible container width --------------------]
|
||||
// [----- Available container width -------]
|
||||
// [ Sticky A ][ Sticky B ][ Tab C ][ Tab D ][ Tab E ][ Tab F ][ Tab G ][ Tab H ][ Tab I ]
|
||||
// Active Tab Width [-------]
|
||||
// [------- Active Tab Pos X -------]
|
||||
// [-------- Adjusted Tab Pos X -------]
|
||||
// [-- Sticky Tabs Width --]
|
||||
//
|
||||
//
|
||||
if (activeTabFits && tabsContainerScrollPosX + availableTabsContainerWidth < adjustedActiveTabPosX + activeTabWidth) {
|
||||
tabsScrollbar.setScrollPosition({
|
||||
scrollLeft: containerScrollPosX + ((activeTabPosX + activeTabWidth) /* right corner of tab */ - (containerScrollPosX + visibleContainerWidth) /* right corner of view port */)
|
||||
scrollLeft: tabsContainerScrollPosX + ((adjustedActiveTabPosX + activeTabWidth) /* right corner of tab */ - (tabsContainerScrollPosX + availableTabsContainerWidth) /* right corner of view port */)
|
||||
});
|
||||
}
|
||||
|
||||
// Tab is overlflowng to the left or does not fit: Scroll it into view to the left
|
||||
else if (containerScrollPosX > activeTabPosX || !activeTabFits) {
|
||||
//
|
||||
// Tab is overlflowing to the left or does not fit: Scroll it into view to the left
|
||||
//
|
||||
// Example: Tab C should be made active and needs to be fully revealed as such.
|
||||
//
|
||||
// [----------------------------- All tabs width ----------------------------------------]
|
||||
// [------------------ Visible container width ------------------]
|
||||
// [----- Available container width -------]
|
||||
// [ Sticky A ][ Sticky B ][ Tab C ][ Tab D ][ Tab E ][ Tab F ][ Tab G ][ Tab H ][ Tab I ]
|
||||
// Active Tab Width [-------]
|
||||
// [------- Active Tab Pos X -------]
|
||||
// Adjusted Tab Pos X []
|
||||
// [-- Sticky Tabs Width --]
|
||||
//
|
||||
//
|
||||
else if (tabsContainerScrollPosX > adjustedActiveTabPosX || !activeTabFits) {
|
||||
tabsScrollbar.setScrollPosition({
|
||||
scrollLeft: activeTabPosX
|
||||
scrollLeft: adjustedActiveTabPosX
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getTab(editor: IEditorInput): HTMLElement | undefined {
|
||||
private getTabAndIndex(editor: IEditorInput): [HTMLElement, number /* index */] | undefined {
|
||||
const editorIndex = this.group.getIndexOfEditor(editor);
|
||||
if (editorIndex >= 0) {
|
||||
const tabsContainer = assertIsDefined(this.tabsContainer);
|
||||
|
||||
return tabsContainer.children[editorIndex] as HTMLElement;
|
||||
return [tabsContainer.children[editorIndex] as HTMLElement, editorIndex];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -1494,11 +1617,11 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
|
||||
|
||||
// Adjust gradient for focused and unfocused hover background
|
||||
const makeTabHoverBackgroundRule = (color: Color, colorDrag: Color, hasFocus = false) => `
|
||||
.monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
|
||||
.monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):not(.sticky):hover > .tab-label::after {
|
||||
background: linear-gradient(to left, ${color}, transparent) !important;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content.dragged-over .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
|
||||
.monaco-workbench .part.editor > .content.dragged-over .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):not(.sticky):hover > .tab-label::after {
|
||||
background: linear-gradient(to left, ${colorDrag}, transparent) !important;
|
||||
}
|
||||
`;
|
||||
@@ -1521,19 +1644,19 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
|
||||
if (editorDragAndDropBackground && adjustedTabDragBackground) {
|
||||
const adjustedColorDrag = editorDragAndDropBackground.flatten(adjustedTabDragBackground);
|
||||
collector.addRule(`
|
||||
.monaco-workbench .part.editor > .content.dragged-over .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.active):not(.dragged) > .tab-label::after,
|
||||
.monaco-workbench .part.editor > .content.dragged-over .editor-group-container:not(.active) > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.dragged) > .tab-label::after {
|
||||
.monaco-workbench .part.editor > .content.dragged-over .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.active):not(.dragged):not(.sticky) > .tab-label::after,
|
||||
.monaco-workbench .part.editor > .content.dragged-over .editor-group-container:not(.active) > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.dragged):not(.sticky) > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColorDrag}, transparent) !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const makeTabBackgroundRule = (color: Color, colorDrag: Color, focused: boolean, active: boolean) => `
|
||||
.monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${focused ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink${active ? '.active' : ''}:not(.dragged) > .tab-label::after {
|
||||
.monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${focused ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink${active ? '.active' : ''}:not(.dragged):not(.sticky) > .tab-label::after {
|
||||
background: linear-gradient(to left, ${color}, transparent);
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content.dragged-over .editor-group-container${focused ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink${active ? '.active' : ''}:not(.dragged) > .tab-label::after {
|
||||
.monaco-workbench .part.editor > .content.dragged-over .editor-group-container${focused ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink${active ? '.active' : ''}:not(.dragged):not(.sticky) > .tab-label::after {
|
||||
background: linear-gradient(to left, ${colorDrag}, transparent);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -181,7 +181,12 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
|
||||
// because we are triggering another openEditor() call
|
||||
// and do not control the initial intent that resulted
|
||||
// in us now opening as binary.
|
||||
const preservingOptions: IEditorOptions = { activation: EditorActivation.PRESERVE, pinned: this.group?.isPinned(input) };
|
||||
const preservingOptions: IEditorOptions = {
|
||||
activation: EditorActivation.PRESERVE,
|
||||
pinned: this.group?.isPinned(input),
|
||||
sticky: this.group?.isSticky(input)
|
||||
};
|
||||
|
||||
if (options) {
|
||||
options.overwrite(preservingOptions);
|
||||
} else {
|
||||
@@ -237,7 +242,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
|
||||
if (isArray(error)) {
|
||||
const errors = <Error[]>error;
|
||||
|
||||
return errors.some(e => this.isFileBinaryError(e));
|
||||
return errors.some(error => this.isFileBinaryError(error));
|
||||
}
|
||||
|
||||
return (<TextFileOperationError>error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/titlecontrol';
|
||||
import { applyDragImage, DataTransfers } from 'vs/base/browser/dnd';
|
||||
import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
@@ -12,8 +13,7 @@ import { IAction, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecut
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import 'vs/css!./media/titlecontrol';
|
||||
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { getCodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { localize } from 'vs/nls';
|
||||
import { createActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { ExecuteCommandAction, IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
@@ -32,13 +32,14 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
|
||||
import { EDITOR_TITLE_HEIGHT, IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor, EditorPinnedContext } from 'vs/workbench/common/editor';
|
||||
import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor, EditorPinnedContext, EditorStickyContext } from 'vs/workbench/common/editor';
|
||||
import { ResourceContextKey } from 'vs/workbench/common/resources';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types';
|
||||
import { isFirefox } from 'vs/base/browser/browser';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
|
||||
export interface IToolbarActions {
|
||||
primary: IAction[];
|
||||
@@ -59,6 +60,7 @@ export abstract class TitleControl extends Themable {
|
||||
|
||||
private resourceContext: ResourceContextKey;
|
||||
private editorPinnedContext: IContextKey<boolean>;
|
||||
private editorStickyContext: IContextKey<boolean>;
|
||||
|
||||
private readonly editorToolBarMenuDisposables = this._register(new DisposableStore());
|
||||
|
||||
@@ -86,6 +88,7 @@ export abstract class TitleControl extends Themable {
|
||||
|
||||
this.resourceContext = this._register(instantiationService.createInstance(ResourceContextKey));
|
||||
this.editorPinnedContext = EditorPinnedContext.bindTo(contextKeyService);
|
||||
this.editorStickyContext = EditorStickyContext.bindTo(contextKeyService);
|
||||
|
||||
this.contextMenu = this._register(this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService));
|
||||
|
||||
@@ -222,6 +225,7 @@ export abstract class TitleControl extends Themable {
|
||||
// Update contexts
|
||||
this.resourceContext.set(this.group.activeEditor ? withUndefinedAsNull(toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.MASTER })) : null);
|
||||
this.editorPinnedContext.set(this.group.activeEditor ? this.group.isPinned(this.group.activeEditor) : false);
|
||||
this.editorStickyContext.set(this.group.activeEditor ? this.group.isSticky(this.group.activeEditor) : false);
|
||||
|
||||
// Editor actions require the editor control to be there, so we retrieve it via service
|
||||
const activeEditorPane = this.group.activeEditorPane;
|
||||
@@ -266,10 +270,8 @@ export abstract class TitleControl extends Themable {
|
||||
// If tabs are disabled, treat dragging as if an editor tab was dragged
|
||||
let hasDataTransfer = false;
|
||||
if (!this.accessor.partOptions.showTabs) {
|
||||
const resource = this.group.activeEditor ? toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null;
|
||||
if (resource) {
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e);
|
||||
hasDataTransfer = true;
|
||||
if (this.group.activeEditor) {
|
||||
hasDataTransfer = this.doFillResourceDataTransfers(this.group.activeEditor, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,6 +297,31 @@ export abstract class TitleControl extends Themable {
|
||||
}));
|
||||
}
|
||||
|
||||
protected doFillResourceDataTransfers(editor: IEditorInput, e: DragEvent): boolean {
|
||||
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
if (!resource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const editorOptions: ITextEditorOptions = {
|
||||
viewState: (() => {
|
||||
if (this.group.activeEditor === editor) {
|
||||
const activeControl = this.group.activeEditorPane?.getControl();
|
||||
if (isCodeEditor(activeControl)) {
|
||||
return withNullAsUndefined(activeControl.saveViewState());
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
})(),
|
||||
sticky: this.group.isSticky(editor)
|
||||
};
|
||||
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], () => editorOptions, e);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onContextMenu(editor: IEditorInput, e: Event, node: HTMLElement): void {
|
||||
|
||||
// Update contexts based on editor picked and remember previous to restore
|
||||
@@ -302,6 +329,8 @@ export abstract class TitleControl extends Themable {
|
||||
this.resourceContext.set(withUndefinedAsNull(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER })));
|
||||
const currentPinnedContext = !!this.editorPinnedContext.get();
|
||||
this.editorPinnedContext.set(this.group.isPinned(editor));
|
||||
const currentStickyContext = !!this.editorStickyContext.get();
|
||||
this.editorStickyContext.set(this.group.isSticky(editor));
|
||||
|
||||
// Find target anchor
|
||||
let anchor: HTMLElement | { x: number, y: number } = node;
|
||||
@@ -325,6 +354,7 @@ export abstract class TitleControl extends Themable {
|
||||
// restore previous contexts
|
||||
this.resourceContext.set(currentResourceContext || null);
|
||||
this.editorPinnedContext.set(currentPinnedContext);
|
||||
this.editorStickyContext.set(currentStickyContext);
|
||||
|
||||
// restore focus to active group
|
||||
this.accessor.activeGroup.focus();
|
||||
@@ -351,12 +381,14 @@ export abstract class TitleControl extends Themable {
|
||||
|
||||
abstract closeEditors(editors: IEditorInput[]): void;
|
||||
|
||||
abstract closeAllEditors(): void;
|
||||
|
||||
abstract moveEditor(editor: IEditorInput, fromIndex: number, targetIndex: number): void;
|
||||
|
||||
abstract pinEditor(editor: IEditorInput): void;
|
||||
|
||||
abstract stickEditor(editor: IEditorInput): void;
|
||||
|
||||
abstract unstickEditor(editor: IEditorInput): void;
|
||||
|
||||
abstract setActive(isActive: boolean): void;
|
||||
|
||||
abstract updateEditorLabel(editor: IEditorInput): void;
|
||||
|
||||
@@ -207,7 +207,8 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
}
|
||||
|
||||
if (isActive) {
|
||||
if (!activePanel) {
|
||||
// Only try to open the panel if it has been created and visible
|
||||
if (!activePanel && this.element && this.layoutService.isVisible(Parts.PANEL_PART)) {
|
||||
this.doOpenPanel(panel.id);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { isThemeColor } from 'vs/editor/common/editorCommon';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { addClass, EventHelper, createStyleSheet, addDisposableListener, addClasses, removeClass, EventType, hide, show, removeClasses } from 'vs/base/browser/dom';
|
||||
import { addClass, EventHelper, createStyleSheet, addDisposableListener, addClasses, removeClass, EventType, hide, show, removeClasses, isAncestor } from 'vs/base/browser/dom';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
|
||||
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
@@ -35,6 +35,11 @@ import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Command } from 'vs/editor/common/modes';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
interface IPendingStatusbarEntry {
|
||||
id: string;
|
||||
@@ -51,8 +56,11 @@ interface IStatusbarViewModelEntry {
|
||||
alignment: StatusbarAlignment;
|
||||
priority: number;
|
||||
container: HTMLElement;
|
||||
labelContainer: HTMLElement;
|
||||
}
|
||||
|
||||
const CONTEXT_STATUS_BAR_FOCUSED = new RawContextKey<boolean>('statusBarFocused', false);
|
||||
|
||||
class StatusbarViewModel extends Disposable {
|
||||
|
||||
static readonly HIDDEN_ENTRIES_KEY = 'workbench.statusbar.hidden';
|
||||
@@ -188,6 +196,40 @@ class StatusbarViewModel extends Disposable {
|
||||
return this._entries.filter(entry => entry.alignment === alignment);
|
||||
}
|
||||
|
||||
focusNextEntry(): void {
|
||||
this.focusEntry(+1, 0);
|
||||
}
|
||||
|
||||
focusPreviousEntry(): void {
|
||||
this.focusEntry(-1, this.entries.length - 1);
|
||||
}
|
||||
|
||||
private focusEntry(delta: number, restartPosition: number): void {
|
||||
const getVisibleEntry = (start: number) => {
|
||||
let indexToFocus = start;
|
||||
let entry = (indexToFocus >= 0 && indexToFocus < this._entries.length) ? this._entries[indexToFocus] : undefined;
|
||||
while (entry && this.isHidden(entry.id)) {
|
||||
indexToFocus += delta;
|
||||
entry = (indexToFocus >= 0 && indexToFocus < this._entries.length) ? this._entries[indexToFocus] : undefined;
|
||||
}
|
||||
return entry;
|
||||
};
|
||||
|
||||
const focused = this._entries.find(entry => isAncestor(document.activeElement, entry.container));
|
||||
if (focused) {
|
||||
const entry = getVisibleEntry(this._entries.indexOf(focused) + delta);
|
||||
if (entry) {
|
||||
entry.labelContainer.focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const entry = getVisibleEntry(restartPosition);
|
||||
if (entry) {
|
||||
entry.labelContainer.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private updateVisibility(id: string, trigger: boolean): void;
|
||||
private updateVisibility(entry: IStatusbarViewModelEntry, trigger: boolean): void;
|
||||
private updateVisibility(arg1: string | IStatusbarViewModelEntry, trigger: boolean): void {
|
||||
@@ -355,6 +397,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
) {
|
||||
super(Parts.STATUSBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
|
||||
@@ -415,7 +458,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
this.appendOneStatusbarEntry(itemContainer, alignment, priority);
|
||||
|
||||
// Add to view model
|
||||
const viewModelEntry: IStatusbarViewModelEntry = { id, name, alignment, priority, container: itemContainer };
|
||||
const viewModelEntry: IStatusbarViewModelEntry = { id, name, alignment, priority, container: itemContainer, labelContainer: item.labelContainer };
|
||||
const viewModelEntryDispose = this.viewModel.add(viewModelEntry);
|
||||
|
||||
return {
|
||||
@@ -442,9 +485,21 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
}
|
||||
}
|
||||
|
||||
focusNextEntry(): void {
|
||||
this.viewModel.focusNextEntry();
|
||||
}
|
||||
|
||||
focusPreviousEntry(): void {
|
||||
this.viewModel.focusPreviousEntry();
|
||||
}
|
||||
|
||||
createContentArea(parent: HTMLElement): HTMLElement {
|
||||
this.element = parent;
|
||||
|
||||
// Track focus within container
|
||||
const scopedContextKeyService = this.contextKeyService.createScoped(this.element);
|
||||
CONTEXT_STATUS_BAR_FOCUSED.bindTo(scopedContextKeyService).set(true);
|
||||
|
||||
// Left items container
|
||||
this.leftItemsContainer = document.createElement('div');
|
||||
addClasses(this.leftItemsContainer, 'left-items', 'items-container');
|
||||
@@ -645,13 +700,14 @@ class StatusbarEntryItem extends Disposable {
|
||||
|
||||
private entry!: IStatusbarEntry;
|
||||
|
||||
private labelContainer!: HTMLElement;
|
||||
labelContainer!: HTMLElement;
|
||||
private label!: CodiconLabel;
|
||||
|
||||
private readonly foregroundListener = this._register(new MutableDisposable());
|
||||
private readonly backgroundListener = this._register(new MutableDisposable());
|
||||
|
||||
private readonly commandListener = this._register(new MutableDisposable());
|
||||
private readonly commandMouseListener = this._register(new MutableDisposable());
|
||||
private readonly commandKeyboardListener = this._register(new MutableDisposable());
|
||||
|
||||
constructor(
|
||||
private container: HTMLElement,
|
||||
@@ -711,11 +767,18 @@ class StatusbarEntryItem extends Disposable {
|
||||
|
||||
// Update: Command
|
||||
if (!this.entry || entry.command !== this.entry.command) {
|
||||
this.commandListener.clear();
|
||||
this.commandMouseListener.clear();
|
||||
this.commandKeyboardListener.clear();
|
||||
|
||||
const command = entry.command;
|
||||
if (command) {
|
||||
this.commandListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command));
|
||||
this.commandMouseListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command));
|
||||
this.commandKeyboardListener.value = addDisposableListener(this.labelContainer, EventType.KEY_UP, e => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {
|
||||
this.executeCommand(command);
|
||||
}
|
||||
});
|
||||
|
||||
removeClass(this.labelContainer, 'disabled');
|
||||
} else {
|
||||
@@ -814,7 +877,8 @@ class StatusbarEntryItem extends Disposable {
|
||||
|
||||
dispose(this.foregroundListener);
|
||||
dispose(this.backgroundListener);
|
||||
dispose(this.commandListener);
|
||||
dispose(this.commandMouseListener);
|
||||
dispose(this.commandKeyboardListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,6 +886,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
|
||||
const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND);
|
||||
if (statusBarItemHoverBackground) {
|
||||
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:hover { background-color: ${statusBarItemHoverBackground}; }`);
|
||||
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:focus { background-color: ${statusBarItemHoverBackground}; }`);
|
||||
}
|
||||
|
||||
const statusBarItemActiveBackground = theme.getColor(STATUS_BAR_ITEM_ACTIVE_BACKGROUND);
|
||||
@@ -846,3 +911,27 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
|
||||
});
|
||||
|
||||
registerSingleton(IStatusbarService, StatusbarPart);
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.statusBar.focusPrevious',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyCode.LeftArrow,
|
||||
secondary: [KeyCode.UpArrow],
|
||||
when: CONTEXT_STATUS_BAR_FOCUSED,
|
||||
handler: (accessor: ServicesAccessor) => {
|
||||
const statusBarService = accessor.get(IStatusbarService);
|
||||
statusBarService.focusPreviousEntry();
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.statusBar.focusNext',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyCode.RightArrow,
|
||||
secondary: [KeyCode.DownArrow],
|
||||
when: CONTEXT_STATUS_BAR_FOCUSED,
|
||||
handler: (accessor: ServicesAccessor) => {
|
||||
const statusBarService = accessor.get(IStatusbarService);
|
||||
statusBarService.focusNextEntry();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -934,17 +934,21 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
getContextMenuActions(viewDescriptor?: IViewDescriptor): IAction[] {
|
||||
const result: IAction[] = [];
|
||||
|
||||
let showHide = true;
|
||||
if (!viewDescriptor && this.isViewMergedWithContainer()) {
|
||||
viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.panes[0].id) || undefined;
|
||||
showHide = false;
|
||||
}
|
||||
|
||||
if (viewDescriptor) {
|
||||
result.push(<IAction>{
|
||||
id: `${viewDescriptor.id}.removeView`,
|
||||
label: nls.localize('hideView', "Hide"),
|
||||
enabled: viewDescriptor.canToggleVisibility,
|
||||
run: () => this.toggleViewVisibility(viewDescriptor!.id)
|
||||
});
|
||||
if (showHide) {
|
||||
result.push(<IAction>{
|
||||
id: `${viewDescriptor.id}.removeView`,
|
||||
label: nls.localize('hideView', "Hide"),
|
||||
enabled: viewDescriptor.canToggleVisibility,
|
||||
run: () => this.toggleViewVisibility(viewDescriptor!.id)
|
||||
});
|
||||
}
|
||||
const view = this.getView(viewDescriptor.id);
|
||||
if (view) {
|
||||
result.push(...view.getContextMenuActions());
|
||||
@@ -955,7 +959,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
id: `${viewDescriptor.id}.toggleVisibility`,
|
||||
label: viewDescriptor.name,
|
||||
checked: this.viewContainerModel.isVisible(viewDescriptor.id),
|
||||
enabled: viewDescriptor.canToggleVisibility,
|
||||
enabled: viewDescriptor.canToggleVisibility && (!this.viewContainerModel.isVisible(viewDescriptor.id) || this.viewContainerModel.visibleViewDescriptors.length > 1),
|
||||
run: () => this.toggleViewVisibility(viewDescriptor.id)
|
||||
}));
|
||||
|
||||
|
||||
@@ -127,15 +127,10 @@ export class ViewsService extends Disposable implements IViewsService {
|
||||
this.deregisterViewletOrPanel(container, location);
|
||||
}
|
||||
for (const { container, location } of added) {
|
||||
this.registerViewletOrPanel(container, location);
|
||||
this.onDidRegisterViewContainer(container, location);
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeContainerLocation(viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation): void {
|
||||
this.deregisterViewletOrPanel(viewContainer, from);
|
||||
this.registerViewletOrPanel(viewContainer, to);
|
||||
}
|
||||
|
||||
private onDidRegisterViewContainer(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void {
|
||||
this.registerViewletOrPanel(viewContainer, viewContainerLocation);
|
||||
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
@@ -146,6 +141,11 @@ export class ViewsService extends Disposable implements IViewsService {
|
||||
}));
|
||||
}
|
||||
|
||||
private onDidChangeContainerLocation(viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation): void {
|
||||
this.deregisterViewletOrPanel(viewContainer, from);
|
||||
this.registerViewletOrPanel(viewContainer, to);
|
||||
}
|
||||
|
||||
private onViewDescriptorsAdded(views: ReadonlyArray<IViewDescriptor>, container: ViewContainer): void {
|
||||
const location = this.viewDescriptorService.getViewContainerLocation(container);
|
||||
if (location === null) {
|
||||
|
||||
@@ -71,7 +71,7 @@ class BrowserMain extends Disposable {
|
||||
services.storageService.store(Settings.WORKSPACE_FIRST_OPEN, !(firstOpen ?? false), StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
{ await domContentLoaded(); }
|
||||
await domContentLoaded();
|
||||
mark('willStartWorkbench');
|
||||
|
||||
// Base Theme
|
||||
@@ -272,7 +272,7 @@ class BrowserMain extends Disposable {
|
||||
if (!this.configuration.userDataProvider) {
|
||||
const remoteUserDataUri = this.getRemoteUserDataUri();
|
||||
if (remoteUserDataUri) {
|
||||
this.configuration.userDataProvider = this._register(new FileUserDataProvider(remoteUserDataUri, joinPath(remoteUserDataUri, BACKUPS), remoteFileSystemProvider, environmentService));
|
||||
this.configuration.userDataProvider = this._register(new FileUserDataProvider(remoteUserDataUri, joinPath(remoteUserDataUri, BACKUPS), remoteFileSystemProvider, environmentService, logService));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,12 +108,12 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio
|
||||
},
|
||||
'workbench.editor.enablePreview': {
|
||||
'type': 'boolean',
|
||||
'description': nls.localize('enablePreview', "Controls whether opened editors show as preview. Preview editors are reused until they are pinned (e.g. via double click or editing) and show up with an italic font style."),
|
||||
'description': nls.localize('enablePreview', "Controls whether opened editors show as preview. Preview editors are reused until they are explicitly set to be kept open (e.g. via double click or editing) and show up with an italic font style."),
|
||||
'default': true
|
||||
},
|
||||
'workbench.editor.enablePreviewFromQuickOpen': {
|
||||
'type': 'boolean',
|
||||
'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors are reused until they are pinned (e.g. via double click or editing)."),
|
||||
'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors are reused until they are explicitly set to be kept open (e.g. via double click or editing)."),
|
||||
'default': true
|
||||
},
|
||||
'workbench.editor.closeOnFileDelete': {
|
||||
|
||||
@@ -35,6 +35,7 @@ export const ActiveEditorIsReadonlyContext = new RawContextKey<boolean>('activeE
|
||||
export const ActiveEditorAvailableEditorIdsContext = new RawContextKey<string>('activeEditorAvailableEditorIds', '');
|
||||
export const EditorsVisibleContext = new RawContextKey<boolean>('editorIsOpen', false);
|
||||
export const EditorPinnedContext = new RawContextKey<boolean>('editorPinned', false);
|
||||
export const EditorStickyContext = new RawContextKey<boolean>('editorSticky', false);
|
||||
export const EditorGroupActiveEditorDirtyContext = new RawContextKey<boolean>('groupActiveEditorDirty', false);
|
||||
export const EditorGroupEditorsCountContext = new RawContextKey<number>('groupEditorsCount', 0);
|
||||
export const NoEditorsVisibleContext = EditorsVisibleContext.toNegated();
|
||||
@@ -1046,6 +1047,12 @@ export class EditorOptions implements IEditorOptions {
|
||||
*/
|
||||
pinned: boolean | undefined;
|
||||
|
||||
/**
|
||||
* An editor that is sticky moves to the beginning of the editors list within the group and will remain
|
||||
* there unless explicitly closed. Operations such as "Close All" will not close sticky editors.
|
||||
*/
|
||||
sticky: boolean | undefined;
|
||||
|
||||
/**
|
||||
* The index in the document stack where to insert the editor into when opening.
|
||||
*/
|
||||
@@ -1111,6 +1118,10 @@ export class EditorOptions implements IEditorOptions {
|
||||
this.pinned = options.pinned;
|
||||
}
|
||||
|
||||
if (typeof options.sticky === 'boolean') {
|
||||
this.sticky = options.sticky;
|
||||
}
|
||||
|
||||
if (typeof options.inactive === 'boolean') {
|
||||
this.inactive = options.inactive;
|
||||
}
|
||||
@@ -1291,6 +1302,7 @@ export class EditorCommandsContextActionRunner extends ActionRunner {
|
||||
export interface IEditorCloseEvent extends IEditorIdentifier {
|
||||
replaced: boolean;
|
||||
index: number;
|
||||
sticky: boolean;
|
||||
}
|
||||
|
||||
export type GroupIdentifier = number;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, CloseDirection, SideBySideEditorInput, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor';
|
||||
import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, SideBySideEditorInput, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
@@ -30,6 +30,7 @@ export interface EditorIdentifier extends IEditorIdentifier {
|
||||
|
||||
export interface IEditorOpenOptions {
|
||||
pinned?: boolean;
|
||||
sticky?: boolean;
|
||||
active?: boolean;
|
||||
index?: number;
|
||||
}
|
||||
@@ -44,6 +45,7 @@ export interface ISerializedEditorGroup {
|
||||
editors: ISerializedEditorInput[];
|
||||
mru: number[];
|
||||
preview?: number;
|
||||
sticky?: number;
|
||||
}
|
||||
|
||||
export function isSerializedEditorGroup(obj?: unknown): obj is ISerializedEditorGroup {
|
||||
@@ -92,6 +94,7 @@ export class EditorGroup extends Disposable {
|
||||
|
||||
private preview: EditorInput | null = null; // editor in preview state
|
||||
private active: EditorInput | null = null; // editor in active state
|
||||
private sticky: number = -1; // index of first editor in sticky state
|
||||
|
||||
private editorOpenPositioning: ('left' | 'right' | 'first' | 'last') | undefined;
|
||||
private focusRecentEditorAfterClose: boolean | undefined;
|
||||
@@ -114,10 +117,10 @@ export class EditorGroup extends Disposable {
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e)));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(() => this.onConfigurationUpdated()));
|
||||
}
|
||||
|
||||
private onConfigurationUpdated(event?: IConfigurationChangeEvent): void {
|
||||
private onConfigurationUpdated(): void {
|
||||
this.editorOpenPositioning = this.configurationService.getValue('workbench.editor.openPositioning');
|
||||
this.focusRecentEditorAfterClose = this.configurationService.getValue('workbench.editor.focusRecentEditorAfterClose');
|
||||
}
|
||||
@@ -126,8 +129,25 @@ export class EditorGroup extends Disposable {
|
||||
return this.editors.length;
|
||||
}
|
||||
|
||||
getEditors(order: EditorsOrder): EditorInput[] {
|
||||
return order === EditorsOrder.MOST_RECENTLY_ACTIVE ? this.mru.slice(0) : this.editors.slice(0);
|
||||
get stickyCount(): number {
|
||||
return this.sticky + 1;
|
||||
}
|
||||
|
||||
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): EditorInput[] {
|
||||
const editors = order === EditorsOrder.MOST_RECENTLY_ACTIVE ? this.mru.slice(0) : this.editors.slice(0);
|
||||
|
||||
if (options?.excludeSticky) {
|
||||
|
||||
// MRU: need to check for index on each
|
||||
if (order === EditorsOrder.MOST_RECENTLY_ACTIVE) {
|
||||
return editors.filter(editor => !this.isSticky(editor));
|
||||
}
|
||||
|
||||
// Sequential: simply start after sticky index
|
||||
return editors.slice(this.sticky + 1);
|
||||
}
|
||||
|
||||
return editors;
|
||||
}
|
||||
|
||||
getEditorByIndex(index: number): EditorInput | undefined {
|
||||
@@ -146,18 +166,15 @@ export class EditorGroup extends Disposable {
|
||||
return this.preview;
|
||||
}
|
||||
|
||||
isPreview(editor: EditorInput): boolean {
|
||||
return this.matches(this.preview, editor);
|
||||
}
|
||||
|
||||
openEditor(candidate: EditorInput, options?: IEditorOpenOptions): EditorInput {
|
||||
const makePinned = options?.pinned;
|
||||
const makeSticky = options?.sticky || (typeof options?.index === 'number' && this.isSticky(options.index));
|
||||
const makePinned = options?.pinned || options?.sticky;
|
||||
const makeActive = options?.active || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor));
|
||||
|
||||
const existingEditor = this.findEditor(candidate);
|
||||
const existingEditorAndIndex = this.findEditor(candidate);
|
||||
|
||||
// New editor
|
||||
if (!existingEditor) {
|
||||
if (!existingEditorAndIndex) {
|
||||
const newEditor = candidate;
|
||||
const indexOfActive = this.indexOf(this.active);
|
||||
|
||||
@@ -170,6 +187,12 @@ export class EditorGroup extends Disposable {
|
||||
// Insert to the BEGINNING
|
||||
else if (this.editorOpenPositioning === EditorOpenPositioning.FIRST) {
|
||||
targetIndex = 0;
|
||||
|
||||
// Always make sure targetIndex is after sticky editors
|
||||
// unless we are explicitly told to make the editor sticky
|
||||
if (!makeSticky && this.isSticky(targetIndex)) {
|
||||
targetIndex = this.sticky + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert to the END
|
||||
@@ -177,18 +200,38 @@ export class EditorGroup extends Disposable {
|
||||
targetIndex = this.editors.length;
|
||||
}
|
||||
|
||||
// Insert to the LEFT of active editor
|
||||
else if (this.editorOpenPositioning === EditorOpenPositioning.LEFT) {
|
||||
if (indexOfActive === 0 || !this.editors.length) {
|
||||
targetIndex = 0; // to the left becoming first editor in list
|
||||
} else {
|
||||
targetIndex = indexOfActive; // to the left of active editor
|
||||
// Insert to LEFT or RIGHT of active editor
|
||||
else {
|
||||
|
||||
// Insert to the LEFT of active editor
|
||||
if (this.editorOpenPositioning === EditorOpenPositioning.LEFT) {
|
||||
if (indexOfActive === 0 || !this.editors.length) {
|
||||
targetIndex = 0; // to the left becoming first editor in list
|
||||
} else {
|
||||
targetIndex = indexOfActive; // to the left of active editor
|
||||
}
|
||||
}
|
||||
|
||||
// Insert to the RIGHT of active editor
|
||||
else {
|
||||
targetIndex = indexOfActive + 1;
|
||||
}
|
||||
|
||||
// Always make sure targetIndex is after sticky editors
|
||||
// unless we are explicitly told to make the editor sticky
|
||||
if (!makeSticky && this.isSticky(targetIndex)) {
|
||||
targetIndex = this.sticky + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert to the RIGHT of active editor
|
||||
else {
|
||||
targetIndex = indexOfActive + 1;
|
||||
// If the editor becomes sticky, increment the sticky index and adjust
|
||||
// the targetIndex to be at the end of sticky editors unless already.
|
||||
if (makeSticky) {
|
||||
this.sticky++;
|
||||
|
||||
if (!this.isSticky(targetIndex)) {
|
||||
targetIndex = this.sticky;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert into our list of editors if pinned or we have no preview editor
|
||||
@@ -228,6 +271,7 @@ export class EditorGroup extends Disposable {
|
||||
|
||||
// Existing editor
|
||||
else {
|
||||
const [existingEditor] = existingEditorAndIndex;
|
||||
|
||||
// Pin it
|
||||
if (makePinned) {
|
||||
@@ -244,6 +288,12 @@ export class EditorGroup extends Disposable {
|
||||
this.moveEditor(existingEditor, options.index);
|
||||
}
|
||||
|
||||
// Stick it (intentionally after the moveEditor call in case
|
||||
// the editor was already moved into the sticky range)
|
||||
if (makeSticky) {
|
||||
this.doStick(existingEditor, this.indexOf(existingEditor));
|
||||
}
|
||||
|
||||
return existingEditor;
|
||||
}
|
||||
}
|
||||
@@ -252,8 +302,7 @@ export class EditorGroup extends Disposable {
|
||||
const listeners = new DisposableStore();
|
||||
|
||||
// Re-emit disposal of editor input as our own event
|
||||
const onceDispose = Event.once(editor.onDispose);
|
||||
listeners.add(onceDispose(() => {
|
||||
listeners.add(Event.once(editor.onDispose)(() => {
|
||||
if (this.indexOf(editor) >= 0) {
|
||||
this._onDidDisposeEditor.fire(editor);
|
||||
}
|
||||
@@ -309,6 +358,7 @@ export class EditorGroup extends Disposable {
|
||||
}
|
||||
|
||||
const editor = this.editors[index];
|
||||
const sticky = this.isSticky(index);
|
||||
|
||||
// Active Editor closed
|
||||
if (openNext && this.matches(this.active, editor)) {
|
||||
@@ -344,45 +394,18 @@ export class EditorGroup extends Disposable {
|
||||
this.splice(index, true);
|
||||
|
||||
// Event
|
||||
return { editor, replaced, index, groupId: this.id };
|
||||
}
|
||||
|
||||
closeEditors(except: EditorInput, direction?: CloseDirection): void {
|
||||
const index = this.indexOf(except);
|
||||
if (index === -1) {
|
||||
return; // not found
|
||||
}
|
||||
|
||||
// Close to the left
|
||||
if (direction === CloseDirection.LEFT) {
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
this.closeEditor(this.editors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Close to the right
|
||||
else if (direction === CloseDirection.RIGHT) {
|
||||
for (let i = this.editors.length - 1; i > index; i--) {
|
||||
this.closeEditor(this.editors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Both directions
|
||||
else {
|
||||
this.mru.filter(e => !this.matches(e, except)).forEach(e => this.closeEditor(e));
|
||||
}
|
||||
}
|
||||
|
||||
closeAllEditors(): void {
|
||||
|
||||
// Optimize: close all non active editors first to produce less upstream work
|
||||
this.mru.filter(e => !this.matches(e, this.active)).forEach(e => this.closeEditor(e));
|
||||
if (this.active) {
|
||||
this.closeEditor(this.active);
|
||||
}
|
||||
return { editor, replaced, sticky, index, groupId: this.id };
|
||||
}
|
||||
|
||||
moveEditor(candidate: EditorInput, toIndex: number): EditorInput | undefined {
|
||||
|
||||
// Ensure toIndex is in bounds of our model
|
||||
if (toIndex >= this.editors.length) {
|
||||
toIndex = this.editors.length - 1;
|
||||
} else if (toIndex < 0) {
|
||||
toIndex = 0;
|
||||
}
|
||||
|
||||
const index = this.indexOf(candidate);
|
||||
if (index < 0 || toIndex === index) {
|
||||
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||
@@ -390,6 +413,16 @@ export class EditorGroup extends Disposable {
|
||||
|
||||
const editor = this.editors[index];
|
||||
|
||||
// Adjust sticky index: editor moved out of sticky state into unsticky state
|
||||
if (this.isSticky(index) && toIndex > this.sticky) {
|
||||
this.sticky--;
|
||||
}
|
||||
|
||||
// ...or editor moved into sticky state from unsticky state
|
||||
else if (!this.isSticky(index) && toIndex <= this.sticky) {
|
||||
this.sticky++;
|
||||
}
|
||||
|
||||
// Move
|
||||
this.editors.splice(index, 1);
|
||||
this.editors.splice(toIndex, 0, editor);
|
||||
@@ -401,11 +434,13 @@ export class EditorGroup extends Disposable {
|
||||
}
|
||||
|
||||
setActive(candidate: EditorInput): EditorInput | undefined {
|
||||
const editor = this.findEditor(candidate);
|
||||
if (!editor) {
|
||||
const res = this.findEditor(candidate);
|
||||
if (!res) {
|
||||
return undefined; // not found {{SQL CARBON EDIT}} strict-null-check
|
||||
}
|
||||
|
||||
const [editor] = res;
|
||||
|
||||
this.doSetActive(editor);
|
||||
|
||||
return editor;
|
||||
@@ -428,18 +463,20 @@ export class EditorGroup extends Disposable {
|
||||
}
|
||||
|
||||
pin(candidate: EditorInput): EditorInput | undefined {
|
||||
const editor = this.findEditor(candidate);
|
||||
if (!editor) {
|
||||
const res = this.findEditor(candidate);
|
||||
if (!res) {
|
||||
return undefined; // not found {{SQL CARBON EDIT}} strict-null-check
|
||||
}
|
||||
|
||||
const [editor] = res;
|
||||
|
||||
this.doPin(editor);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
private doPin(editor: EditorInput): void {
|
||||
if (!this.isPreview(editor)) {
|
||||
if (this.isPinned(editor)) {
|
||||
return; // can only pin a preview editor
|
||||
}
|
||||
|
||||
@@ -451,11 +488,13 @@ export class EditorGroup extends Disposable {
|
||||
}
|
||||
|
||||
unpin(candidate: EditorInput): EditorInput | undefined {
|
||||
const editor = this.findEditor(candidate);
|
||||
if (!editor) {
|
||||
const res = this.findEditor(candidate);
|
||||
if (!res) {
|
||||
return undefined; // not found {{SQL CARBON EDIT}} strict-null-check
|
||||
}
|
||||
|
||||
const [editor] = res;
|
||||
|
||||
this.doUnpin(editor);
|
||||
|
||||
return editor;
|
||||
@@ -479,33 +518,97 @@ export class EditorGroup extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
isPinned(editor: EditorInput): boolean;
|
||||
isPinned(index: number): boolean;
|
||||
isPinned(arg1: EditorInput | number): boolean {
|
||||
if (!this.preview) {
|
||||
return true; // no preview editor
|
||||
}
|
||||
|
||||
isPinned(editorOrIndex: EditorInput | number): boolean {
|
||||
let editor: EditorInput;
|
||||
let index: number;
|
||||
if (typeof arg1 === 'number') {
|
||||
editor = this.editors[arg1];
|
||||
index = arg1;
|
||||
if (typeof editorOrIndex === 'number') {
|
||||
editor = this.editors[editorOrIndex];
|
||||
} else {
|
||||
editor = arg1;
|
||||
index = this.indexOf(editor);
|
||||
}
|
||||
|
||||
if (index === -1 || !editor) {
|
||||
return false; // editor not found
|
||||
editor = editorOrIndex;
|
||||
}
|
||||
|
||||
return !this.matches(this.preview, editor);
|
||||
}
|
||||
|
||||
stick(candidate: EditorInput): EditorInput | undefined {
|
||||
const res = this.findEditor(candidate);
|
||||
if (!res) {
|
||||
return undefined; // not found {{SQL CARBON EDIT}} strict-null-check
|
||||
}
|
||||
|
||||
const [editor, index] = res;
|
||||
|
||||
this.doStick(editor, index);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
private doStick(editor: EditorInput, index: number): void {
|
||||
if (this.isSticky(index)) {
|
||||
return; // can only stick a non-sticky editor
|
||||
}
|
||||
|
||||
// Pin editor
|
||||
this.pin(editor);
|
||||
|
||||
// Move editor to be the last sticky editor
|
||||
this.moveEditor(editor, this.sticky + 1);
|
||||
|
||||
// Adjust sticky index
|
||||
this.sticky++;
|
||||
}
|
||||
|
||||
unstick(candidate: EditorInput): EditorInput | undefined {
|
||||
const res = this.findEditor(candidate);
|
||||
if (!res) {
|
||||
return undefined; // not found {{SQL CARBON EDIT}} strict-null-check
|
||||
}
|
||||
|
||||
const [editor, index] = res;
|
||||
|
||||
this.doUnstick(editor, index);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
private doUnstick(editor: EditorInput, index: number): void {
|
||||
if (!this.isSticky(index)) {
|
||||
return; // can only unstick a sticky editor
|
||||
}
|
||||
|
||||
// Move editor to be the first non-sticky editor
|
||||
this.moveEditor(editor, this.sticky);
|
||||
|
||||
// Adjust sticky index
|
||||
this.sticky--;
|
||||
}
|
||||
|
||||
isSticky(candidateOrIndex: EditorInput | number): boolean {
|
||||
if (this.sticky < 0) {
|
||||
return false; // no sticky editor
|
||||
}
|
||||
|
||||
let index: number;
|
||||
if (typeof candidateOrIndex === 'number') {
|
||||
index = candidateOrIndex;
|
||||
} else {
|
||||
index = this.indexOf(candidateOrIndex);
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return index <= this.sticky;
|
||||
}
|
||||
|
||||
private splice(index: number, del: boolean, editor?: EditorInput): void {
|
||||
const editorToDeleteOrReplace = this.editors[index];
|
||||
|
||||
// Perform on sticky index
|
||||
if (del && this.isSticky(index)) {
|
||||
this.sticky--;
|
||||
}
|
||||
|
||||
// Perform on editors array
|
||||
if (editor) {
|
||||
this.editors.splice(index, del ? 1 : 0, editor);
|
||||
@@ -513,35 +616,38 @@ export class EditorGroup extends Disposable {
|
||||
this.editors.splice(index, del ? 1 : 0);
|
||||
}
|
||||
|
||||
// Add
|
||||
if (!del && editor) {
|
||||
if (this.mru.length === 0) {
|
||||
// the list of most recent editors is empty
|
||||
// so this editor can only be the most recent
|
||||
this.mru.push(editor);
|
||||
} else {
|
||||
// we have most recent editors. as such we
|
||||
// put this newly opened editor right after
|
||||
// the current most recent one because it cannot
|
||||
// be the most recently active one unless
|
||||
// it becomes active. but it is still more
|
||||
// active then any other editor in the list.
|
||||
this.mru.splice(1, 0, editor);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove / Replace
|
||||
else {
|
||||
const indexInMRU = this.indexOf(editorToDeleteOrReplace, this.mru);
|
||||
|
||||
// Remove
|
||||
if (del && !editor) {
|
||||
this.mru.splice(indexInMRU, 1); // remove from MRU
|
||||
// Perform on MRU
|
||||
{
|
||||
// Add
|
||||
if (!del && editor) {
|
||||
if (this.mru.length === 0) {
|
||||
// the list of most recent editors is empty
|
||||
// so this editor can only be the most recent
|
||||
this.mru.push(editor);
|
||||
} else {
|
||||
// we have most recent editors. as such we
|
||||
// put this newly opened editor right after
|
||||
// the current most recent one because it cannot
|
||||
// be the most recently active one unless
|
||||
// it becomes active. but it is still more
|
||||
// active then any other editor in the list.
|
||||
this.mru.splice(1, 0, editor);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace
|
||||
else if (del && editor) {
|
||||
this.mru.splice(indexInMRU, 1, editor); // replace MRU at location
|
||||
// Remove / Replace
|
||||
else {
|
||||
const indexInMRU = this.indexOf(editorToDeleteOrReplace, this.mru);
|
||||
|
||||
// Remove
|
||||
if (del && !editor) {
|
||||
this.mru.splice(indexInMRU, 1); // remove from MRU
|
||||
}
|
||||
|
||||
// Replace
|
||||
else if (del && editor) {
|
||||
this.mru.splice(indexInMRU, 1, editor); // replace MRU at location
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -560,13 +666,13 @@ export class EditorGroup extends Disposable {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private findEditor(candidate: EditorInput | null): EditorInput | undefined {
|
||||
private findEditor(candidate: EditorInput | null): [EditorInput, number /* index */] | undefined {
|
||||
const index = this.indexOf(candidate, this.editors);
|
||||
if (index === -1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.editors[index];
|
||||
return [this.editors[index], index];
|
||||
}
|
||||
|
||||
contains(candidate: EditorInput, searchInSideBySideEditors?: boolean): boolean {
|
||||
@@ -599,7 +705,7 @@ export class EditorGroup extends Disposable {
|
||||
group.mru = this.mru.slice(0);
|
||||
group.preview = this.preview;
|
||||
group.active = this.active;
|
||||
group.editorOpenPositioning = this.editorOpenPositioning;
|
||||
group.sticky = this.sticky;
|
||||
|
||||
return group;
|
||||
}
|
||||
@@ -609,32 +715,52 @@ export class EditorGroup extends Disposable {
|
||||
|
||||
// Serialize all editor inputs so that we can store them.
|
||||
// Editors that cannot be serialized need to be ignored
|
||||
// from mru, active and preview if any.
|
||||
// from mru, active, preview and sticky if any.
|
||||
let serializableEditors: EditorInput[] = [];
|
||||
let serializedEditors: ISerializedEditorInput[] = [];
|
||||
let serializablePreviewIndex: number | undefined;
|
||||
this.editors.forEach(e => {
|
||||
const factory = registry.getEditorInputFactory(e.getTypeId());
|
||||
if (factory) {
|
||||
const value = factory.serialize(e);
|
||||
if (typeof value === 'string') {
|
||||
serializedEditors.push({ id: e.getTypeId(), value });
|
||||
serializableEditors.push(e);
|
||||
let serializableSticky = this.sticky;
|
||||
|
||||
if (this.preview === e) {
|
||||
for (let i = 0; i < this.editors.length; i++) {
|
||||
const editor = this.editors[i];
|
||||
let canSerializeEditor = false;
|
||||
|
||||
const factory = registry.getEditorInputFactory(editor.getTypeId());
|
||||
if (factory) {
|
||||
const value = factory.serialize(editor);
|
||||
|
||||
// Editor can be serialized
|
||||
if (typeof value === 'string') {
|
||||
canSerializeEditor = true;
|
||||
|
||||
serializedEditors.push({ id: editor.getTypeId(), value });
|
||||
serializableEditors.push(editor);
|
||||
|
||||
if (this.preview === editor) {
|
||||
serializablePreviewIndex = serializableEditors.length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const serializableMru = this.mru.map(e => this.indexOf(e, serializableEditors)).filter(i => i >= 0);
|
||||
// Editor cannot be serialized
|
||||
else {
|
||||
canSerializeEditor = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust index of sticky editors if the editor cannot be serialized and is pinned
|
||||
if (!canSerializeEditor && this.isSticky(i)) {
|
||||
serializableSticky--;
|
||||
}
|
||||
}
|
||||
|
||||
const serializableMru = this.mru.map(editor => this.indexOf(editor, serializableEditors)).filter(i => i >= 0);
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
editors: serializedEditors,
|
||||
mru: serializableMru,
|
||||
preview: serializablePreviewIndex,
|
||||
sticky: serializableSticky >= 0 ? serializableSticky : undefined
|
||||
};
|
||||
}
|
||||
|
||||
@@ -649,18 +775,22 @@ export class EditorGroup extends Disposable {
|
||||
this._id = EditorGroup.IDS++; // backwards compatibility
|
||||
}
|
||||
|
||||
this.editors = coalesce(data.editors.map(e => {
|
||||
this.editors = coalesce(data.editors.map((e, index) => {
|
||||
let editor: EditorInput | undefined = undefined;
|
||||
|
||||
const factory = registry.getEditorInputFactory(e.id);
|
||||
if (factory) {
|
||||
const editor = doHandleUpgrade(factory.deserialize(this.instantiationService, e.value)); // {{SQL CARBON EDIT}} handle upgrade path to new serialization
|
||||
editor = doHandleUpgrade(factory.deserialize(this.instantiationService, e.value)); // {{SQL CARBON EDIT}} handle upgrade path to new serialization
|
||||
if (editor) {
|
||||
this.registerEditorListeners(editor);
|
||||
}
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
return null;
|
||||
if (!editor && typeof data.sticky === 'number' && index <= data.sticky) {
|
||||
data.sticky--; // if editor cannot be deserialized but was sticky, we need to decrease sticky index
|
||||
}
|
||||
|
||||
return editor;
|
||||
}));
|
||||
|
||||
this.mru = coalesce(data.mru.map(i => this.editors[i]));
|
||||
@@ -671,6 +801,10 @@ export class EditorGroup extends Disposable {
|
||||
this.preview = this.editors[data.preview];
|
||||
}
|
||||
|
||||
if (typeof data.sticky === 'number') {
|
||||
this.sticky = data.sticky;
|
||||
}
|
||||
|
||||
return this._id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ export interface IViewContentDescriptor {
|
||||
|
||||
export interface IViewsRegistry {
|
||||
|
||||
readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }>;
|
||||
readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }[]>;
|
||||
|
||||
readonly onViewsDeregistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }>;
|
||||
|
||||
@@ -290,6 +290,8 @@ export interface IViewsRegistry {
|
||||
|
||||
registerViews(views: IViewDescriptor[], viewContainer: ViewContainer): void;
|
||||
|
||||
registerViews2(views: { views: IViewDescriptor[], viewContainer: ViewContainer }[]): void;
|
||||
|
||||
deregisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void;
|
||||
|
||||
moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void;
|
||||
@@ -319,8 +321,8 @@ function compareViewContentDescriptors(a: IViewContentDescriptor, b: IViewConten
|
||||
|
||||
class ViewsRegistry extends Disposable implements IViewsRegistry {
|
||||
|
||||
private readonly _onViewsRegistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>());
|
||||
readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._onViewsRegistered.event;
|
||||
private readonly _onViewsRegistered = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }[]>());
|
||||
readonly onViewsRegistered = this._onViewsRegistered.event;
|
||||
|
||||
private readonly _onViewsDeregistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>());
|
||||
readonly onViewsDeregistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._onViewsDeregistered.event;
|
||||
@@ -336,8 +338,12 @@ class ViewsRegistry extends Disposable implements IViewsRegistry {
|
||||
private _viewWelcomeContents = new SetMap<string, IViewContentDescriptor>();
|
||||
|
||||
registerViews(views: IViewDescriptor[], viewContainer: ViewContainer): void {
|
||||
this.addViews(views, viewContainer);
|
||||
this._onViewsRegistered.fire({ views: views, viewContainer });
|
||||
this.registerViews2([{ views, viewContainer }]);
|
||||
}
|
||||
|
||||
registerViews2(views: { views: IViewDescriptor[], viewContainer: ViewContainer }[]): void {
|
||||
views.forEach(({ views, viewContainer }) => this.addViews(views, viewContainer));
|
||||
this._onViewsRegistered.fire(views);
|
||||
}
|
||||
|
||||
deregisterViews(viewDescriptors: IViewDescriptor[], viewContainer: ViewContainer): void {
|
||||
|
||||
@@ -172,6 +172,20 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
|
||||
updatePickerItems();
|
||||
disposables.add(picker.onDidChangeValue(updatePickerItems));
|
||||
|
||||
let ignoreFirstActiveEvent = true;
|
||||
disposables.add(picker.onDidChangeActive(() => {
|
||||
const [entry] = picker.activeItems;
|
||||
|
||||
if (entry && entries[entry.index]) {
|
||||
if (ignoreFirstActiveEvent) {
|
||||
ignoreFirstActiveEvent = false;
|
||||
return;
|
||||
}
|
||||
|
||||
entries[entry.index]?.reveal();
|
||||
}
|
||||
}));
|
||||
|
||||
}).catch(err => {
|
||||
onUnexpectedError(err);
|
||||
picker.hide();
|
||||
|
||||
@@ -350,7 +350,7 @@ class BreakpointsRenderer implements IListRenderer<IBreakpoint, IBreakpointTempl
|
||||
|
||||
data.filePath = dom.append(data.breakpoint, $('span.file-path'));
|
||||
const lineNumberContainer = dom.append(data.breakpoint, $('.line-number-container'));
|
||||
data.lineNumber = dom.append(lineNumberContainer, $('span.line-number'));
|
||||
data.lineNumber = dom.append(lineNumberContainer, $('span.line-number.monaco-count-badge'));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { commonSuffixLength } from 'vs/base/common/strings';
|
||||
import { posix } from 'vs/base/common/path';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
@@ -78,6 +80,26 @@ export function getContextForContributedActions(element: CallStackItem | null):
|
||||
return '';
|
||||
}
|
||||
|
||||
export function getSpecificSourceName(stackFrame: IStackFrame): string {
|
||||
// To reduce flashing of the path name and the way we fetch stack frames
|
||||
// We need to compute the source name based on the other frames in the stale call stack
|
||||
let callStack = (<Thread>stackFrame.thread).getStaleCallStack();
|
||||
callStack = callStack.length > 0 ? callStack : stackFrame.thread.getCallStack();
|
||||
const otherSources = callStack.map(sf => sf.source).filter(s => s !== stackFrame.source);
|
||||
let suffixLength = 0;
|
||||
otherSources.forEach(s => {
|
||||
if (s.name === stackFrame.source.name) {
|
||||
suffixLength = Math.max(suffixLength, commonSuffixLength(stackFrame.source.uri.path, s.uri.path));
|
||||
}
|
||||
});
|
||||
if (suffixLength === 0) {
|
||||
return stackFrame.source.name;
|
||||
}
|
||||
|
||||
const from = Math.max(0, stackFrame.source.uri.path.lastIndexOf(posix.sep, stackFrame.source.uri.path.length - suffixLength - 1));
|
||||
return (from > 0 ? '...' : '') + stackFrame.source.uri.path.substr(from);
|
||||
}
|
||||
|
||||
export class CallStackView extends ViewPane {
|
||||
private pauseMessage!: HTMLSpanElement;
|
||||
private pauseMessageLabel!: HTMLSpanElement;
|
||||
@@ -453,7 +475,7 @@ class SessionsRenderer implements ITreeRenderer<IDebugSession, FuzzyScore, ISess
|
||||
dom.append(session, $('.codicon.codicon-bug'));
|
||||
const name = dom.append(session, $('.name'));
|
||||
const state = dom.append(session, $('.state'));
|
||||
const stateLabel = dom.append(state, $('span.label'));
|
||||
const stateLabel = dom.append(state, $('span.label.monaco-count-badge.long'));
|
||||
const label = new HighlightedLabel(name, false);
|
||||
const actionBar = new ActionBar(session, {
|
||||
actionViewItemProvider: action => {
|
||||
@@ -562,7 +584,7 @@ class StackFramesRenderer implements ITreeRenderer<IStackFrame, FuzzyScore, ISta
|
||||
const file = dom.append(stackFrame, $('.file'));
|
||||
const fileName = dom.append(file, $('span.file-name'));
|
||||
const wrapper = dom.append(file, $('span.line-number-wrapper'));
|
||||
const lineNumber = dom.append(wrapper, $('span.line-number'));
|
||||
const lineNumber = dom.append(wrapper, $('span.line-number.monaco-count-badge'));
|
||||
const label = new HighlightedLabel(labelDiv, false);
|
||||
const actionBar = new ActionBar(stackFrame);
|
||||
|
||||
@@ -582,7 +604,7 @@ class StackFramesRenderer implements ITreeRenderer<IStackFrame, FuzzyScore, ISta
|
||||
data.file.title += `\n${stackFrame.source.raw.origin}`;
|
||||
}
|
||||
data.label.set(stackFrame.name, createMatches(element.filterData), stackFrame.name);
|
||||
data.fileName.textContent = stackFrame.getSpecificSourceName();
|
||||
data.fileName.textContent = getSpecificSourceName(stackFrame);
|
||||
if (stackFrame.range.startLineNumber !== undefined) {
|
||||
data.lineNumber.textContent = `${stackFrame.range.startLineNumber}`;
|
||||
if (stackFrame.range.startColumn) {
|
||||
@@ -855,7 +877,7 @@ class CallStackAccessibilityProvider implements IListAccessibilityProvider<CallS
|
||||
return nls.localize('threadAriaLabel', "Thread {0}, callstack, debug", (<Thread>element).name);
|
||||
}
|
||||
if (element instanceof StackFrame) {
|
||||
return nls.localize('stackFrameAriaLabel', "Stack Frame {0}, line {1}, {2}, callstack, debug", element.name, element.range.startLineNumber, element.getSpecificSourceName());
|
||||
return nls.localize('stackFrameAriaLabel', "Stack Frame {0}, line {1}, {2}, callstack, debug", element.name, element.range.startLineNumber, getSpecificSourceName(element));
|
||||
}
|
||||
if (isDebugSession(element)) {
|
||||
return nls.localize('sessionLabel', "Debug Session {0}", element.getLabel());
|
||||
|
||||
@@ -229,11 +229,6 @@ configurationRegistry.registerConfiguration({
|
||||
default: 'openOnSessionStart',
|
||||
description: nls.localize('openDebug', "Controls when the debug view should open.")
|
||||
},
|
||||
'debug.enableAllHovers': {
|
||||
type: 'boolean',
|
||||
description: nls.localize({ comment: ['This is the description for a setting'], key: 'enableAllHovers' }, "Controls whether the non-debug hovers should be enabled while debugging. When enabled the hover providers will be called to provide a hover. Regular hovers will not be shown even if this setting is enabled."),
|
||||
default: false
|
||||
},
|
||||
'debug.showSubSessionsInToolBar': {
|
||||
type: 'boolean',
|
||||
description: nls.localize({ comment: ['This is the description for a setting'], key: 'showSubSessionsInToolBar' }, "Controls whether the debug sub-sessions are shown in the debug tool bar. When this setting is false the stop command on a sub-session will also stop the parent session."),
|
||||
|
||||
@@ -24,7 +24,6 @@ import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { PanelFocusContext } from 'vs/workbench/common/panel';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -206,7 +205,7 @@ export function registerCommands(): void {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.F5,
|
||||
when: CONTEXT_IN_DEBUG_MODE,
|
||||
handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
|
||||
handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
let session: IDebugSession | undefined;
|
||||
if (isSessionContext(context)) {
|
||||
@@ -217,10 +216,10 @@ export function registerCommands(): void {
|
||||
|
||||
if (!session) {
|
||||
const { launch, name } = debugService.getConfigurationManager().selectedConfiguration;
|
||||
debugService.startDebugging(launch, name, { noDebug: false });
|
||||
await debugService.startDebugging(launch, name, { noDebug: false });
|
||||
} else {
|
||||
session.removeReplExpressions();
|
||||
debugService.restartSession(session).then(undefined, onUnexpectedError);
|
||||
await debugService.restartSession(session);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -268,10 +267,15 @@ export function registerCommands(): void {
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: DISCONNECT_ID,
|
||||
handler: (accessor: ServicesAccessor, sessionId: string | undefined) => {
|
||||
handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const session = debugService.getModel().getSession(sessionId) || debugService.getViewModel().focusedSession;
|
||||
debugService.stopSession(session).then(undefined, onUnexpectedError);
|
||||
let session: IDebugSession | undefined;
|
||||
if (isSessionContext(context)) {
|
||||
session = debugService.getModel().getSession(context.sessionId);
|
||||
} else {
|
||||
session = debugService.getViewModel().focusedSession;
|
||||
}
|
||||
await debugService.stopSession(session);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -280,7 +284,7 @@ export function registerCommands(): void {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.Shift | KeyCode.F5,
|
||||
when: CONTEXT_IN_DEBUG_MODE,
|
||||
handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
|
||||
handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
let session: IDebugSession | undefined;
|
||||
if (isSessionContext(context)) {
|
||||
@@ -296,7 +300,7 @@ export function registerCommands(): void {
|
||||
session = session.parentSession;
|
||||
}
|
||||
|
||||
debugService.stopSession(session).then(undefined, onUnexpectedError);
|
||||
await debugService.stopSession(session);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -336,9 +340,9 @@ export function registerCommands(): void {
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'debug.startFromConfig',
|
||||
handler: (accessor, config: IConfig) => {
|
||||
handler: async (accessor, config: IConfig) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
debugService.startDebugging(undefined, config).then(undefined, onUnexpectedError);
|
||||
await debugService.startDebugging(undefined, config);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -30,10 +30,8 @@ import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
|
||||
import { first } from 'vs/base/common/arrays';
|
||||
import { memoize, createMemoizer } from 'vs/base/common/decorators';
|
||||
import { IEditorHoverOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { DebugHoverWidget } from 'vs/workbench/contrib/debug/browser/debugHover';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { getHover } from 'vs/editor/contrib/hover/getHover';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
@@ -170,7 +168,6 @@ class DebugEditorContribution implements IDebugEditorContribution {
|
||||
|
||||
private toDispose: IDisposable[];
|
||||
private hoverWidget: DebugHoverWidget;
|
||||
private nonDebugHoverPosition: Position | undefined;
|
||||
private hoverRange: Range | null = null;
|
||||
private mouseDown = false;
|
||||
private static readonly MEMOIZER = createMemoizer();
|
||||
@@ -204,7 +201,6 @@ class DebugEditorContribution implements IDebugEditorContribution {
|
||||
this.toDispose.push(this.editor.onMouseUp(() => this.mouseDown = false));
|
||||
this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => this.onEditorMouseMove(e)));
|
||||
this.toDispose.push(this.editor.onMouseLeave((e: IPartialEditorMouseEvent) => {
|
||||
this.provideNonDebugHoverScheduler.cancel();
|
||||
const hoverDomNode = this.hoverWidget.getDomNode();
|
||||
if (!hoverDomNode) {
|
||||
return;
|
||||
@@ -315,24 +311,11 @@ class DebugEditorContribution implements IDebugEditorContribution {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get provideNonDebugHoverScheduler(): RunOnceScheduler {
|
||||
const scheduler = new RunOnceScheduler(() => {
|
||||
if (this.editor.hasModel() && this.nonDebugHoverPosition) {
|
||||
getHover(this.editor.getModel(), this.nonDebugHoverPosition, CancellationToken.None);
|
||||
}
|
||||
}, HOVER_DELAY);
|
||||
this.toDispose.push(scheduler);
|
||||
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
private hideHoverWidget(): void {
|
||||
if (!this.hideHoverScheduler.isScheduled() && this.hoverWidget.isVisible()) {
|
||||
this.hideHoverScheduler.schedule();
|
||||
}
|
||||
this.showHoverScheduler.cancel();
|
||||
this.provideNonDebugHoverScheduler.cancel();
|
||||
}
|
||||
|
||||
// hover business
|
||||
@@ -351,10 +334,6 @@ class DebugEditorContribution implements IDebugEditorContribution {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.configurationService.getValue<IDebugConfiguration>('debug').enableAllHovers && mouseEvent.target.position) {
|
||||
this.nonDebugHoverPosition = mouseEvent.target.position;
|
||||
this.provideNonDebugHoverScheduler.schedule();
|
||||
}
|
||||
const targetType = mouseEvent.target.type;
|
||||
const stopKey = env.isMacintosh ? 'metaKey' : 'ctrlKey';
|
||||
|
||||
|
||||
@@ -66,10 +66,8 @@
|
||||
/* Debug viewlet trees */
|
||||
|
||||
.debug-pane .line-number {
|
||||
border-radius: 2px;
|
||||
font-size: 0.9em;
|
||||
padding: 0 3px;
|
||||
line-height: 20px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.debug-pane .disabled {
|
||||
@@ -118,6 +116,8 @@
|
||||
|
||||
.debug-pane .debug-call-stack .thread > .state,
|
||||
.debug-pane .debug-call-stack .session > .state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: right;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -159,9 +159,8 @@
|
||||
|
||||
.debug-pane .debug-call-stack .thread > .state > .label,
|
||||
.debug-pane .debug-call-stack .session > .state > .label {
|
||||
border-radius: 2px;
|
||||
font-size: 0.8em;
|
||||
padding: 0 3px;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.debug-pane .debug-call-stack .stack-frame {
|
||||
|
||||
@@ -318,7 +318,6 @@ export interface IStackFrame extends ITreeElement {
|
||||
readonly source: Source;
|
||||
getScopes(): Promise<IScope[]>;
|
||||
getMostSpecificScopes(range: IRange): Promise<ReadonlyArray<IScope>>;
|
||||
getSpecificSourceName(): string;
|
||||
forgetScopes(): void;
|
||||
restart(): Promise<any>;
|
||||
toString(): string;
|
||||
|
||||
@@ -17,8 +17,6 @@ import {
|
||||
IThread, IRawModelUpdate, IScope, IRawStoppedDetails, IEnablement, IBreakpointData, IExceptionInfo, IBreakpointsChangeEvent, IBreakpointUpdateData, IBaseBreakpoint, State, IDataBreakpoint
|
||||
} from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource';
|
||||
import { commonSuffixLength } from 'vs/base/common/strings';
|
||||
import { posix } from 'vs/base/common/path';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextEditorPane } from 'vs/workbench/common/editor';
|
||||
@@ -325,26 +323,6 @@ export class StackFrame implements IStackFrame {
|
||||
return this.scopes;
|
||||
}
|
||||
|
||||
getSpecificSourceName(): string {
|
||||
// To reduce flashing of the path name and the way we fetch stack frames
|
||||
// We need to compute the source name based on the other frames in the stale call stack
|
||||
let callStack = (<Thread>this.thread).getStaleCallStack();
|
||||
callStack = callStack.length > 0 ? callStack : this.thread.getCallStack();
|
||||
const otherSources = callStack.map(sf => sf.source).filter(s => s !== this.source);
|
||||
let suffixLength = 0;
|
||||
otherSources.forEach(s => {
|
||||
if (s.name === this.source.name) {
|
||||
suffixLength = Math.max(suffixLength, commonSuffixLength(this.source.uri.path, s.uri.path));
|
||||
}
|
||||
});
|
||||
if (suffixLength === 0) {
|
||||
return this.source.name;
|
||||
}
|
||||
|
||||
const from = Math.max(0, this.source.uri.path.lastIndexOf(posix.sep, this.source.uri.path.length - suffixLength - 1));
|
||||
return (from > 0 ? '...' : '') + this.source.uri.path.substr(from);
|
||||
}
|
||||
|
||||
async getMostSpecificScopes(range: IRange): Promise<IScope[]> {
|
||||
const scopes = await this.getScopes();
|
||||
const nonExpensiveScopes = scopes.filter(s => !s.expensive);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IDebugSessionOptions, State } from 'vs/workbench/contrib/debug/common/d
|
||||
import { NullOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { createDecorationsForStackFrame } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import { getContext, getContextForContributedActions } from 'vs/workbench/contrib/debug/browser/callStackView';
|
||||
import { getContext, getContextForContributedActions, getSpecificSourceName } from 'vs/workbench/contrib/debug/browser/callStackView';
|
||||
import { getStackFrameThreadAndSessionToFocus } from 'vs/workbench/contrib/debug/browser/debugService';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
@@ -250,8 +250,8 @@ suite('Debug - CallStack', () => {
|
||||
model.addSession(session);
|
||||
const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session);
|
||||
|
||||
assert.equal(firstStackFrame.getSpecificSourceName(), '.../b/c/d/internalModule.js');
|
||||
assert.equal(secondStackFrame.getSpecificSourceName(), '.../x/c/d/internalModule.js');
|
||||
assert.equal(getSpecificSourceName(firstStackFrame), '.../b/c/d/internalModule.js');
|
||||
assert.equal(getSpecificSourceName(secondStackFrame), '.../x/c/d/internalModule.js');
|
||||
});
|
||||
|
||||
test('stack frame toString()', () => {
|
||||
|
||||
@@ -1629,11 +1629,11 @@ export class ShowInstalledExtensionsAction extends Action {
|
||||
super(id, label, undefined, true);
|
||||
}
|
||||
|
||||
run(): Promise<void> {
|
||||
run(refresh?: boolean): Promise<void> {
|
||||
return this.viewletService.openViewlet(VIEWLET_ID, true)
|
||||
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
|
||||
.then(viewlet => {
|
||||
viewlet.search('@installed ');
|
||||
viewlet.search('@installed ', refresh);
|
||||
viewlet.focus();
|
||||
});
|
||||
}
|
||||
@@ -2983,90 +2983,84 @@ export class InstallVSIXAction extends Action {
|
||||
@IHostService private readonly hostService: IHostService,
|
||||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService, // {{SQL CARBON EDIT}}
|
||||
@IStorageService private storageService: IStorageService
|
||||
) {
|
||||
super(id, label, 'extension-action install-vsix', true);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(vsixPaths?: URI[]): Promise<void> {
|
||||
// {{SQL CARBON EDIT}} - Replace run body
|
||||
let extensionPolicy = this.configurationService.getValue<string>(ExtensionsPolicyKey);
|
||||
if (extensionPolicy === ExtensionsPolicy.allowAll) {
|
||||
return Promise.resolve(this.fileDialogService.showOpenDialog({
|
||||
title: localize('installFromVSIX', "Install from VSIX"),
|
||||
filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }],
|
||||
canSelectFiles: true,
|
||||
openLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install"))
|
||||
}).then(result => {
|
||||
if (!result) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.all(result.map(vsix => {
|
||||
if (!this.storageService.getBoolean(vsix.fsPath, StorageScope.GLOBAL)) {
|
||||
if (!vsixPaths) {
|
||||
vsixPaths = await this.fileDialogService.showOpenDialog({
|
||||
title: localize('installFromVSIX', "Install from VSIX"),
|
||||
filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }],
|
||||
canSelectFiles: true,
|
||||
openLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install"))
|
||||
});
|
||||
}
|
||||
|
||||
await Promise.all(vsixPaths.map(async vsix => {
|
||||
if (!this.storageService.getBoolean(vsix.fsPath, StorageScope.GLOBAL)) {
|
||||
const accept = await new Promise<boolean>(resolve => {
|
||||
this.notificationService.prompt(
|
||||
Severity.Warning,
|
||||
localize('thirdPartyExtension.vsix', 'This is a third party extension and might involve security risks. Are you sure you want to install this extension?'),
|
||||
[
|
||||
{
|
||||
label: localize('thirdPartExt.yes', 'Yes'),
|
||||
run: () => {
|
||||
this.extensionsWorkbenchService.install(vsix).then(extension => {
|
||||
const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local)));
|
||||
const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Azure Data Studio to complete installing the extension {0}.", extension.identifier.id)
|
||||
: localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.identifier.id);
|
||||
const actions = requireReload ? [{
|
||||
label: localize('InstallVSIXAction.reloadNow', "Reload Now"),
|
||||
run: () => this.hostService.reload()
|
||||
}] : [];
|
||||
this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
message,
|
||||
actions,
|
||||
{ sticky: true }
|
||||
);
|
||||
});
|
||||
}
|
||||
run: () => resolve(true)
|
||||
},
|
||||
{
|
||||
label: localize('thirdPartyExt.no', 'No'),
|
||||
run: () => { return Promise.resolve(); }
|
||||
run: () => resolve(false)
|
||||
},
|
||||
{
|
||||
label: localize('thirdPartyExt.dontShowAgain', 'Don\'t Show Again'),
|
||||
isSecondary: true,
|
||||
run: () => {
|
||||
this.storageService.store(vsix.fsPath, true, StorageScope.GLOBAL);
|
||||
return Promise.resolve();
|
||||
resolve(true);
|
||||
}
|
||||
}
|
||||
],
|
||||
{ sticky: true }
|
||||
);
|
||||
} else {
|
||||
this.extensionsWorkbenchService.install(vsix).then(extension => {
|
||||
const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local)));
|
||||
const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Azure Data Studio to complete installing the extension {0}.", extension.identifier.id)
|
||||
: localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.identifier.id);
|
||||
const actions = requireReload ? [{
|
||||
label: localize('InstallVSIXAction.reloadNow', "Reload Now"),
|
||||
run: () => this.hostService.reload()
|
||||
}] : [];
|
||||
this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
message,
|
||||
actions,
|
||||
{ sticky: true }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
if (!accept) {
|
||||
return undefined;
|
||||
}
|
||||
})).then(() => Promise.resolve());
|
||||
}));
|
||||
}
|
||||
|
||||
return this.extensionsWorkbenchService.install(vsix);
|
||||
})).then(async (extensions) => {
|
||||
for (const extension of extensions) {
|
||||
if (!extension) {
|
||||
return;
|
||||
}
|
||||
const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local)));
|
||||
const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Visual Studio Code to complete installing the extension {0}.", extension.displayName || extension.name)
|
||||
: localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.displayName || extension.name);
|
||||
const actions = requireReload ? [{
|
||||
label: localize('InstallVSIXAction.reloadNow', "Reload Now"),
|
||||
run: () => this.hostService.reload()
|
||||
}] : [];
|
||||
this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
message,
|
||||
actions,
|
||||
{ sticky: true }
|
||||
);
|
||||
}
|
||||
await this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL).run(true);
|
||||
});
|
||||
} else {
|
||||
this.notificationService.error(localize('InstallVSIXAction.allowNone', 'Your extension policy does not allow downloading extensions. Please change your extension policy and try again.'));
|
||||
return Promise.resolve();
|
||||
}
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
}
|
||||
|
||||
get enabled(): boolean { // {{SQL CARBON EDIT}} add enabled logic
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { append, $, addClass, toggleClass, Dimension } from 'vs/base/browser/dom';
|
||||
import { append, $, addClass, toggleClass, Dimension, hide, show } from 'vs/base/browser/dom';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
@@ -57,6 +57,9 @@ import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { MementoObject } from 'vs/workbench/common/memento';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
|
||||
const NonEmptyWorkspaceContext = new RawContextKey<boolean>('nonEmptyWorkspace', false);
|
||||
const DefaultViewsContext = new RawContextKey<boolean>('defaultExtensionViews', true);
|
||||
@@ -400,8 +403,12 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
addClass(parent, 'extensions-viewlet');
|
||||
this.root = parent;
|
||||
|
||||
const header = append(this.root, $('.header'));
|
||||
const overlay = append(this.root, $('.overlay'));
|
||||
const overlayBackgroundColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';
|
||||
overlay.style.backgroundColor = overlayBackgroundColor;
|
||||
hide(overlay);
|
||||
|
||||
const header = append(this.root, $('.header'));
|
||||
const placeholder = localize('searchExtensions', "Search Extensions in Marketplace");
|
||||
const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : '';
|
||||
|
||||
@@ -435,6 +442,49 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
}
|
||||
}));
|
||||
|
||||
// Register DragAndDrop support
|
||||
this._register(new DragAndDropObserver(this.root, {
|
||||
onDragEnd: (e: DragEvent) => undefined,
|
||||
onDragEnter: (e: DragEvent) => {
|
||||
if (this.isSupportedDragElement(e)) {
|
||||
show(overlay);
|
||||
}
|
||||
},
|
||||
onDragLeave: (e: DragEvent) => {
|
||||
if (this.isSupportedDragElement(e)) {
|
||||
hide(overlay);
|
||||
}
|
||||
},
|
||||
onDragOver: (e: DragEvent) => {
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.dropEffect = this.isSupportedDragElement(e) ? 'copy' : 'none';
|
||||
}
|
||||
},
|
||||
onDrop: async (e: DragEvent) => {
|
||||
if (this.isSupportedDragElement(e)) {
|
||||
hide(overlay);
|
||||
|
||||
if (e.dataTransfer && e.dataTransfer.files.length > 0) {
|
||||
let vsixPaths: URI[] = [];
|
||||
for (let index = 0; index < e.dataTransfer.files.length; index++) {
|
||||
const path = e.dataTransfer.files.item(index)!.path;
|
||||
if (path.indexOf('.vsix') !== -1) {
|
||||
vsixPaths.push(URI.parse(path));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Attempt to install the extension(s)
|
||||
await this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL).run(vsixPaths);
|
||||
}
|
||||
catch (err) {
|
||||
this.notificationService.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
super.create(append(this.root, $('.extensions')));
|
||||
}
|
||||
|
||||
@@ -622,6 +672,15 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
|
||||
this.notificationService.error(err);
|
||||
}
|
||||
|
||||
private isSupportedDragElement(e: DragEvent): boolean {
|
||||
if (e.dataTransfer) {
|
||||
const typesLowerCase = e.dataTransfer.types.map(t => t.toLocaleLowerCase());
|
||||
return typesLowerCase.indexOf('files') !== -1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class StatusUpdater extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
@@ -4,9 +4,19 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.extensions-viewlet {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.extensions-viewlet > .overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.extensions-viewlet > .header {
|
||||
height: 41px;
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -60,10 +60,9 @@
|
||||
}
|
||||
|
||||
.dirty-count.monaco-count-badge {
|
||||
padding: 1px 6px 2px;
|
||||
padding: 2px 4px;
|
||||
margin-left: 6px;
|
||||
min-height: auto;
|
||||
border-radius: 0; /* goes better when ellipsis shows up on narrow sidebar */
|
||||
}
|
||||
|
||||
.explorer-viewlet .explorer-item.nonexistent-root {
|
||||
|
||||
@@ -890,7 +890,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||
const items = FileDragAndDrop.getStatsFromDragAndDropData(data as ElementsDragAndDropData<ExplorerItem, ExplorerItem[]>, originalEvent);
|
||||
if (items && items.length && originalEvent.dataTransfer) {
|
||||
// Apply some datatransfer types to allow for dragging the element outside of the application
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, items, originalEvent);
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, items, undefined, originalEvent);
|
||||
|
||||
// The only custom data transfer we set from the explorer is a file transfer
|
||||
// to be able to DND between multiple code file explorers across windows
|
||||
@@ -965,7 +965,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||
private async handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
|
||||
const droppedResources = extractResources(originalEvent, true);
|
||||
// Check for dropped external files to be folders
|
||||
const result = await this.fileService.resolveAll(droppedResources);
|
||||
const result = await this.fileService.resolveAll(droppedResources.map(droppedResource => ({ resource: droppedResource.resource })));
|
||||
|
||||
// Pass focus to window
|
||||
this.hostService.focus();
|
||||
|
||||
@@ -184,7 +184,7 @@ export class OpenEditorsView extends ViewPane {
|
||||
super.renderHeaderTitle(container, this.title);
|
||||
|
||||
const count = dom.append(container, $('.count'));
|
||||
this.dirtyCountElement = dom.append(count, $('.dirty-count.monaco-count-badge'));
|
||||
this.dirtyCountElement = dom.append(count, $('.dirty-count.monaco-count-badge.long'));
|
||||
|
||||
this._register((attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => {
|
||||
const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
|
||||
@@ -660,7 +660,7 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop<OpenEditor | IEditorGro
|
||||
|
||||
if (resources.length) {
|
||||
// Apply some datatransfer types to allow for dragging the element outside of the application
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, originalEvent);
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, undefined, originalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -849,7 +849,7 @@ export class ResourceDragAndDrop implements ITreeDragAndDrop<TreeElement> {
|
||||
|
||||
if (resources.length) {
|
||||
// Apply some datatransfer types to allow for dragging the element outside of the application
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, originalEvent);
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, undefined, originalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -540,7 +540,12 @@ registerAction2(class extends InsertCellCommand {
|
||||
id: INSERT_CODE_CELL_ABOVE_COMMAND_ID,
|
||||
title: localize('notebookActions.insertCodeCellAbove', "Insert Code Cell Above"),
|
||||
category: NOTEBOOK_ACTIONS_CATEGORY,
|
||||
f1: true
|
||||
f1: true,
|
||||
keybinding: {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter,
|
||||
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
}
|
||||
},
|
||||
CellKind.Code,
|
||||
'above');
|
||||
@@ -572,7 +577,12 @@ registerAction2(class extends InsertCellCommand {
|
||||
title: localize('notebookActions.insertCodeCellBelow', "Insert Code Cell Below"),
|
||||
category: NOTEBOOK_ACTIONS_CATEGORY,
|
||||
icon: { id: 'codicon/add' },
|
||||
f1: true
|
||||
f1: true,
|
||||
keybinding: {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Enter,
|
||||
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
}
|
||||
},
|
||||
CellKind.Code,
|
||||
'below');
|
||||
@@ -770,7 +780,12 @@ registerAction2(class extends Action2 {
|
||||
title: localize('notebookActions.moveCellUp', "Move Cell Up"),
|
||||
category: NOTEBOOK_ACTIONS_CATEGORY,
|
||||
icon: { id: 'codicon/arrow-up' },
|
||||
f1: true
|
||||
f1: true,
|
||||
keybinding: {
|
||||
primary: KeyMod.Alt | KeyCode.UpArrow,
|
||||
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -794,7 +809,12 @@ registerAction2(class extends Action2 {
|
||||
title: localize('notebookActions.moveCellDown', "Move Cell Down"),
|
||||
category: NOTEBOOK_ACTIONS_CATEGORY,
|
||||
icon: { id: 'codicon/arrow-down' },
|
||||
f1: true
|
||||
f1: true,
|
||||
keybinding: {
|
||||
primary: KeyMod.Alt | KeyCode.DownArrow,
|
||||
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -969,7 +989,12 @@ registerAction2(class extends Action2 {
|
||||
id: COPY_CELL_UP_COMMAND_ID,
|
||||
title: localize('notebookActions.copyCellUp', "Copy Cell Up"),
|
||||
category: NOTEBOOK_ACTIONS_CATEGORY,
|
||||
f1: true
|
||||
f1: true,
|
||||
keybinding: {
|
||||
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow,
|
||||
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -992,7 +1017,12 @@ registerAction2(class extends Action2 {
|
||||
id: COPY_CELL_DOWN_COMMAND_ID,
|
||||
title: localize('notebookActions.copyCellDown', "Copy Cell Down"),
|
||||
category: NOTEBOOK_ACTIONS_CATEGORY,
|
||||
f1: true
|
||||
f1: true,
|
||||
keybinding: {
|
||||
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow,
|
||||
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
overflow: visible !important;
|
||||
} */
|
||||
|
||||
.monaco-workbench .part.editor > .content .notebook-editor .simple-fr-find-part-wrapper.visible {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .overflowingContentWidgets > div {
|
||||
z-index: 600 !important;
|
||||
/* @rebornix: larger than the editor title bar */
|
||||
|
||||
@@ -110,7 +110,6 @@ export interface ICellViewModel {
|
||||
resolveTextModel(): Promise<ITextModel>;
|
||||
getEvaluatedMetadata(documentMetadata: NotebookDocumentMetadata | undefined): NotebookCellMetadata;
|
||||
getSelectionsStartPosition(): IPosition[] | undefined;
|
||||
getLinesContent(): string[];
|
||||
}
|
||||
|
||||
export interface IEditableCellViewModel extends ICellViewModel {
|
||||
|
||||
@@ -47,6 +47,7 @@ import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor
|
||||
import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { IReadonlyTextBuffer } from 'vs/editor/common/model';
|
||||
|
||||
const $ = DOM.$;
|
||||
const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState';
|
||||
@@ -170,19 +171,20 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private updateEditorFocus() {
|
||||
// Note - focus going to the webview will fire 'blur', but the webview element will be
|
||||
// a descendent of the notebook editor root.
|
||||
this.editorFocus?.set(DOM.isAncestor(document.activeElement, this.getDomNode()));
|
||||
}
|
||||
|
||||
protected createEditor(parent: HTMLElement): void {
|
||||
this._rootElement = DOM.append(parent, $('.notebook-editor'));
|
||||
this.createBody(this._rootElement);
|
||||
this.generateFontInfo();
|
||||
this.editorFocus = NOTEBOOK_EDITOR_FOCUSED.bindTo(this.contextKeyService);
|
||||
this.editorFocus.set(true);
|
||||
this._register(this.onDidFocus(() => {
|
||||
this.editorFocus?.set(true);
|
||||
}));
|
||||
|
||||
this._register(this.onDidBlur(() => {
|
||||
this.editorFocus?.set(false);
|
||||
}));
|
||||
this._register(this.onDidFocus(() => this.updateEditorFocus()));
|
||||
this._register(this.onDidBlur(() => this.updateEditorFocus()));
|
||||
|
||||
this.editorEditable = NOTEBOOK_EDITOR_EDITABLE.bindTo(this.contextKeyService);
|
||||
this.editorEditable.set(true);
|
||||
@@ -307,6 +309,8 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
|
||||
|
||||
this.control = new NotebookCodeEditors(this.list, this.renderedEditors);
|
||||
this.webview = this.instantiationService.createInstance(BackLayerWebView, this);
|
||||
this.webview.webview.onDidBlur(() => this.updateEditorFocus());
|
||||
this.webview.webview.onDidFocus(() => this.updateEditorFocus());
|
||||
this._register(this.webview.onMessage(message => {
|
||||
if (this.viewModel) {
|
||||
this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.viewModel.uri, message);
|
||||
@@ -778,11 +782,6 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
|
||||
return newCell;
|
||||
}
|
||||
|
||||
private isAtEOL(p: IPosition, lines: string[]) {
|
||||
const line = lines[p.lineNumber - 1];
|
||||
return line.length + 1 === p.column;
|
||||
}
|
||||
|
||||
private pushIfAbsent(positions: IPosition[], p: IPosition) {
|
||||
const last = positions.length > 0 ? positions[positions.length - 1] : undefined;
|
||||
if (!last || last.lineNumber !== p.lineNumber || last.column !== p.column) {
|
||||
@@ -795,8 +794,12 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
|
||||
* Move end of line split points to the beginning of the next line;
|
||||
* Avoid duplicate split points
|
||||
*/
|
||||
private splitPointsToBoundaries(splitPoints: IPosition[], lines: string[]): IPosition[] | null {
|
||||
private splitPointsToBoundaries(splitPoints: IPosition[], textBuffer: IReadonlyTextBuffer): IPosition[] | null {
|
||||
const boundaries: IPosition[] = [];
|
||||
const lineCnt = textBuffer.getLineCount();
|
||||
const getLineLen = (lineNumber: number) => {
|
||||
return textBuffer.getLineLength(lineNumber);
|
||||
};
|
||||
|
||||
// split points need to be sorted
|
||||
splitPoints = splitPoints.sort((l, r) => {
|
||||
@@ -809,22 +812,21 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
|
||||
this.pushIfAbsent(boundaries, new Position(1, 1));
|
||||
|
||||
for (let sp of splitPoints) {
|
||||
if (this.isAtEOL(sp, lines) && sp.lineNumber < lines.length) {
|
||||
if (getLineLen(sp.lineNumber) + 1 === sp.column && sp.lineNumber < lineCnt) {
|
||||
sp = new Position(sp.lineNumber + 1, 1);
|
||||
}
|
||||
this.pushIfAbsent(boundaries, sp);
|
||||
}
|
||||
|
||||
// eat-up any split point at the beginning, i.e. we ignore the split point at the very end
|
||||
this.pushIfAbsent(boundaries, new Position(lines.length, lines[lines.length - 1].length + 1));
|
||||
this.pushIfAbsent(boundaries, new Position(lineCnt, getLineLen(lineCnt) + 1));
|
||||
|
||||
// if we only have two then they describe the whole range and nothing needs to be split
|
||||
return boundaries.length > 2 ? boundaries : null;
|
||||
}
|
||||
|
||||
private computeCellLinesContents(cell: IEditableCellViewModel, splitPoints: IPosition[]): string[] | null {
|
||||
const lines = cell.getLinesContent();
|
||||
const rangeBoundaries = this.splitPointsToBoundaries(splitPoints, lines);
|
||||
const rangeBoundaries = this.splitPointsToBoundaries(splitPoints, cell.textBuffer);
|
||||
if (!rangeBoundaries) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions, IMoveResult, IRevertOptions } from 'vs/workbench/common/editor';
|
||||
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { isEqual, basename } from 'vs/base/common/resources';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
|
||||
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
|
||||
export class NotebookEditorInput extends EditorInput {
|
||||
|
||||
@@ -39,7 +40,10 @@ export class NotebookEditorInput extends EditorInput {
|
||||
public name: string,
|
||||
public readonly viewType: string | undefined,
|
||||
@INotebookService private readonly notebookService: INotebookService,
|
||||
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService
|
||||
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
|
||||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
// @IEditorService private readonly editorService: IEditorService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -85,6 +89,48 @@ export class NotebookEditorInput extends EditorInput {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
|
||||
if (!this.textModel) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const dialogPath = this.textModel.resource;
|
||||
const target = await this.fileDialogService.pickFileToSave(dialogPath, options?.availableFileSystems);
|
||||
if (!target) {
|
||||
return undefined; // save cancelled
|
||||
}
|
||||
|
||||
if (!await this.textModel.saveAs(target)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this._move(group, target)?.editor;
|
||||
}
|
||||
|
||||
move(group: GroupIdentifier, target: URI): IMoveResult | undefined {
|
||||
if (this.textModel) {
|
||||
const contributedNotebookProviders = this.notebookService.getContributedNotebookProviders(target);
|
||||
|
||||
if (contributedNotebookProviders.find(provider => provider.id === this.textModel!.viewType)) {
|
||||
return this._move(group, target);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
_move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined {
|
||||
const editorInput = NotebookEditorInput.getOrCreate(this.instantiationService, newResource, basename(newResource), this.viewType);
|
||||
return { editor: editorInput };
|
||||
}
|
||||
|
||||
async revert(group: GroupIdentifier, options?: IRevertOptions): Promise<void> {
|
||||
if (this.textModel) {
|
||||
await this.textModel.revert(options);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async resolve(): Promise<NotebookEditorModel> {
|
||||
if (!await this.notebookService.canResolve(this.viewType!)) {
|
||||
throw new Error(`Cannot open notebook of type '${this.viewType}'`);
|
||||
@@ -96,6 +142,10 @@ export class NotebookEditorInput extends EditorInput {
|
||||
this._onDidChangeDirty.fire();
|
||||
}));
|
||||
|
||||
if (this.textModel.isDirty()) {
|
||||
this._onDidChangeDirty.fire();
|
||||
}
|
||||
|
||||
return this.textModel;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { notebookProviderExtensionPoint, notebookRendererExtensionPoint } from '
|
||||
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
|
||||
import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo, NotebookDocumentMetadata, CellEditType, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
@@ -202,13 +202,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu
|
||||
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||
}
|
||||
|
||||
async resolveNotebook(viewType: string, uri: URI): Promise<NotebookTextModel | undefined> {
|
||||
async createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[]): Promise<NotebookTextModel | undefined> {
|
||||
const provider = this._notebookProviders.get(viewType);
|
||||
if (!provider) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const notebookModel = await provider.controller.resolveNotebook(viewType, uri);
|
||||
const notebookModel = await provider.controller.createNotebook(viewType, uri, true, false);
|
||||
if (!notebookModel) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -219,6 +219,39 @@ export class NotebookService extends Disposable implements INotebookService, ICu
|
||||
notebookModel,
|
||||
(model) => this._onWillDispose(model),
|
||||
);
|
||||
this._models[modelId] = modelData;
|
||||
|
||||
notebookModel.metadata = metadata;
|
||||
notebookModel.languages = languages;
|
||||
|
||||
notebookModel.applyEdit(notebookModel.versionId, [
|
||||
{
|
||||
editType: CellEditType.Insert,
|
||||
index: 0,
|
||||
cells: cells
|
||||
}
|
||||
]);
|
||||
|
||||
return modelData.model;
|
||||
}
|
||||
|
||||
async resolveNotebook(viewType: string, uri: URI, forceReload: boolean): Promise<NotebookTextModel | undefined> {
|
||||
const provider = this._notebookProviders.get(viewType);
|
||||
if (!provider) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let notebookModel: NotebookTextModel | undefined;
|
||||
|
||||
notebookModel = await provider.controller.createNotebook(viewType, uri, false, forceReload);
|
||||
|
||||
// new notebook model created
|
||||
const modelId = MODEL_ID(uri);
|
||||
const modelData = new ModelData(
|
||||
notebookModel!,
|
||||
(model) => this._onWillDispose(model),
|
||||
);
|
||||
|
||||
this._models[modelId] = modelData;
|
||||
return modelData.model;
|
||||
}
|
||||
@@ -265,7 +298,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu
|
||||
let provider = this._notebookProviders.get(viewType);
|
||||
|
||||
if (provider) {
|
||||
provider.controller.destoryNotebookDocument(notebook);
|
||||
provider.controller.removeNotebookDocument(notebook);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +324,16 @@ export class NotebookService extends Disposable implements INotebookService, ICu
|
||||
return false;
|
||||
}
|
||||
|
||||
async saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise<boolean> {
|
||||
let provider = this._notebookProviders.get(viewType);
|
||||
|
||||
if (provider) {
|
||||
return provider.controller.saveAs(resource, target, token);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onDidReceiveMessage(viewType: string, uri: URI, message: any): void {
|
||||
let provider = this._notebookProviders.get(viewType);
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
|
||||
|
||||
for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
|
||||
const cell = this.element(i);
|
||||
if (this._viewModel!.hasCell(cell)) {
|
||||
if (this._viewModel!.hasCell(cell.handle)) {
|
||||
hideOutputs.push(...cell?.model.outputs);
|
||||
} else {
|
||||
deletedOutputs.push(...cell?.model.outputs);
|
||||
@@ -177,7 +177,7 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
|
||||
|
||||
for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
|
||||
const cell = this.element(i);
|
||||
if (this._viewModel!.hasCell(cell)) {
|
||||
if (this._viewModel!.hasCell(cell.handle)) {
|
||||
hideOutputs.push(...cell?.model.outputs);
|
||||
} else {
|
||||
deletedOutputs.push(...cell?.model.outputs);
|
||||
@@ -299,7 +299,7 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
|
||||
|
||||
for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
|
||||
const cell = this.element(i);
|
||||
if (this._viewModel!.hasCell(cell)) {
|
||||
if (this._viewModel!.hasCell(cell.handle)) {
|
||||
hideOutputs.push(...cell?.model.outputs);
|
||||
} else {
|
||||
deletedOutputs.push(...cell?.model.outputs);
|
||||
@@ -316,6 +316,18 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
|
||||
splice2(start: number, deleteCount: number, elements: CellViewModel[] = []): void {
|
||||
// we need to convert start and delete count based on hidden ranges
|
||||
super.splice(start, deleteCount, elements);
|
||||
|
||||
const selectionsLeft = [];
|
||||
this._viewModel!.selectionHandles.forEach(handle => {
|
||||
if (this._viewModel!.hasCell(handle)) {
|
||||
selectionsLeft.push(handle);
|
||||
}
|
||||
});
|
||||
|
||||
if (!selectionsLeft.length && this._viewModel!.viewCells) {
|
||||
// after splice, the selected cells are deleted
|
||||
this._viewModel!.selectionHandles = [this._viewModel!.viewCells[0].handle];
|
||||
}
|
||||
}
|
||||
|
||||
getViewIndex(cell: ICellViewModel) {
|
||||
|
||||
@@ -250,7 +250,6 @@ export class StatefullMarkdownCell extends Disposable {
|
||||
bindEditorListeners(model: ITextModel, dimension?: IDimension) {
|
||||
this.localDisposables.add(model.onDidChangeContent(() => {
|
||||
// we don't need to update view cell text anymore as the textbuffer is shared
|
||||
// this.viewCell.setText(model.getLinesContent());
|
||||
this.viewCell.clearHTML();
|
||||
let clientHeight = this.markdownContainer.clientHeight;
|
||||
this.markdownContainer.innerHTML = '';
|
||||
|
||||
@@ -205,25 +205,6 @@ export abstract class BaseCellViewModel extends Disposable {
|
||||
return this.model.getValue();
|
||||
}
|
||||
|
||||
getLinesContent(): string[] {
|
||||
if (this._textModel) {
|
||||
return this._textModel.getLinesContent();
|
||||
}
|
||||
|
||||
return this.model.textBuffer.getLinesContent();
|
||||
}
|
||||
|
||||
// setLinesContent(value: string[]) {
|
||||
// if (this._textModel) {
|
||||
// // TODO @rebornix we should avoid creating a new string here
|
||||
// return this._textModel.setValue(value.join('\n'));
|
||||
// } else {
|
||||
// const range = this.model.getFullModelRange();
|
||||
// this.model.textBuffer.
|
||||
// this.model.source = value;
|
||||
// }
|
||||
// }
|
||||
|
||||
private saveViewState(): void {
|
||||
if (!this._textEditor) {
|
||||
return;
|
||||
|
||||
@@ -268,7 +268,12 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
|
||||
});
|
||||
|
||||
diffs.reverse().forEach(diff => {
|
||||
this._viewCells.splice(diff[0], diff[1], ...diff[2]);
|
||||
const deletedCells = this._viewCells.splice(diff[0], diff[1], ...diff[2]);
|
||||
|
||||
deletedCells.forEach(cell => {
|
||||
this._handleToViewCellMapping.delete(cell.handle);
|
||||
});
|
||||
|
||||
diff[2].forEach(cell => {
|
||||
this._handleToViewCellMapping.set(cell.handle, cell);
|
||||
this._localStore.add(cell);
|
||||
@@ -456,8 +461,8 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
hasCell(cell: ICellViewModel) {
|
||||
return this._handleToViewCellMapping.has(cell.handle);
|
||||
hasCell(handle: number) {
|
||||
return this._handleToViewCellMapping.has(handle);
|
||||
}
|
||||
|
||||
getVersionId() {
|
||||
@@ -586,7 +591,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
|
||||
this._viewCells.splice(deleteIndex, 1);
|
||||
this._handleToViewCellMapping.delete(deleteCell.handle);
|
||||
|
||||
this._notebook.removeCell(deleteIndex);
|
||||
this._notebook.removeCell(deleteIndex, 1);
|
||||
this._onDidChangeViewCells.fire({ synchronous: true, splices: [[deleteIndex, 1, []]] });
|
||||
}
|
||||
|
||||
@@ -638,7 +643,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
|
||||
this._viewCells.splice(index, 1);
|
||||
this._handleToViewCellMapping.delete(viewCell.handle);
|
||||
|
||||
this._notebook.removeCell(index);
|
||||
this._notebook.removeCell(index, 1);
|
||||
|
||||
let endSelections: number[] = [];
|
||||
if (this.selectionHandles.length) {
|
||||
|
||||
@@ -7,7 +7,8 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
|
||||
import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { ITextSnapshot } from 'vs/editor/common/model';
|
||||
|
||||
function compareRangesUsingEnds(a: [number, number], b: [number, number]): number {
|
||||
if (a[1] === b[1]) {
|
||||
@@ -17,6 +18,49 @@ function compareRangesUsingEnds(a: [number, number], b: [number, number]): numbe
|
||||
return a[1] - b[1];
|
||||
}
|
||||
|
||||
export class NotebookTextModelSnapshot implements ITextSnapshot {
|
||||
// private readonly _pieces: Ce[] = [];
|
||||
private _index: number = -1;
|
||||
|
||||
constructor(private _model: NotebookTextModel) {
|
||||
// for (let i = 0; i < this._model.cells.length; i++) {
|
||||
// const cell = this._model.cells[i];
|
||||
// this._pieces.push(this._model.cells[i].textBuffer.createSnapshot(true));
|
||||
// }
|
||||
}
|
||||
|
||||
read(): string | null {
|
||||
|
||||
if (this._index === -1) {
|
||||
this._index++;
|
||||
return `{ "metadata": ${JSON.stringify(this._model.metadata)}, "languages": ${JSON.stringify(this._model.languages)}, "cells": [`;
|
||||
}
|
||||
|
||||
if (this._index < this._model.cells.length) {
|
||||
const cell = this._model.cells[this._index];
|
||||
|
||||
const data = {
|
||||
source: cell.getValue(),
|
||||
metadata: cell.metadata,
|
||||
cellKind: cell.cellKind,
|
||||
language: cell.language
|
||||
};
|
||||
|
||||
const rawStr = JSON.stringify(data);
|
||||
const isLastCell = this._index === this._model.cells.length - 1;
|
||||
|
||||
this._index++;
|
||||
return isLastCell ? rawStr : (rawStr + ',');
|
||||
} else if (this._index === this._model.cells.length) {
|
||||
this._index++;
|
||||
return `]}`;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class NotebookTextModel extends Disposable implements INotebookTextModel {
|
||||
private static _cellhandlePool: number = 0;
|
||||
|
||||
@@ -77,6 +121,18 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
|
||||
return new NotebookCellTextModel(cellUri, cellHandle, source, language, cellKind, outputs || [], metadata);
|
||||
}
|
||||
|
||||
initialize(cells: ICellDto2[]) {
|
||||
this.cells = [];
|
||||
this._versionId = 0;
|
||||
|
||||
const mainCells = cells.map(cell => {
|
||||
const cellHandle = NotebookTextModel._cellhandlePool++;
|
||||
const cellUri = CellUri.generate(this.uri, cellHandle);
|
||||
return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.cellKind, cell.outputs || [], cell.metadata);
|
||||
});
|
||||
this.insertNewCell(0, mainCells);
|
||||
}
|
||||
|
||||
applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[]): boolean {
|
||||
if (modelVersionId !== this._versionId) {
|
||||
return false;
|
||||
@@ -127,7 +183,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
|
||||
this.insertNewCell(insertEdit.index, mainCells);
|
||||
break;
|
||||
case CellEditType.Delete:
|
||||
this.removeCell(operations[i].index);
|
||||
this.removeCell(operations[i].index, operations[i].end - operations[i].start);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -142,6 +198,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
|
||||
return true;
|
||||
}
|
||||
|
||||
createSnapshot(preserveBOM?: boolean): ITextSnapshot {
|
||||
return new NotebookTextModelSnapshot(this);
|
||||
}
|
||||
|
||||
private _increaseVersionId(): void {
|
||||
this._versionId = this._versionId + 1;
|
||||
}
|
||||
@@ -250,17 +310,19 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
|
||||
return;
|
||||
}
|
||||
|
||||
removeCell(index: number) {
|
||||
removeCell(index: number, count: number) {
|
||||
this._isUntitled = false;
|
||||
|
||||
let cell = this.cells[index];
|
||||
this._cellListeners.get(cell.handle)?.dispose();
|
||||
this._cellListeners.delete(cell.handle);
|
||||
this.cells.splice(index, 1);
|
||||
for (let i = index; i < index + count; i++) {
|
||||
let cell = this.cells[i];
|
||||
this._cellListeners.get(cell.handle)?.dispose();
|
||||
this._cellListeners.delete(cell.handle);
|
||||
}
|
||||
this.cells.splice(index, count);
|
||||
this._onDidChangeContent.fire();
|
||||
|
||||
this._increaseVersionId();
|
||||
this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ModelChange, versionId: this._versionId, changes: [[index, 1, []]] });
|
||||
this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ModelChange, versionId: this._versionId, changes: [[index, count, []]] });
|
||||
}
|
||||
|
||||
moveCellToIdx(index: number, newIdx: number) {
|
||||
|
||||
@@ -273,7 +273,7 @@ export enum CellEditType {
|
||||
}
|
||||
|
||||
export interface ICellDto2 {
|
||||
source: string[];
|
||||
source: string | string[];
|
||||
language: string;
|
||||
cellKind: CellKind;
|
||||
outputs: IOutput[];
|
||||
@@ -300,6 +300,13 @@ export interface INotebookEditData {
|
||||
renderers: number[];
|
||||
}
|
||||
|
||||
export interface NotebookDataDto {
|
||||
readonly cells: ICellDto2[];
|
||||
readonly languages: string[];
|
||||
readonly metadata: NotebookDocumentMetadata;
|
||||
}
|
||||
|
||||
|
||||
export namespace CellUri {
|
||||
|
||||
export const scheme = 'vscode-notebook';
|
||||
|
||||
@@ -15,6 +15,8 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { IWorkingCopyService, IWorkingCopy, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { basename } from 'vs/base/common/resources';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||
import { DefaultEndOfLine, ITextBuffer, EndOfLinePreference } from 'vs/editor/common/model';
|
||||
|
||||
export interface INotebookEditorModelManager {
|
||||
models: NotebookEditorModel[];
|
||||
@@ -24,6 +26,13 @@ export interface INotebookEditorModelManager {
|
||||
get(resource: URI): NotebookEditorModel | undefined;
|
||||
}
|
||||
|
||||
export interface INotebookRevertOptions {
|
||||
/**
|
||||
* Go to disk bypassing any cache of the model if any.
|
||||
*/
|
||||
forceReadFromDisk?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export class NotebookEditorModel extends EditorModel implements IWorkingCopy, INotebookEditorModel {
|
||||
private _dirty = false;
|
||||
@@ -47,7 +56,8 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
|
||||
public readonly resource: URI,
|
||||
public readonly viewType: string,
|
||||
@INotebookService private readonly notebookService: INotebookService,
|
||||
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService
|
||||
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService,
|
||||
@IBackupFileService private readonly backupFileService: IBackupFileService
|
||||
) {
|
||||
super();
|
||||
this._register(this.workingCopyService.registerWorkingCopy(this));
|
||||
@@ -56,28 +66,91 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
|
||||
capabilities = 0;
|
||||
|
||||
async backup(): Promise<IWorkingCopyBackup> {
|
||||
return {};
|
||||
return { content: this._notebook.createSnapshot(true) };
|
||||
}
|
||||
|
||||
async revert(options?: IRevertOptions | undefined): Promise<void> {
|
||||
if (options?.soft) {
|
||||
await this.backupFileService.discardBackup(this.resource);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.load({ forceReadFromDisk: true });
|
||||
this._dirty = false;
|
||||
this._onDidChangeDirty.fire();
|
||||
return;
|
||||
}
|
||||
|
||||
async load(): Promise<NotebookEditorModel> {
|
||||
const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource);
|
||||
async load(options?: INotebookRevertOptions): Promise<NotebookEditorModel> {
|
||||
if (options?.forceReadFromDisk) {
|
||||
return this.loadFromProvider(true);
|
||||
}
|
||||
if (this.isResolved()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
const backup = await this.backupFileService.resolve(this.resource);
|
||||
|
||||
if (this.isResolved()) {
|
||||
return this; // Make sure meanwhile someone else did not succeed in loading
|
||||
}
|
||||
|
||||
if (backup) {
|
||||
try {
|
||||
return await this.loadFromBackup(backup.value.create(DefaultEndOfLine.LF));
|
||||
} catch (error) {
|
||||
// this.logService.error('[text file model] load() from backup', error); // ignore error and continue to load as file below
|
||||
}
|
||||
}
|
||||
|
||||
return this.loadFromProvider(false);
|
||||
}
|
||||
|
||||
private async loadFromBackup(content: ITextBuffer): Promise<NotebookEditorModel> {
|
||||
const fullRange = content.getRangeAt(0, content.getLength());
|
||||
const data = JSON.parse(content.getValueInRange(fullRange, EndOfLinePreference.LF));
|
||||
|
||||
const notebook = await this.notebookService.createNotebookFromBackup(this.viewType!, this.resource, data.metadata, data.languages, data.cells);
|
||||
this._notebook = notebook!;
|
||||
|
||||
this._name = basename(this._notebook!.uri);
|
||||
|
||||
this._register(this._notebook.onDidChangeContent(() => {
|
||||
this._dirty = true;
|
||||
this._onDidChangeDirty.fire();
|
||||
this.setDirty(true);
|
||||
this._onDidChangeContent.fire();
|
||||
}));
|
||||
|
||||
await this.backupFileService.discardBackup(this.resource);
|
||||
this.setDirty(true);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private async loadFromProvider(forceReloadFromDisk: boolean) {
|
||||
const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource, forceReloadFromDisk);
|
||||
this._notebook = notebook!;
|
||||
|
||||
this._name = basename(this._notebook!.uri);
|
||||
|
||||
this._register(this._notebook.onDidChangeContent(() => {
|
||||
this.setDirty(true);
|
||||
this._onDidChangeContent.fire();
|
||||
}));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
isResolved(): boolean {
|
||||
return !!this._notebook;
|
||||
}
|
||||
|
||||
setDirty(newState: boolean) {
|
||||
if (this._dirty !== newState) {
|
||||
this._dirty = newState;
|
||||
this._onDidChangeDirty.fire();
|
||||
}
|
||||
}
|
||||
|
||||
isDirty() {
|
||||
return this._dirty;
|
||||
}
|
||||
@@ -89,6 +162,14 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
|
||||
this._onDidChangeDirty.fire();
|
||||
return true;
|
||||
}
|
||||
|
||||
async saveAs(targetResource: URI): Promise<boolean> {
|
||||
const tokenSource = new CancellationTokenSource();
|
||||
await this.notebookService.saveAs(this.notebook.viewType, this.notebook.uri, targetResource, tokenSource.token);
|
||||
this._dirty = false;
|
||||
this._onDidChangeDirty.fire();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class NotebookEditorModelManager extends Disposable implements INotebookEditorModelManager {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
|
||||
import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
|
||||
@@ -17,12 +17,13 @@ import { INotebookEditorModelManager } from 'vs/workbench/contrib/notebook/commo
|
||||
export const INotebookService = createDecorator<INotebookService>('notebookService');
|
||||
|
||||
export interface IMainNotebookController {
|
||||
resolveNotebook(viewType: string, uri: URI): Promise<NotebookTextModel | undefined>;
|
||||
createNotebook(viewType: string, uri: URI, forBackup: boolean, forceReload: boolean): Promise<NotebookTextModel | undefined>;
|
||||
executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise<void>;
|
||||
onDidReceiveMessage(uri: URI, message: any): void;
|
||||
executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise<void>;
|
||||
destoryNotebookDocument(notebook: INotebookTextModel): Promise<void>;
|
||||
removeNotebookDocument(notebook: INotebookTextModel): Promise<void>;
|
||||
save(uri: URI, token: CancellationToken): Promise<boolean>;
|
||||
saveAs(uri: URI, target: URI, token: CancellationToken): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface INotebookService {
|
||||
@@ -35,7 +36,8 @@ export interface INotebookService {
|
||||
registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]): void;
|
||||
unregisterNotebookRenderer(handle: number): void;
|
||||
getRendererInfo(handle: number): INotebookRendererInfo | undefined;
|
||||
resolveNotebook(viewType: string, uri: URI): Promise<NotebookTextModel | undefined>;
|
||||
resolveNotebook(viewType: string, uri: URI, forceReload: boolean): Promise<NotebookTextModel | undefined>;
|
||||
createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[]): Promise<NotebookTextModel | undefined>;
|
||||
executeNotebook(viewType: string, uri: URI): Promise<void>;
|
||||
executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise<void>;
|
||||
|
||||
@@ -45,6 +47,7 @@ export interface INotebookService {
|
||||
destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void;
|
||||
updateActiveNotebookDocument(viewType: string, resource: URI): void;
|
||||
save(viewType: string, resource: URI, token: CancellationToken): Promise<boolean>;
|
||||
saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise<boolean>;
|
||||
onDidReceiveMessage(viewType: string, uri: URI, message: any): void;
|
||||
setToCopy(items: NotebookCellTextModel[]): void;
|
||||
getToCopy(): NotebookCellTextModel[] | undefined;
|
||||
|
||||
@@ -47,6 +47,7 @@ import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { MenuRegistry, MenuId, isIMenuItem } from 'vs/platform/actions/common/actions';
|
||||
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { preferencesEditIcon } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
|
||||
|
||||
const $ = DOM.$;
|
||||
|
||||
@@ -400,7 +401,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditorP
|
||||
}
|
||||
|
||||
private createRecordingBadge(container: HTMLElement): HTMLElement {
|
||||
const recordingBadge = DOM.append(container, DOM.$('.recording-badge.disabled'));
|
||||
const recordingBadge = DOM.append(container, DOM.$('.recording-badge.monaco-count-badge.long.disabled'));
|
||||
recordingBadge.textContent = localize('recording', "Recording Keys");
|
||||
this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder, badgeForeground }, colors => {
|
||||
const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
|
||||
@@ -891,7 +892,7 @@ class ActionsColumn extends Column {
|
||||
private createEditAction(keybindingItemEntry: IKeybindingItemEntry): IAction {
|
||||
const keybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_DEFINE);
|
||||
return <IAction>{
|
||||
class: 'codicon-edit',
|
||||
class: preferencesEditIcon.classNames,
|
||||
enabled: true,
|
||||
id: 'editKeybinding',
|
||||
tooltip: keybinding ? localize('editKeybindingLabelWithKey', "Change Keybinding {0}", `(${keybinding.getLabel()})`) : localize('editKeybindingLabel', "Change Keybinding"),
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
|
||||
.keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .recording-badge {
|
||||
margin-right: 8px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-header.small > .search-container > .keybindings-search-actions-container > .recording-badge,
|
||||
|
||||
@@ -40,12 +40,9 @@
|
||||
}
|
||||
|
||||
.settings-editor > .settings-header > .search-container > .settings-count-widget {
|
||||
margin: 6px 0px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
right: 35px;
|
||||
top: 0;
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-header > .search-container > .settings-clear-widget {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user