Allow for multiple editor parts (#193425)

* demo

* wip

* polish

* allow to toggle

* compile

* enable devtools

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* Avoid using global document, global window

* Fix exception caused by reading the size of the newly created window before it is available

* cleanup

* make it more useful

* apply zoom

* first cut editor parts

* cleanup

* scaffold some services

* preserve view state

* simple label distinction

* introduce accessor

* support dnd

* fix open editors view

* share window options

* cleanup

* 💄

* 💄

* 💄

* 💄

* cleanup on unload

* add todo

* stylescleanup

* avoid instanceof checks

* share more styles

* revert changes partially

* fix custom menus

* more alignment to main window

* codicon does not seem to be needed anymore

* no need for isHTMLElement

* fix icon error on macOS

* prevent `document.createElement`

* close child window when main window closes

* better active groups tracking

* cleanup

* pass along editor parts viewer

* eslint rule for instanceof checks

* add dom tests

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

---------

Co-authored-by: Alex Dima <alexdima@microsoft.com>
This commit is contained in:
Benjamin Pasero
2023-10-03 12:20:06 +02:00
committed by GitHub
parent 97cfd032a6
commit 73cc570f58
63 changed files with 1220 additions and 344 deletions

View File

@@ -12,7 +12,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import * as event from 'vs/base/common/event';
import * as dompurify from 'vs/base/browser/dompurify/dompurify';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { FileAccess, RemoteAuthorities, Schemas } from 'vs/base/common/network';
import * as platform from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
@@ -729,6 +729,25 @@ export function getActiveDocument(): Document {
return documents.find(doc => doc.hasFocus()) ?? document;
}
export function getActiveWindow(): Window & typeof globalThis {
const document = getActiveDocument();
return document.defaultView?.window ?? window;
}
function getWindow(e: unknown): Window & typeof globalThis {
const candidateNode = e as Node | undefined;
if (candidateNode?.ownerDocument?.defaultView) {
return candidateNode.ownerDocument.defaultView.window;
}
const candidateEvent = e as UIEvent | undefined;
if (candidateEvent?.view) {
return candidateEvent.view.window;
}
return window;
}
export function createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0], beforeAppend?: (style: HTMLStyleElement) => void): HTMLStyleElement {
const style = document.createElement('style');
style.type = 'text/css';
@@ -791,11 +810,24 @@ export function removeCSSRulesContainingSelector(ruleName: string, style: HTMLSt
}
}
export function isHTMLElement(o: any): o is HTMLElement {
if (typeof HTMLElement === 'object') {
return o instanceof HTMLElement;
}
return o && typeof o === 'object' && o.nodeType === 1 && typeof o.nodeName === 'string';
export function isMouseEvent(e: unknown): e is MouseEvent {
// eslint-disable-next-line no-restricted-syntax
return e instanceof MouseEvent || e instanceof getWindow(e).MouseEvent;
}
export function isKeyboardEvent(e: unknown): e is KeyboardEvent {
// eslint-disable-next-line no-restricted-syntax
return e instanceof KeyboardEvent || e instanceof getWindow(e).KeyboardEvent;
}
export function isPointerEvent(e: unknown): e is PointerEvent {
// eslint-disable-next-line no-restricted-syntax
return e instanceof PointerEvent || e instanceof getWindow(e).PointerEvent;
}
export function isDragEvent(e: unknown): e is DragEvent {
// eslint-disable-next-line no-restricted-syntax
return e instanceof DragEvent || e instanceof getWindow(e).DragEvent;
}
export const EventType = {
@@ -915,7 +947,7 @@ class FocusTracker extends Disposable implements IFocusTracker {
private _refreshStateHandler: () => void;
private static hasFocusWithin(element: HTMLElement | Window): boolean {
if (isHTMLElement(element)) {
if (element instanceof HTMLElement) {
const shadowRoot = getShadowRoot(element);
const activeElement = (shadowRoot ? shadowRoot.activeElement : element.ownerDocument.activeElement);
return isAncestor(activeElement, element);
@@ -1942,3 +1974,58 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia
function camelCaseToHyphenCase(str: string) {
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}
interface IObserver extends IDisposable {
readonly onDidChangeAttribute: event.Event<string>;
}
function observeAttributes(element: Element, filter?: string[]): IObserver {
const onDidChangeAttribute = new event.Emitter<string>();
const observer = new MutationObserver(mutations => {
for (const mutation of mutations) {
if (mutation.type === 'attributes' && mutation.attributeName) {
onDidChangeAttribute.fire(mutation.attributeName);
}
}
});
observer.observe(element, {
attributes: true,
attributeFilter: filter
});
return {
onDidChangeAttribute: onDidChangeAttribute.event,
dispose: () => {
observer.disconnect();
onDidChangeAttribute.dispose();
}
};
}
export function copyAttributes(from: Element, to: Element): void {
for (const { name, value } of from.attributes) {
to.setAttribute(name, value);
}
}
function copyAttribute(from: Element, to: Element, name: string): void {
const value = from.getAttribute(name);
if (value) {
to.setAttribute(name, value);
} else {
to.removeAttribute(name);
}
}
export function trackAttributes(from: Element, to: Element, filter?: string[]): IDisposable {
copyAttributes(from, to);
const observer = observeAttributes(from, filter);
return combinedDisposable(
observer,
observer.onDidChangeAttribute(name => copyAttribute(from, to, name))
);
}