├── prepare.js
├── src
├── typings
│ ├── css.d.ts
│ ├── thenable.d.ts
│ └── titlebar.d.ts
├── index.ts
├── mnemonic.ts
├── accelerators.ts
├── api.ts
├── vs
│ └── base
│ │ ├── common
│ │ ├── functional.ts
│ │ ├── process.ts
│ │ ├── uint.ts
│ │ ├── sequence.ts
│ │ ├── iterator.ts
│ │ ├── linkedList.ts
│ │ ├── network.ts
│ │ ├── cancellation.ts
│ │ ├── decorators.ts
│ │ ├── errors.ts
│ │ ├── platform.ts
│ │ ├── lifecycle.ts
│ │ ├── keyCodes.ts
│ │ └── arrays.ts
│ │ └── browser
│ │ ├── __mocks__
│ │ └── dom.js
│ │ ├── event.ts
│ │ ├── canIUse.ts
│ │ ├── iframe.ts
│ │ ├── browser.ts
│ │ ├── mouseEvent.ts
│ │ ├── keyboardEvent.ts
│ │ └── touch.ts
├── specs
│ ├── index.spec.js
│ └── menuitem.spec.js
├── themebar.ts
└── menuitem.ts
├── __mocks__
├── @treverix
│ └── remote.js
├── navigatorMock.js
└── electron.js
├── example
├── images
│ └── icon.png
├── renderer.js
├── index.html
├── preload.js
└── main.js
├── screenshots
├── window_1.png
├── window_2.png
└── window_3.png
├── dev-example
├── images
│ └── icon.png
├── renderer.js
├── index.html
├── preload.js
└── main.js
├── .idea
├── vcs.xml
├── modules.xml
├── custom-electron-titlebar.iml
└── misc.xml
├── README-MAC
├── jest.config.js
├── .babelrc
├── .npmignore
├── .github
└── ISSUE_TEMPLATE
│ └── issue-report.md
├── tsconfig.json
├── .gitignore
├── LICENSE
├── package.json
├── static
└── theme
│ ├── win.css
│ ├── mac.css
│ └── common.css
└── README.md
/prepare.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/typings/css.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css' {
2 | const value: string;
3 | export = value
4 | }
5 |
--------------------------------------------------------------------------------
/__mocks__/@treverix/remote.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | Menu: {
3 | getApplicationMenu: () => {}
4 | }
5 | }
--------------------------------------------------------------------------------
/example/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Treverix/custom-electron-titlebar/HEAD/example/images/icon.png
--------------------------------------------------------------------------------
/screenshots/window_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Treverix/custom-electron-titlebar/HEAD/screenshots/window_1.png
--------------------------------------------------------------------------------
/screenshots/window_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Treverix/custom-electron-titlebar/HEAD/screenshots/window_2.png
--------------------------------------------------------------------------------
/screenshots/window_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Treverix/custom-electron-titlebar/HEAD/screenshots/window_3.png
--------------------------------------------------------------------------------
/dev-example/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Treverix/custom-electron-titlebar/HEAD/dev-example/images/icon.png
--------------------------------------------------------------------------------
/__mocks__/navigatorMock.js:
--------------------------------------------------------------------------------
1 | Object.defineProperty(global, 'navigator', {
2 | writable: true,
3 | value: {
4 | userAgent: 'Chrome',
5 | }
6 | })
--------------------------------------------------------------------------------
/__mocks__/electron.js:
--------------------------------------------------------------------------------
1 | const browserWindow = {
2 | id: 42
3 | }
4 |
5 | module.exports =
6 | {
7 | remote: {
8 | getCurrentWindow: jest.fn(() => browserWindow)
9 | }
10 | }
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/example/renderer.js:
--------------------------------------------------------------------------------
1 | // This file is required by the index.html file and will
2 | // be executed in the renderer process for that window.
3 | // No Node.js APIs are available in this process because
4 | // `nodeIntegration` is turned off. Use `preload.js` to
5 | // selectively enable features needed in the rendering
6 | // process.
7 |
--------------------------------------------------------------------------------
/dev-example/renderer.js:
--------------------------------------------------------------------------------
1 | // This file is required by the index.html file and will
2 | // be executed in the renderer process for that window.
3 | // No Node.js APIs are available in this process because
4 | // `nodeIntegration` is turned off. Use `preload.js` to
5 | // selectively enable features needed in the rendering
6 | // process.
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/README-MAC:
--------------------------------------------------------------------------------
1 | To maintain this library on MacOS
2 |
3 | 1. open platform.ts
4 | 2. uncomment lines 109, 110 - comment line 111, 112
5 |
6 | Reason: the title bar is not visible on MacOS by default. This hack makes it visible and so we can test and maintain it
7 |
8 | WARNING!!
9 |
10 | remove the change before releasing, clear the dist folder and rebuild.
11 |
--------------------------------------------------------------------------------
/.idea/custom-electron-titlebar.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest/presets/js-with-babel',
3 | testEnvironment: 'jsdom',
4 | setupFilesAfterEnv: ['/prepare.js'],
5 | moduleNameMapper: {
6 | '^vs/(.*)$': '/src/vs/$1',
7 | '^static/(.*)$': '/static/$1'
8 | },
9 | globals: {
10 | 'ts-jest': {
11 | babelConfig: true,
12 | }
13 | },
14 | };
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /*-------------------------------------------------------------------------------------------
2 | * Copyright (c) 2018 Alex Torres
3 | * Licensed under the MIT License. See License in the project root for license information.
4 | *------------------------------------------------------------------------------------------*/
5 |
6 | export * from './titlebar';
7 | export * from './themebar';
8 | export * from './vs/base/common/color';
9 | export * from './vs/base/common/platform';
10 |
--------------------------------------------------------------------------------
/src/mnemonic.ts:
--------------------------------------------------------------------------------
1 | export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/;
2 | export const MENU_ESCAPED_MNEMONIC_REGEX = /(&)?(&)([^\s&])/g;
3 |
4 | export function cleanMnemonic(label: string): string {
5 | const regex = MENU_MNEMONIC_REGEX;
6 |
7 | const matches = regex.exec(label);
8 | if (!matches) {
9 | return label;
10 | }
11 |
12 | const mnemonicInText = !matches[1];
13 |
14 | return label.replace(regex, mnemonicInText ? '$2$3' : '').trim();
15 | }
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "rewire",
4 | [
5 | "module-resolver",
6 | {
7 | "root": [
8 | "./dist"
9 | ],
10 | "alias": {
11 | "vs": "./dist/vs",
12 | "static": "./static"
13 | },
14 | "extensions": [
15 | ".js"
16 | ]
17 | }
18 | ],
19 | [
20 | "import-require-as-string",
21 | {
22 | "extensions": [
23 | ".css"
24 | ]
25 | }
26 | ]
27 | ]
28 | }
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .git/
2 |
3 | # for WebStorm
4 | .idea/
5 |
6 | # for npm
7 | node_modules/
8 | npm-shrinkwrap.json
9 |
10 | # for grunt
11 | typings/
12 | .tscache/
13 | espowered/
14 |
15 | # for https://github.com/Microsoft/TypeScript/issues/4667
16 | lib/**/*.ts
17 | !lib/**/*.d.ts
18 |
19 | # codes
20 | test/
21 |
22 | # misc
23 | example/
24 | screenshots/
25 | dev-example/
26 | src/
27 | .gitignore
28 | .editorconfig
29 | circle.yml
30 | setup.sh
31 | Gruntfile.js
32 | tslint.json
33 | tsconfig.json
34 |
35 | npm-debug.log
--------------------------------------------------------------------------------
/src/accelerators.ts:
--------------------------------------------------------------------------------
1 | import {Accelerator} from "electron";
2 | import {isMacintosh} from "vs/base/common/platform";
3 |
4 | export const convertAccelerator = (electronAccelerator: Accelerator): string => {
5 | let vsAccelerator;
6 | if (!isMacintosh) {
7 | vsAccelerator = electronAccelerator.replace(/(Cmd)|(Command)/gi, '');
8 | } else {
9 | vsAccelerator = electronAccelerator.replace(/(Ctrl)|(Control)/gi, '');
10 | }
11 |
12 | vsAccelerator = vsAccelerator.replace(/(Or)/gi, '');
13 |
14 | return vsAccelerator;
15 | }
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Hello World!
7 |
12 |
13 |
14 |
15 | Hello World!
16 | We are using Node.js ,
17 | Chromium ,
18 | and Electron .
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/dev-example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Hello World!
7 |
12 |
13 |
14 |
15 | Hello World!
16 | We are using Node.js ,
17 | Chromium ,
18 | and Electron .
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/api.ts:
--------------------------------------------------------------------------------
1 | import {Color} from "vs/base/common/color";
2 |
3 | export interface IMenuItem {
4 | render(element: HTMLElement): void;
5 |
6 | isEnabled(): boolean;
7 |
8 | isSeparator(): boolean;
9 |
10 | focus(): void;
11 |
12 | blur(): void;
13 |
14 | dispose(): void;
15 | }
16 |
17 | export interface IMenuOptions {
18 | icon?: boolean;
19 | label?: boolean;
20 | ariaLabel?: string;
21 | enableMnemonics?: boolean;
22 | }
23 |
24 | export interface IMenuStyle {
25 | foregroundColor?: Color;
26 | backgroundColor?: Color;
27 | selectionForegroundColor?: Color;
28 | selectionBackgroundColor?: Color;
29 | separatorColor?: Color;
30 | }
--------------------------------------------------------------------------------
/src/vs/base/common/functional.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | export function once(this: unknown, fn: T): T {
7 | const _this = this;
8 | let didCall = false;
9 | let result: unknown;
10 |
11 | return function () {
12 | if (didCall) {
13 | return result;
14 | }
15 |
16 | didCall = true;
17 | result = fn.apply(_this, arguments);
18 |
19 | return result;
20 | } as unknown as T;
21 | }
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Issue report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. Windows]
28 | - Electron version [e.g. 4.0.0]
29 | - Node Version [e.g. 2.2]
30 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "ES2020", "DOM"
5 | ],
6 | "baseUrl": "./src",
7 | "paths": {
8 | "vs/*": [
9 | "./vs/*"
10 | ],
11 | "static/*": [
12 | "../static/*"
13 | ]
14 | },
15 | "module": "commonjs",
16 | // "noEmit": true,
17 | "target": "es2019",
18 | "declaration": true,
19 | "outDir": "./dist",
20 | "moduleResolution": "node",
21 | "strict": true,
22 | "noImplicitAny": true,
23 | "esModuleInterop": true,
24 | "skipLibCheck": true,
25 | "forceConsistentCasingInFileNames": true,
26 | "experimentalDecorators": true
27 | },
28 | "include": [
29 | "src/**/*"
30 | ],
31 | "exclude": [
32 | "./node_modules",
33 | "**/node_modules/*"
34 | ]
35 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 | .nyc_output
17 | coverage.*
18 |
19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
20 | .grunt
21 |
22 | # node-waf configuration
23 | .lock-wscript
24 |
25 | # Compiled binary addons (http://nodejs.org/api/addons.html)
26 | build/Release
27 |
28 | # Dependency directory
29 | node_modules
30 |
31 | # Optional npm cache directory
32 | .npm
33 |
34 | # Optional REPL history
35 | .node_repl_history
36 |
37 | # IDE
38 | .idea/
39 |
40 | lib/
41 | dist/
42 | *.map
43 | package-lock.json
44 | NOTES.md
--------------------------------------------------------------------------------
/src/vs/base/browser/__mocks__/dom.js:
--------------------------------------------------------------------------------
1 | // import {
2 | // EventType,
3 | // addDisposableListener,
4 | // addClass,
5 | // removeClass,
6 | // removeNode,
7 | // append,
8 | // $,
9 | // hasClass,
10 | // EventHelper,
11 | // EventLike
12 | // } from "vs/base/browser/dom";
13 | console.log('mocking...')
14 | module.exports = {
15 | EventType : {},
16 | EventHelper : {},
17 | EventLike : {},
18 | addDisposableListener : jest.fn(),
19 | addClass : jest.fn(),
20 | removeClass : jest.fn(),
21 | removeNode : jest.fn(),
22 | append : jest.fn(),
23 | $ : jest.fn(),
24 | hasClass : jest.fn(),
25 | }
26 |
27 |
28 | export const addDisposableListener = jest.fn();
29 | export const addClass = jest.fn();
30 | export const removeClass = jest.fn();
31 | export const removeNode = jest.fn();
32 | export const append = jest.fn();
33 | export const $ = jest.fn();
34 | export const hasClass = jest.fn();
35 |
--------------------------------------------------------------------------------
/src/specs/index.spec.js:
--------------------------------------------------------------------------------
1 | const customElectronTitlebar = require('../index');
2 |
3 | jest.mock('@treverix/remote');
4 |
5 | describe('custom-electron-titlebar', () => {
6 | it('should export the Titlebar class', () => {
7 | expect (typeof customElectronTitlebar.Titlebar).toBe('function');
8 | })
9 |
10 | it('should export the Themebar class', () => {
11 | expect (typeof customElectronTitlebar.Themebar).toBe('function');
12 | })
13 |
14 | it('should export the RGBA class', () => {
15 | expect (typeof customElectronTitlebar.RGBA).toBe('function');
16 | })
17 |
18 | it('should export the HSLA class', () => {
19 | expect (typeof customElectronTitlebar.HSLA).toBe('function');
20 | })
21 |
22 | it('should export the HSVA class', () => {
23 | expect (typeof customElectronTitlebar.HSLA).toBe('function');
24 | })
25 |
26 | it('should export the Color class', () => {
27 | expect (typeof customElectronTitlebar.HSLA).toBe('function');
28 | })
29 | })
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Alex Torres, 2020 Andreas Dolk
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/vs/base/common/process.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { isWindows, isMacintosh, setImmediate, IProcessEnvironment } from 'vs/base/common/platform';
7 |
8 | interface IProcess {
9 | platform: string;
10 | env: IProcessEnvironment;
11 |
12 | cwd(): string;
13 | nextTick(callback: (...args: any[]) => void): void;
14 | }
15 |
16 | declare const process: IProcess;
17 | const safeProcess: IProcess = (typeof process === 'undefined') ? {
18 | cwd(): string { return '/'; },
19 | env: Object.create(null),
20 | get platform(): string { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; },
21 | nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); }
22 | } : process;
23 |
24 | export const cwd = safeProcess.cwd;
25 | export const env = safeProcess.env;
26 | export const platform = safeProcess.platform;
27 | export const nextTick = safeProcess.nextTick;
28 |
--------------------------------------------------------------------------------
/src/typings/thenable.d.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | /**
7 | * Thenable is a common denominator between ES6 promises, Q, jquery.Deferred, WinJS.Promise,
8 | * and others. This API makes no assumption about what promise library is being used which
9 | * enables reusing existing code without migrating to a specific promise implementation. Still,
10 | * we recommend the use of native promises which are available in VS Code.
11 | */
12 | interface Thenable {
13 | /**
14 | * Attaches callbacks for the resolution and/or rejection of the Promise.
15 | * @param onfulfilled The callback to execute when the Promise is resolved.
16 | * @param onrejected The callback to execute when the Promise is rejected.
17 | * @returns A Promise for the completion of which ever callback is executed.
18 | */
19 | then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Thenable;
20 | then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => void): Thenable;
21 | }
--------------------------------------------------------------------------------
/src/vs/base/browser/event.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { Event as BaseEvent, Emitter } from 'vs/base/common/event';
7 |
8 | export type EventHandler = HTMLElement | HTMLDocument | Window;
9 |
10 | export interface IDomEvent {
11 | (element: EventHandler, type: K, useCapture?: boolean): BaseEvent;
12 | (element: EventHandler, type: string, useCapture?: boolean): BaseEvent;
13 | }
14 |
15 | export const domEvent: IDomEvent = (element: EventHandler, type: string, useCapture?: boolean) => {
16 | const fn = (e: Event) => emitter.fire(e);
17 | const emitter = new Emitter({
18 | onFirstListenerAdd: () => {
19 | element.addEventListener(type, fn, useCapture);
20 | },
21 | onLastListenerRemove: () => {
22 | element.removeEventListener(type, fn, useCapture);
23 | }
24 | });
25 |
26 | return emitter.event;
27 | };
28 |
29 | export interface CancellableEvent {
30 | preventDefault(): void;
31 | stopPropagation(): void;
32 | }
33 |
34 | export function stop(event: BaseEvent): BaseEvent {
35 | return BaseEvent.map(event, e => {
36 | e.preventDefault();
37 | e.stopPropagation();
38 | return e;
39 | });
40 | }
--------------------------------------------------------------------------------
/src/vs/base/common/uint.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | export const enum Constants {
7 | /**
8 | * MAX SMI (SMall Integer) as defined in v8.
9 | * one bit is lost for boxing/unboxing flag.
10 | * one bit is lost for sign flag.
11 | * See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
12 | */
13 | MAX_SAFE_SMALL_INTEGER = 1 << 30,
14 |
15 | /**
16 | * MIN SMI (SMall Integer) as defined in v8.
17 | * one bit is lost for boxing/unboxing flag.
18 | * one bit is lost for sign flag.
19 | * See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
20 | */
21 | MIN_SAFE_SMALL_INTEGER = -(1 << 30),
22 |
23 | /**
24 | * Max unsigned integer that fits on 8 bits.
25 | */
26 | MAX_UINT_8 = 255, // 2^8 - 1
27 |
28 | /**
29 | * Max unsigned integer that fits on 16 bits.
30 | */
31 | MAX_UINT_16 = 65535, // 2^16 - 1
32 |
33 | /**
34 | * Max unsigned integer that fits on 32 bits.
35 | */
36 | MAX_UINT_32 = 4294967295, // 2^32 - 1
37 |
38 | UNICODE_SUPPLEMENTARY_PLANE_BEGIN = 0x010000
39 | }
40 |
41 | export function toUint8(v: number): number {
42 | if (v < 0) {
43 | return 0;
44 | }
45 | if (v > Constants.MAX_UINT_8) {
46 | return Constants.MAX_UINT_8;
47 | }
48 | return v | 0;
49 | }
50 |
51 | export function toUint32(v: number): number {
52 | if (v < 0) {
53 | return 0;
54 | }
55 | if (v > Constants.MAX_UINT_32) {
56 | return Constants.MAX_UINT_32;
57 | }
58 | return v | 0;
59 | }
--------------------------------------------------------------------------------
/src/typings/titlebar.d.ts:
--------------------------------------------------------------------------------
1 | import {MenubarOptions} from "../menubar";
2 | import {Color} from "vs/base/common/color";
3 | import {Theme} from "../themebar";
4 |
5 | interface TitlebarOptions extends MenubarOptions {
6 | /**
7 | * The background color of titlebar.
8 | */
9 | backgroundColor: Color;
10 | /**
11 | * The icon shown on the left side of titlebar.
12 | */
13 | icon?: string;
14 | /**
15 | * Style of the icons of titlebar.
16 | * You can create your custom style using [`Theme`](https://github.com/AlexTorresSk/custom-electron-titlebar/THEMES.md)
17 | */
18 | iconsTheme: Theme;
19 | /**
20 | * The shadow color of titlebar.
21 | */
22 | shadow: boolean;
23 | /**
24 | * Define if the minimize window button is displayed.
25 | * *The default is true*
26 | */
27 | minimizable?: boolean;
28 | /**
29 | * Define if the maximize and restore window buttons are displayed.
30 | * *The default is true*
31 | */
32 | maximizable?: boolean;
33 | /**
34 | * Define if the close window button is displayed.
35 | * *The default is true*
36 | */
37 | closeable?: boolean;
38 | /**
39 | * When the close button is clicked, the window is hidden instead of closed.
40 | * *The default is false*
41 | */
42 | hideWhenClickingClose: boolean;
43 | /**
44 | * Enables or disables the blur option in titlebar.
45 | * *The default is true*
46 | */
47 | unfocusEffect?: boolean;
48 | /**
49 | * Set the order of the elements on the title bar. You can use `inverted`, `first-buttons` or don't add for.
50 | * *The default is normal*
51 | */
52 | order?: "inverted" | "first-buttons";
53 | /**
54 | * Set horizontal alignment of the window title.
55 | * *The default value is center*
56 | */
57 | titleHorizontalAlignment: "left" | "center" | "right";
58 | /**
59 | * Sets the value for the overflow of the window.
60 | * *The default value is auto*
61 | */
62 | overflow: "auto" | "hidden" | "visible";
63 | }
64 |
--------------------------------------------------------------------------------
/src/vs/base/browser/canIUse.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import * as browser from 'vs/base/browser/browser';
7 | import * as platform from 'vs/base/common/platform';
8 |
9 | export const enum KeyboardSupport {
10 | Always,
11 | FullScreen,
12 | None
13 | }
14 |
15 | /**
16 | * Browser feature we can support in current platform, browser and environment.
17 | */
18 | export const BrowserFeatures = {
19 | clipboard: {
20 | writeText: (
21 | platform.isNative
22 | || (document.queryCommandSupported && document.queryCommandSupported('copy'))
23 | || !!(navigator && navigator.clipboard && navigator.clipboard.writeText)
24 | ),
25 | readText: (
26 | platform.isNative
27 | || !!(navigator && navigator.clipboard && navigator.clipboard.readText)
28 | ),
29 | richText: (() => {
30 | if (browser.isEdge) {
31 | let index = navigator.userAgent.indexOf('Edge/');
32 | let version = parseInt(navigator.userAgent.substring(index + 5, navigator.userAgent.indexOf('.', index)), 10);
33 |
34 | if (!version || (version >= 12 && version <= 16)) {
35 | return false;
36 | }
37 | }
38 |
39 | return true;
40 | })()
41 | },
42 | keyboard: (() => {
43 | if (platform.isNative || browser.isStandalone) {
44 | return KeyboardSupport.Always;
45 | }
46 |
47 | if ((navigator).keyboard || browser.isSafari) {
48 | return KeyboardSupport.FullScreen;
49 | }
50 |
51 | return KeyboardSupport.None;
52 | })(),
53 |
54 | // 'ontouchstart' in window always evaluates to true with typescript's modern typings. This causes `window` to be
55 | // `never` later in `window.navigator`. That's why we need the explicit `window as Window` cast
56 | touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0,
57 | pointerEvents: window.PointerEvent && ('ontouchstart' in window || (window as Window).navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0)
58 | };
59 |
--------------------------------------------------------------------------------
/src/vs/base/common/sequence.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { Event, Emitter } from 'vs/base/common/event';
7 | import { IDisposable } from 'vs/base/common/lifecycle';
8 |
9 | export interface ISplice {
10 | readonly start: number;
11 | readonly deleteCount: number;
12 | readonly toInsert: T[];
13 | }
14 |
15 | export interface ISpliceable {
16 | splice(start: number, deleteCount: number, toInsert: T[]): void;
17 | }
18 |
19 | export interface ISequence {
20 | readonly elements: T[];
21 | readonly onDidSplice: Event>;
22 | }
23 |
24 | export class Sequence implements ISequence, ISpliceable {
25 |
26 | readonly elements: T[] = [];
27 |
28 | private readonly _onDidSplice = new Emitter>();
29 | readonly onDidSplice: Event> = this._onDidSplice.event;
30 |
31 | splice(start: number, deleteCount: number, toInsert: T[] = []): void {
32 | this.elements.splice(start, deleteCount, ...toInsert);
33 | this._onDidSplice.fire({ start, deleteCount, toInsert });
34 | }
35 | }
36 |
37 | export class SimpleSequence implements ISequence {
38 |
39 | private _elements: T[];
40 | get elements(): T[] { return this._elements; }
41 |
42 | readonly onDidSplice: Event>;
43 | private disposable: IDisposable;
44 |
45 | constructor(elements: T[], onDidAdd: Event, onDidRemove: Event) {
46 | this._elements = [...elements];
47 | this.onDidSplice = Event.any(
48 | Event.map(onDidAdd, e => ({ start: this.elements.length, deleteCount: 0, toInsert: [e] })),
49 | Event.map(Event.filter(Event.map(onDidRemove, e => this.elements.indexOf(e)), i => i > -1), i => ({ start: i, deleteCount: 1, toInsert: [] }))
50 | );
51 |
52 | this.disposable = this.onDidSplice(({ start, deleteCount, toInsert }) => this._elements.splice(start, deleteCount, ...toInsert));
53 | }
54 |
55 | dispose(): void {
56 | this.disposable.dispose();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/dev-example/preload.js:
--------------------------------------------------------------------------------
1 | // All of the Node.js APIs are available in the preload process.
2 | // It has the same sandbox as a Chrome extension.
3 |
4 | const path = require('path');
5 | const url = require('url');
6 | const {isMacintosh} = require('../dist')
7 | const {Menu, MenuItem} = require('@treverix/remote')
8 |
9 | window.addEventListener('DOMContentLoaded', () => {
10 |
11 | // It does not make sense to use the custom titlebar on macOS where
12 | // it only tries to simulate what we get with the normal behavior anyway.
13 | if (!isMacintosh) {
14 | const customTitlebar = require('../dist'); // for local library development and testing only
15 |
16 | // add a menu
17 | const menu = new Menu();
18 | menu.append(new MenuItem({
19 | label: 'Item &1',
20 | click: () => console.log('Click on submenu'),
21 | submenu: [
22 | {
23 | label: '&Subitem 1',
24 | click: () => console.log('Click on subitem 1')
25 | },
26 | {
27 | type: 'separator'
28 | }
29 | ]
30 | }));
31 |
32 | menu.append(new MenuItem({
33 | label: 'Item &2',
34 | submenu: [
35 | {
36 | label: 'Subitem &checkbox',
37 | type: 'checkbox',
38 | checked: true
39 | },
40 | {
41 | type: 'separator'
42 | },
43 | {
44 | label: 'Subitem with &submenu',
45 | submenu: [
46 | {
47 | label: 'Submenu &item 2',
48 | accelerator: 'Ctrl+T',
49 | click: () => console.log('Click on subitem 2')
50 | }
51 | ]
52 | }
53 | ]
54 | }));
55 |
56 | const titlebar = new customTitlebar.Titlebar({
57 | backgroundColor: customTitlebar.Color.fromHex('#2f3241'),
58 | icon: url.format(path.join(__dirname, '/images', '/icon.png')),
59 | menu,
60 | });
61 |
62 | titlebar.updateMenu(menu);
63 | }
64 |
65 | const replaceText = (selector, text) => {
66 | const element = document.getElementById(selector)
67 | if (element) element.innerText = text
68 | }
69 |
70 | for (const type of ['chrome', 'node', 'electron']) {
71 | replaceText(`${type}-version`, process.versions[type])
72 | }
73 | })
74 |
--------------------------------------------------------------------------------
/example/preload.js:
--------------------------------------------------------------------------------
1 | // All of the Node.js APIs are available in the preload process.
2 | // It has the same sandbox as a Chrome extension.
3 |
4 | const path = require('path');
5 | const url = require('url');
6 | const {isMacintosh} = require('@treverix/custom-electron-titlebar')
7 | const {Menu, MenuItem} = require('@electron/remote')
8 |
9 | window.addEventListener('DOMContentLoaded', () => {
10 |
11 | // It does not make sense to use the custom titlebar on macOS where
12 | // it only tries to simulate what we get with the normal behavior anyway.
13 | if (!isMacintosh) {
14 | const customTitlebar = require('@treverix/custom-electron-titlebar');
15 |
16 | // add a menu
17 | const menu = new Menu();
18 | menu.append(new MenuItem({
19 | label: 'Item &1',
20 | click: () => console.log('Click on submenu'),
21 | submenu: [
22 | {
23 | label: '&Subitem 1',
24 | click: () => console.log('Click on subitem 1')
25 | },
26 | {
27 | type: 'separator'
28 | }
29 | ]
30 | }));
31 |
32 | menu.append(new MenuItem({
33 | label: 'Item &2',
34 | submenu: [
35 | {
36 | label: 'Subitem &checkbox',
37 | type: 'checkbox',
38 | checked: true
39 | },
40 | {
41 | type: 'separator'
42 | },
43 | {
44 | label: 'Subitem with &submenu',
45 | submenu: [
46 | {
47 | label: 'Submenu &item 2',
48 | accelerator: 'Ctrl+T',
49 | click: () => console.log('Click on subitem 2')
50 | }
51 | ]
52 | }
53 | ]
54 | }));
55 |
56 | const titlebar = new customTitlebar.Titlebar({
57 | backgroundColor: customTitlebar.Color.fromHex('#2f3241'),
58 | icon: url.format(path.join(__dirname, '/images', '/icon.png')),
59 | menu,
60 | });
61 |
62 | titlebar.updateMenu(menu);
63 | }
64 |
65 | const replaceText = (selector, text) => {
66 | const element = document.getElementById(selector)
67 | if (element) element.innerText = text
68 | }
69 |
70 | for (const type of ['chrome', 'node', 'electron']) {
71 | replaceText(`${type}-version`, process.versions[type])
72 | }
73 | })
74 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@treverix/custom-electron-titlebar",
3 | "version": "4.2.0",
4 | "description": "Custom Electron Titlebar is a library for electron 10+ that allows you to configure a fully customizable title bar.",
5 | "main": "dist/index.js",
6 | "types": "dist/index.d.ts",
7 | "scripts": {
8 | "prepublishOnly": "npm run build",
9 | "build:test": "tsc && babel ./dist --out-dir ./dist --extensions \".js\" & ncp ./src ./dist --filter=\"^.*\\.js$\"",
10 | "build": "tsc && babel ./dist --out-dir ./dist --extensions \".js\"",
11 | "dev-example": "electron dev-example/main.js",
12 | "example": "electron example/main.js",
13 | "start": "npm run build && npm run dev-example",
14 | "test": "jest"
15 | },
16 | "author": "Andreas Dolk ",
17 | "contributors": [
18 | "Alex Torres "
19 | ],
20 | "license": "MIT",
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/Treverix/custom-electron-titlebar.git"
24 | },
25 | "keywords": [
26 | "typescript",
27 | "electron",
28 | "titlebar",
29 | "menubar",
30 | "windows",
31 | "linux"
32 | ],
33 | "bugs": {
34 | "url": "https://github.com/Treverix/custom-electron-titlebar/issues"
35 | },
36 | "homepage": "https://github.com/Treverix/custom-electron-titlebar#readme",
37 | "directories": {
38 | "example": "example",
39 | "lib": "lib"
40 | },
41 | "dependencies": {
42 | "@electron/remote": "^2.0.1"
43 | },
44 | "devDependencies": {
45 | "@babel/cli": "^7.15.7",
46 | "@babel/core": "^7.15.5",
47 | "@babel/plugin-proposal-class-properties": "^7.14.5",
48 | "@babel/plugin-proposal-object-rest-spread": "^7.15.6",
49 | "@babel/preset-env": "^7.15.6",
50 | "@babel/preset-typescript": "^7.15.0",
51 | "@types/jest": "^27.0.2",
52 | "@types/rewire": "^2.5.28",
53 | "babel-plugin-const-enum": "^1.1.0",
54 | "babel-plugin-import-require-as-string": "^1.0.2",
55 | "babel-plugin-module-resolver": "^4.1.0",
56 | "electron": "^14.0.2",
57 | "typescript": "^4.4.3",
58 | "babel-plugin-rewire": "^1.2.0",
59 | "jest": "^27.2.4",
60 | "ncp": "^2.0.0",
61 | "rewire": "^5.0.0",
62 | "ts-jest": "^27.0.5"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/example/main.js:
--------------------------------------------------------------------------------
1 | // Modules to control application life and create native browser window
2 | const { app, BrowserWindow } = require('electron')
3 | const path = require('path')
4 |
5 | // initialize the remote module
6 | require('@electron/remote/main').initialize()
7 |
8 | // Keep a global reference of the window object, if you don't, the window will
9 | // be closed automatically when the JavaScript object is garbage collected.
10 | let mainWindow
11 |
12 | function createWindow() {
13 | // Create the browser window.
14 | mainWindow = new BrowserWindow({
15 | width: 800,
16 | height: 600,
17 | // the custom title bar works best on frameless windows. But on macos, the custom titlebar is usually useless.
18 | frame: process.platform === 'darwin',
19 | webPreferences: {
20 | enableRemoteModule: true,
21 | preload: path.join(__dirname, 'preload.js'),
22 | nodeIntegration: true,
23 | }
24 | })
25 |
26 | // and load the index.html of the app.
27 | mainWindow.loadFile('index.html')
28 |
29 | // Open the DevTools.
30 | // mainWindow.webContents.openDevTools()
31 |
32 | // Emitted when the window is closed.
33 | mainWindow.on('closed', function () {
34 | // Dereference the window object, usually you would store windows
35 | // in an array if your app supports multi windows, this is the time
36 | // when you should delete the corresponding element.
37 | mainWindow = null
38 | })
39 | }
40 |
41 | // This method will be called when Electron has finished
42 | // initialization and is ready to create browser windows.
43 | // Some APIs can only be used after this event occurs.
44 | app.on('ready', createWindow)
45 |
46 | // Quit when all windows are closed.
47 | app.on('window-all-closed', function () {
48 | // On macOS it is common for applications and their menu bar
49 | // to stay active until the user quits explicitly with Cmd + Q
50 | if (process.platform !== 'darwin') app.quit()
51 | })
52 |
53 | app.on('activate', function () {
54 | // On macOS it's common to re-create a window in the app when the
55 | // dock icon is clicked and there are no other windows open.
56 | if (mainWindow === null) createWindow()
57 | })
58 |
59 | // In this file you can include the rest of your app's specific main process
60 | // code. You can also put them in separate files and require them here.
61 |
--------------------------------------------------------------------------------
/dev-example/main.js:
--------------------------------------------------------------------------------
1 | // Modules to control application life and create native browser window
2 | const { app, BrowserWindow } = require('electron')
3 | const path = require('path')
4 |
5 | // initialize the remote module
6 | require('@treverix/remote/main').initialize()
7 |
8 | // Keep a global reference of the window object, if you don't, the window will
9 | // be closed automatically when the JavaScript object is garbage collected.
10 | let mainWindow
11 |
12 | function createWindow() {
13 | // Create the browser window.
14 | mainWindow = new BrowserWindow({
15 | width: 800,
16 | height: 600,
17 | // the custom title bar works best on frameless windows. But on macos, the custom titlebar is usually useless.
18 | frame: process.platform === 'darwin',
19 | webPreferences: {
20 | enableRemoteModule: true,
21 | preload: path.join(__dirname, 'preload.js'),
22 | nodeIntegration: true,
23 | }
24 | })
25 |
26 | // and load the index.html of the app.
27 | mainWindow.loadFile('index.html')
28 |
29 | // Open the DevTools.
30 | // mainWindow.webContents.openDevTools()
31 |
32 | // Emitted when the window is closed.
33 | mainWindow.on('closed', function () {
34 | // Dereference the window object, usually you would store windows
35 | // in an array if your app supports multi windows, this is the time
36 | // when you should delete the corresponding element.
37 | mainWindow = null
38 | })
39 | }
40 |
41 | // This method will be called when Electron has finished
42 | // initialization and is ready to create browser windows.
43 | // Some APIs can only be used after this event occurs.
44 | app.on('ready', createWindow)
45 |
46 | // Quit when all windows are closed.
47 | app.on('window-all-closed', function () {
48 | // On macOS it is common for applications and their menu bar
49 | // to stay active until the user quits explicitly with Cmd + Q
50 | if (process.platform !== 'darwin') app.quit()
51 | })
52 |
53 | app.on('activate', function () {
54 | // On macOS it's common to re-create a window in the app when the
55 | // dock icon is clicked and there are no other windows open.
56 | if (mainWindow === null) createWindow()
57 | })
58 |
59 | // In this file you can include the rest of your app's specific main process
60 | // code. You can also put them in separate files and require them here.
61 |
--------------------------------------------------------------------------------
/src/vs/base/common/iterator.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | export namespace Iterable {
7 |
8 | export function is(thing: any): thing is IterableIterator {
9 | return thing && typeof thing === 'object' && typeof thing[Symbol.iterator] === 'function';
10 | }
11 |
12 | const _empty: Iterable = Object.freeze([]);
13 | export function empty(): Iterable {
14 | return _empty;
15 | }
16 |
17 | export function* single(element: T): Iterable {
18 | yield element;
19 | }
20 |
21 | export function from(iterable: Iterable | undefined | null): Iterable {
22 | return iterable || _empty;
23 | }
24 |
25 | export function first(iterable: Iterable): T | undefined {
26 | return iterable[Symbol.iterator]().next().value;
27 | }
28 |
29 | export function some(iterable: Iterable, predicate: (t: T) => boolean): boolean {
30 | for (const element of iterable) {
31 | if (predicate(element)) {
32 | return true;
33 | }
34 | }
35 | return false;
36 | }
37 |
38 | export function* filter(iterable: Iterable, predicate: (t: T) => boolean): Iterable {
39 | for (const element of iterable) {
40 | if (predicate(element)) {
41 | yield element;
42 | }
43 | }
44 | }
45 |
46 | export function* map(iterable: Iterable, fn: (t: T) => R): Iterable {
47 | for (const element of iterable) {
48 | yield fn(element);
49 | }
50 | }
51 |
52 | export function* concat(...iterables: Iterable[]): Iterable {
53 | for (const iterable of iterables) {
54 | for (const element of iterable) {
55 | yield element;
56 | }
57 | }
58 | }
59 |
60 | /**
61 | * Consumes `atMost` elements from iterable and returns the consumed elements,
62 | * and an iterable for the rest of the elements.
63 | */
64 | export function consume(iterable: Iterable, atMost: number = Number.POSITIVE_INFINITY): [T[], Iterable] {
65 | const consumed: T[] = [];
66 |
67 | if (atMost === 0) {
68 | return [consumed, iterable];
69 | }
70 |
71 | const iterator = iterable[Symbol.iterator]();
72 |
73 | for (let i = 0; i < atMost; i++) {
74 | const next = iterator.next();
75 |
76 | if (next.done) {
77 | return [consumed, Iterable.empty()];
78 | }
79 |
80 | consumed.push(next.value);
81 | }
82 |
83 | return [consumed, { [Symbol.iterator]() { return iterator; } }];
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/themebar.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------------------------------------
2 | * Copyright (c) 2018 Alex Torres
3 | * Licensed under the MIT License. See License in the project root for license information.
4 | *
5 | * This file has parts of one or more project files (VS Code) from Microsoft
6 | * You can check your respective license and the original file in https://github.com/Microsoft/vscode/
7 | *-------------------------------------------------------------------------------------------------------*/
8 |
9 | import {toDisposable, IDisposable, Disposable} from "vs/base/common/lifecycle";
10 | const commonTheme: string = require('static/theme/common.css');
11 | const macTheme: string = require('static/theme/mac.css');
12 | const winTheme: string = require('static/theme/win.css');
13 |
14 | class ThemingRegistry extends Disposable {
15 | private readonly theming: Theme[] = [];
16 |
17 | constructor() {
18 | super();
19 |
20 | this.theming = [];
21 | }
22 |
23 | protected onThemeChange(theme: Theme): IDisposable {
24 | this.theming.push(theme);
25 | return toDisposable(() => {
26 | const idx = this.theming.indexOf(theme);
27 | this.theming.splice(idx, 1);
28 | });
29 | }
30 |
31 | protected getTheming(): Theme[] {
32 | return this.theming;
33 | }
34 | }
35 |
36 | export class Themebar extends ThemingRegistry {
37 |
38 | constructor() {
39 | super();
40 |
41 | this.registerTheme((collector: CssStyle) => {
42 | collector.addRule(commonTheme);
43 | });
44 | }
45 |
46 | protected registerTheme(theme: Theme) {
47 | this.onThemeChange(theme);
48 |
49 | let cssRules: string[] = [];
50 | let hasRule: { [rule: string]: boolean } = {};
51 | let ruleCollector = {
52 | addRule: (rule: string) => {
53 | if (!hasRule[rule]) {
54 | cssRules.push(rule);
55 | hasRule[rule] = true;
56 | }
57 | }
58 | };
59 |
60 | this.getTheming().forEach(p => p(ruleCollector));
61 |
62 | _applyRules(cssRules.join('\n'), 'titlebar-style');
63 | }
64 |
65 | static get win(): Theme {
66 | return ((collector: CssStyle) => {
67 | collector.addRule(winTheme);
68 | });
69 | }
70 |
71 | static get mac(): Theme {
72 | return ((collector: CssStyle) => {
73 | collector.addRule(macTheme);
74 | });
75 | }
76 |
77 | }
78 |
79 | export interface CssStyle {
80 | addRule(rule: string): void;
81 | }
82 |
83 | export interface Theme {
84 | (collector: CssStyle): void;
85 | }
86 |
87 | function _applyRules(styleSheetContent: string, rulesClassName: string) {
88 | let themeStyles = document.head.getElementsByClassName(rulesClassName);
89 |
90 | if (themeStyles.length === 0) {
91 | let styleElement = document.createElement('style');
92 | styleElement.className = rulesClassName;
93 | styleElement.innerHTML = styleSheetContent;
94 | document.head.appendChild(styleElement);
95 | } else {
96 | (themeStyles[0]).innerHTML = styleSheetContent;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/vs/base/common/linkedList.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | class Node {
7 |
8 | static readonly Undefined = new Node(undefined);
9 |
10 | element: E;
11 | next: Node;
12 | prev: Node;
13 |
14 | constructor(element: E) {
15 | this.element = element;
16 | this.next = Node.Undefined;
17 | this.prev = Node.Undefined;
18 | }
19 | }
20 |
21 | export class LinkedList {
22 |
23 | private _first: Node = Node.Undefined;
24 | private _last: Node = Node.Undefined;
25 | private _size: number = 0;
26 |
27 | get size(): number {
28 | return this._size;
29 | }
30 |
31 | isEmpty(): boolean {
32 | return this._first === Node.Undefined;
33 | }
34 |
35 | clear(): void {
36 | this._first = Node.Undefined;
37 | this._last = Node.Undefined;
38 | this._size = 0;
39 | }
40 |
41 | unshift(element: E): () => void {
42 | return this._insert(element, false);
43 | }
44 |
45 | push(element: E): () => void {
46 | return this._insert(element, true);
47 | }
48 |
49 | private _insert(element: E, atTheEnd: boolean): () => void {
50 | const newNode = new Node(element);
51 | if (this._first === Node.Undefined) {
52 | this._first = newNode;
53 | this._last = newNode;
54 |
55 | } else if (atTheEnd) {
56 | // push
57 | const oldLast = this._last!;
58 | this._last = newNode;
59 | newNode.prev = oldLast;
60 | oldLast.next = newNode;
61 |
62 | } else {
63 | // unshift
64 | const oldFirst = this._first;
65 | this._first = newNode;
66 | newNode.next = oldFirst;
67 | oldFirst.prev = newNode;
68 | }
69 | this._size += 1;
70 |
71 | let didRemove = false;
72 | return () => {
73 | if (!didRemove) {
74 | didRemove = true;
75 | this._remove(newNode);
76 | }
77 | };
78 | }
79 |
80 | shift(): E | undefined {
81 | if (this._first === Node.Undefined) {
82 | return undefined;
83 | } else {
84 | const res = this._first.element;
85 | this._remove(this._first);
86 | return res;
87 | }
88 | }
89 |
90 | pop(): E | undefined {
91 | if (this._last === Node.Undefined) {
92 | return undefined;
93 | } else {
94 | const res = this._last.element;
95 | this._remove(this._last);
96 | return res;
97 | }
98 | }
99 |
100 | private _remove(node: Node): void {
101 | if (node.prev !== Node.Undefined && node.next !== Node.Undefined) {
102 | // middle
103 | const anchor = node.prev;
104 | anchor.next = node.next;
105 | node.next.prev = anchor;
106 |
107 | } else if (node.prev === Node.Undefined && node.next === Node.Undefined) {
108 | // only node
109 | this._first = Node.Undefined;
110 | this._last = Node.Undefined;
111 |
112 | } else if (node.next === Node.Undefined) {
113 | // last
114 | this._last = this._last!.prev!;
115 | this._last.next = Node.Undefined;
116 |
117 | } else if (node.prev === Node.Undefined) {
118 | // first
119 | this._first = this._first!.next!;
120 | this._first.prev = Node.Undefined;
121 | }
122 |
123 | // done
124 | this._size -= 1;
125 | }
126 |
127 | *[Symbol.iterator](): Iterator {
128 | let node = this._first;
129 | while (node !== Node.Undefined) {
130 | yield node.element;
131 | node = node.next;
132 | }
133 | }
134 |
135 | toArray(): E[] {
136 | const result: E[] = [];
137 | for (let node = this._first; node !== Node.Undefined; node = node.next) {
138 | result.push(node.element);
139 | }
140 | return result;
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/static/theme/win.css:
--------------------------------------------------------------------------------
1 | .titlebar .window-controls-container .window-icon-bg {
2 | display: inline-block;
3 | -webkit-app-region: no-drag;
4 | height: 100%;
5 | width: 46px;
6 | }
7 |
8 | .titlebar .window-controls-container .window-icon-bg .window-icon {
9 | height: 100%;
10 | width: 100%;
11 | -webkit-mask-size: 23.1%;
12 | mask-size: 23.1%;
13 | }
14 |
15 | .titlebar .window-controls-container .window-icon-bg .window-icon.window-close {
16 | -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.279 5.5L11 10.221l-.779.779L5.5 6.279.779 11 0 10.221 4.721 5.5 0 .779.779 0 5.5 4.721 10.221 0 11 .779 6.279 5.5z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
17 | mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.279 5.5L11 10.221l-.779.779L5.5 6.279.779 11 0 10.221 4.721 5.5 0 .779.779 0 5.5 4.721 10.221 0 11 .779 6.279 5.5z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
18 | }
19 |
20 | .titlebar .window-controls-container .window-icon-bg .window-icon.window-close:hover {
21 | background-color: #ffffff!important;
22 | }
23 |
24 | .titlebar .window-controls-container .window-icon-bg .window-icon.window-unmaximize {
25 | -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 8.798H8.798V11H0V2.202h2.202V0H11v8.798zm-3.298-5.5h-6.6v6.6h6.6v-6.6zM9.9 1.1H3.298v1.101h5.5v5.5h1.1v-6.6z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
26 | mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 8.798H8.798V11H0V2.202h2.202V0H11v8.798zm-3.298-5.5h-6.6v6.6h6.6v-6.6zM9.9 1.1H3.298v1.101h5.5v5.5h1.1v-6.6z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
27 | }
28 |
29 | .titlebar .window-controls-container .window-icon-bg .window-icon.window-maximize {
30 | -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 0v11H0V0h11zM9.899 1.101H1.1V9.9h8.8V1.1z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
31 | mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 0v11H0V0h11zM9.899 1.101H1.1V9.9h8.8V1.1z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
32 | }
33 |
34 | .titlebar .window-controls-container .window-icon-bg .window-icon.window-minimize {
35 | -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 4.399V5.5H0V4.399h11z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
36 | mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 4.399V5.5H0V4.399h11z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
37 | }
38 |
39 | .titlebar .window-controls-container .window-icon-bg.window-close-bg:hover {
40 | background-color: rgba(232, 17, 35, 0.9)!important;
41 | }
42 |
43 | .titlebar .window-controls-container .window-icon-bg.inactive {
44 | background-color: transparent!important;
45 | }
46 |
47 | .titlebar .window-controls-container .window-icon-bg.inactive .window-icon {
48 | opacity: .4;
49 | }
50 |
51 | .titlebar .window-controls-container .window-icon {
52 | background-color: #eeeeee;
53 | }
54 |
55 | .titlebar.light .window-controls-container .window-icon {
56 | background-color: #333333;
57 | }
58 |
59 | .titlebar.inactive .window-controls-container .window-icon {
60 | opacity: .7;
61 | }
62 |
63 | .titlebar .window-controls-container .window-icon-bg:not(.inactive):hover {
64 | background-color: rgba(255, 255, 255, .1);
65 | }
66 |
67 | .titlebar.light .window-controls-container .window-icon-bg:not(.inactive):hover {
68 | background-color: rgba(0, 0, 0, .1);
69 | }
--------------------------------------------------------------------------------
/src/vs/base/common/network.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { URI } from 'vs/base/common/uri';
7 | import * as platform from 'vs/base/common/platform';
8 |
9 | export namespace Schemas {
10 |
11 | /**
12 | * A schema that is used for models that exist in memory
13 | * only and that have no correspondence on a server or such.
14 | */
15 | export const inMemory = 'inmemory';
16 |
17 | /**
18 | * A schema that is used for setting files
19 | */
20 | export const vscode = 'vscode';
21 |
22 | /**
23 | * A schema that is used for internal private files
24 | */
25 | export const internal = 'private';
26 |
27 | /**
28 | * A walk-through document.
29 | */
30 | export const walkThrough = 'walkThrough';
31 |
32 | /**
33 | * An embedded code snippet.
34 | */
35 | export const walkThroughSnippet = 'walkThroughSnippet';
36 |
37 | export const http = 'http';
38 |
39 | export const https = 'https';
40 |
41 | export const file = 'file';
42 |
43 | export const mailto = 'mailto';
44 |
45 | export const untitled = 'untitled';
46 |
47 | export const data = 'data';
48 |
49 | export const command = 'command';
50 |
51 | export const vscodeRemote = 'vscode-remote';
52 |
53 | export const vscodeRemoteResource = 'vscode-remote-resource';
54 |
55 | export const userData = 'vscode-userdata';
56 |
57 | export const vscodeCustomEditor = 'vscode-custom-editor';
58 |
59 | export const vscodeNotebook = 'vscode-notebook';
60 |
61 | export const vscodeNotebookCell = 'vscode-notebook-cell';
62 |
63 | export const vscodeSettings = 'vscode-settings';
64 |
65 | export const webviewPanel = 'webview-panel';
66 |
67 | /**
68 | * Scheme used for loading the wrapper html and script in webviews.
69 | */
70 | export const vscodeWebview = 'vscode-webview';
71 |
72 | /**
73 | * Scheme used for loading resources inside of webviews.
74 | */
75 | export const vscodeWebviewResource = 'vscode-webview-resource';
76 |
77 | /**
78 | * Scheme used for extension pages
79 | */
80 | export const extension = 'extension';
81 | }
82 |
83 | class RemoteAuthoritiesImpl {
84 | private readonly _hosts: { [authority: string]: string | undefined; } = Object.create(null);
85 | private readonly _ports: { [authority: string]: number | undefined; } = Object.create(null);
86 | private readonly _connectionTokens: { [authority: string]: string | undefined; } = Object.create(null);
87 | private _preferredWebSchema: 'http' | 'https' = 'http';
88 | private _delegate: ((uri: URI) => URI) | null = null;
89 |
90 | setPreferredWebSchema(schema: 'http' | 'https') {
91 | this._preferredWebSchema = schema;
92 | }
93 |
94 | setDelegate(delegate: (uri: URI) => URI): void {
95 | this._delegate = delegate;
96 | }
97 |
98 | set(authority: string, host: string, port: number): void {
99 | this._hosts[authority] = host;
100 | this._ports[authority] = port;
101 | }
102 |
103 | setConnectionToken(authority: string, connectionToken: string): void {
104 | this._connectionTokens[authority] = connectionToken;
105 | }
106 |
107 | rewrite(uri: URI): URI {
108 | if (this._delegate) {
109 | return this._delegate(uri);
110 | }
111 | const authority = uri.authority;
112 | let host = this._hosts[authority];
113 | if (host && host.indexOf(':') !== -1) {
114 | host = `[${host}]`;
115 | }
116 | const port = this._ports[authority];
117 | const connectionToken = this._connectionTokens[authority];
118 | let query = `path=${encodeURIComponent(uri.path)}`;
119 | if (typeof connectionToken === 'string') {
120 | query += `&tkn=${encodeURIComponent(connectionToken)}`;
121 | }
122 | return URI.from({
123 | scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
124 | authority: `${host}:${port}`,
125 | path: `/vscode-remote-resource`,
126 | query
127 | });
128 | }
129 | }
130 |
131 | export const RemoteAuthorities = new RemoteAuthoritiesImpl();
132 |
--------------------------------------------------------------------------------
/static/theme/mac.css:
--------------------------------------------------------------------------------
1 | .titlebar.cet-mac {
2 | padding: 0;
3 | }
4 |
5 | .titlebar .window-controls-container .window-icon-bg {
6 | display: inline-block;
7 | -webkit-app-region: no-drag;
8 | height: 12px;
9 | width: 12px;
10 | margin: 7.5px 4px;
11 | border-radius: 50%;
12 | overflow: hidden;
13 | }
14 |
15 | .titlebar .window-controls-container .window-icon-bg.inactive {
16 | background-color: #cdcdcd;
17 | }
18 |
19 | .titlebar .window-controls-container .window-icon-bg:not(.inactive) .window-icon {
20 | height: 100%;
21 | width: 100%;
22 | background-color: transparent;
23 | -webkit-mask-size: 100% !important;
24 | mask-size: 100% !important;
25 | }
26 |
27 | .titlebar .window-controls-container .window-icon-bg:not(.inactive) .window-icon:hover {
28 | background-color: rgba(0, 0, 0, 0.4);
29 | }
30 |
31 | .titlebar .window-controls-container .window-icon-bg:not(.inactive).window-minimize-bg {
32 | background-color: #febc28;
33 | }
34 |
35 | .titlebar .window-controls-container .window-icon-bg:not(.inactive).window-minimize-bg:hover {
36 | background-color: #feb30a;
37 | }
38 |
39 | .titlebar .window-controls-container .window-icon-bg:not(.inactive).window-restore-bg {
40 | background-color: #01cc4e;
41 | }
42 |
43 | .titlebar .window-controls-container .window-icon-bg:not(.inactive).window-restore-bg:hover {
44 | background-color: #01ae42;
45 | }
46 |
47 | .titlebar .window-controls-container .window-icon-bg:not(.inactive).window-close-bg {
48 | background-color: #ff5b5d;
49 | }
50 |
51 | .titlebar .window-controls-container .window-icon-bg:not(.inactive).window-close-bg:hover {
52 | background-color: #ff3c3f;
53 | }
54 |
55 | .titlebar .window-controls-container .window-icon-bg:not(.inactive).window-close-bg .window-close {
56 | -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
57 | mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
58 | }
59 |
60 | .titlebar .window-controls-container .window-icon-bg:not(.inactive) .window-maximize {
61 | -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.17,12L15,8.83L16.41,7.41L21,12L16.41,16.58L15,15.17L18.17,12M5.83,12L9,15.17L7.59,16.59L3,12L7.59,7.42L9,8.83L5.83,12Z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
62 | mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.17,12L15,8.83L16.41,7.41L21,12L16.41,16.58L15,15.17L18.17,12M5.83,12L9,15.17L7.59,16.59L3,12L7.59,7.42L9,8.83L5.83,12Z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
63 | transform: rotate(-45deg);
64 | }
65 |
66 | .titlebar .window-controls-container .window-icon-bg:not(.inactive) .window-unmaximize {
67 | -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5.41,7.41L10,12L5.41,16.59L4,15.17L7.17,12L4,8.83L5.41,7.41M18.59,16.59L14,12L18.59,7.42L20,8.83L16.83,12L20,15.17L18.59,16.59Z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
68 | mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5.41,7.41L10,12L5.41,16.59L4,15.17L7.17,12L4,8.83L5.41,7.41M18.59,16.59L14,12L18.59,7.42L20,8.83L16.83,12L20,15.17L18.59,16.59Z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
69 | transform: rotate(-45deg);
70 | }
71 |
72 | .titlebar .window-controls-container .window-icon-bg:not(.inactive) .window-minimize {
73 | -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M19,13H5V11H19V13Z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
74 | mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M19,13H5V11H19V13Z' fill='%23000'/%3E%3C/svg%3E") no-repeat 50% 50%;
75 | }
--------------------------------------------------------------------------------
/src/vs/base/common/cancellation.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { Emitter, Event } from 'vs/base/common/event';
7 | import { IDisposable } from 'vs/base/common/lifecycle';
8 |
9 | export interface CancellationToken {
10 |
11 | /**
12 | * A flag signalling is cancellation has been requested.
13 | */
14 | readonly isCancellationRequested: boolean;
15 |
16 | /**
17 | * An event which fires when cancellation is requested. This event
18 | * only ever fires `once` as cancellation can only happen once. Listeners
19 | * that are registered after cancellation will be called (next event loop run),
20 | * but also only once.
21 | *
22 | * @event
23 | */
24 | readonly onCancellationRequested: (listener: (e: any) => any, thisArgs?: any, disposables?: IDisposable[]) => IDisposable;
25 | }
26 |
27 | const shortcutEvent: Event = Object.freeze(function (callback, context?): IDisposable {
28 | const handle = setTimeout(callback.bind(context), 0);
29 | return { dispose() { clearTimeout(handle); } };
30 | });
31 |
32 | export namespace CancellationToken {
33 |
34 | export function isCancellationToken(thing: unknown): thing is CancellationToken {
35 | if (thing === CancellationToken.None || thing === CancellationToken.Cancelled) {
36 | return true;
37 | }
38 | if (thing instanceof MutableToken) {
39 | return true;
40 | }
41 | if (!thing || typeof thing !== 'object') {
42 | return false;
43 | }
44 | return typeof (thing as CancellationToken).isCancellationRequested === 'boolean'
45 | && typeof (thing as CancellationToken).onCancellationRequested === 'function';
46 | }
47 |
48 |
49 | export const None: CancellationToken = Object.freeze({
50 | isCancellationRequested: false,
51 | onCancellationRequested: Event.None
52 | });
53 |
54 | export const Cancelled: CancellationToken = Object.freeze({
55 | isCancellationRequested: true,
56 | onCancellationRequested: shortcutEvent
57 | });
58 | }
59 |
60 | class MutableToken implements CancellationToken {
61 |
62 | private _isCancelled: boolean = false;
63 | private _emitter: Emitter | null = null;
64 |
65 | public cancel() {
66 | if (!this._isCancelled) {
67 | this._isCancelled = true;
68 | if (this._emitter) {
69 | this._emitter.fire(undefined);
70 | this.dispose();
71 | }
72 | }
73 | }
74 |
75 | get isCancellationRequested(): boolean {
76 | return this._isCancelled;
77 | }
78 |
79 | get onCancellationRequested(): Event {
80 | if (this._isCancelled) {
81 | return shortcutEvent;
82 | }
83 | if (!this._emitter) {
84 | this._emitter = new Emitter();
85 | }
86 | return this._emitter.event;
87 | }
88 |
89 | public dispose(): void {
90 | if (this._emitter) {
91 | this._emitter.dispose();
92 | this._emitter = null;
93 | }
94 | }
95 | }
96 |
97 | export class CancellationTokenSource {
98 |
99 | private _token?: CancellationToken = undefined;
100 | private _parentListener?: IDisposable = undefined;
101 |
102 | constructor(parent?: CancellationToken) {
103 | this._parentListener = parent && parent.onCancellationRequested(this.cancel, this);
104 | }
105 |
106 | get token(): CancellationToken {
107 | if (!this._token) {
108 | // be lazy and create the token only when
109 | // actually needed
110 | this._token = new MutableToken();
111 | }
112 | return this._token;
113 | }
114 |
115 | cancel(): void {
116 | if (!this._token) {
117 | // save an object by returning the default
118 | // cancelled token when cancellation happens
119 | // before someone asks for the token
120 | this._token = CancellationToken.Cancelled;
121 |
122 | } else if (this._token instanceof MutableToken) {
123 | // actually cancel
124 | this._token.cancel();
125 | }
126 | }
127 |
128 | dispose(cancel: boolean = false): void {
129 | if (cancel) {
130 | this.cancel();
131 | }
132 | if (this._parentListener) {
133 | this._parentListener.dispose();
134 | }
135 | if (!this._token) {
136 | // ensure to initialize with an empty token if we had none
137 | this._token = CancellationToken.None;
138 |
139 | } else if (this._token instanceof MutableToken) {
140 | // actually dispose
141 | this._token.dispose();
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/vs/base/browser/iframe.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | /**
7 | * Represents a window in a possible chain of iframes
8 | */
9 | export interface IWindowChainElement {
10 | /**
11 | * The window object for it
12 | */
13 | window: Window;
14 | /**
15 | * The iframe element inside the window.parent corresponding to window
16 | */
17 | iframeElement: HTMLIFrameElement | null;
18 | }
19 |
20 | let hasDifferentOriginAncestorFlag: boolean = false;
21 | let sameOriginWindowChainCache: IWindowChainElement[] | null = null;
22 |
23 | function getParentWindowIfSameOrigin(w: Window): Window | null {
24 | if (!w.parent || w.parent === w) {
25 | return null;
26 | }
27 |
28 | // Cannot really tell if we have access to the parent window unless we try to access something in it
29 | try {
30 | let location = w.location;
31 | let parentLocation = w.parent.location;
32 | if (location.protocol !== parentLocation.protocol || location.hostname !== parentLocation.hostname || location.port !== parentLocation.port) {
33 | hasDifferentOriginAncestorFlag = true;
34 | return null;
35 | }
36 | } catch (e) {
37 | hasDifferentOriginAncestorFlag = true;
38 | return null;
39 | }
40 |
41 | return w.parent;
42 | }
43 |
44 | function findIframeElementInParentWindow(parentWindow: Window, childWindow: Window): HTMLIFrameElement | null {
45 | let parentWindowIframes = parentWindow.document.getElementsByTagName('iframe');
46 | let iframe: HTMLIFrameElement;
47 | for (let i = 0, len = parentWindowIframes.length; i < len; i++) {
48 | iframe = parentWindowIframes[i];
49 | if (iframe.contentWindow === childWindow) {
50 | return iframe;
51 | }
52 | }
53 | return null;
54 | }
55 |
56 | export class IframeUtils {
57 |
58 | /**
59 | * Returns a chain of embedded windows with the same origin (which can be accessed programmatically).
60 | * Having a chain of length 1 might mean that the current execution environment is running outside of an iframe or inside an iframe embedded in a window with a different origin.
61 | * To distinguish if at one point the current execution environment is running inside a window with a different origin, see hasDifferentOriginAncestor()
62 | */
63 | public static getSameOriginWindowChain(): IWindowChainElement[] {
64 | if (!sameOriginWindowChainCache) {
65 | sameOriginWindowChainCache = [];
66 | let w: Window | null = window;
67 | let parent: Window | null;
68 | do {
69 | parent = getParentWindowIfSameOrigin(w);
70 | if (parent) {
71 | sameOriginWindowChainCache.push({
72 | window: w,
73 | iframeElement: findIframeElementInParentWindow(parent, w)
74 | });
75 | } else {
76 | sameOriginWindowChainCache.push({
77 | window: w,
78 | iframeElement: null
79 | });
80 | }
81 | w = parent;
82 | } while (w);
83 | }
84 | return sameOriginWindowChainCache.slice(0);
85 | }
86 |
87 | /**
88 | * Returns true if the current execution environment is chained in a list of iframes which at one point ends in a window with a different origin.
89 | * Returns false if the current execution environment is not running inside an iframe or if the entire chain of iframes have the same origin.
90 | */
91 | public static hasDifferentOriginAncestor(): boolean {
92 | if (!sameOriginWindowChainCache) {
93 | this.getSameOriginWindowChain();
94 | }
95 | return hasDifferentOriginAncestorFlag;
96 | }
97 |
98 | /**
99 | * Returns the position of `childWindow` relative to `ancestorWindow`
100 | */
101 | public static getPositionOfChildWindowRelativeToAncestorWindow(childWindow: Window, ancestorWindow: Window | null) {
102 |
103 | if (!ancestorWindow || childWindow === ancestorWindow) {
104 | return {
105 | top: 0,
106 | left: 0
107 | };
108 | }
109 |
110 | let top = 0, left = 0;
111 |
112 | let windowChain = this.getSameOriginWindowChain();
113 |
114 | for (const windowChainEl of windowChain) {
115 |
116 | if (windowChainEl.window === ancestorWindow) {
117 | break;
118 | }
119 |
120 | if (!windowChainEl.iframeElement) {
121 | break;
122 | }
123 |
124 | let boundingRect = windowChainEl.iframeElement.getBoundingClientRect();
125 | top += boundingRect.top;
126 | left += boundingRect.left;
127 | }
128 |
129 | return {
130 | top: top,
131 | left: left
132 | };
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/vs/base/browser/browser.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { Emitter, Event } from 'vs/base/common/event';
7 | import { IDisposable } from 'vs/base/common/lifecycle';
8 |
9 | class WindowManager {
10 |
11 | public static readonly INSTANCE = new WindowManager();
12 |
13 | // --- Zoom Level
14 | private _zoomLevel: number = 0;
15 | private _lastZoomLevelChangeTime: number = 0;
16 | private readonly _onDidChangeZoomLevel = new Emitter();
17 |
18 | public readonly onDidChangeZoomLevel: Event = this._onDidChangeZoomLevel.event;
19 | public getZoomLevel(): number {
20 | return this._zoomLevel;
21 | }
22 | public getTimeSinceLastZoomLevelChanged(): number {
23 | return Date.now() - this._lastZoomLevelChangeTime;
24 | }
25 | public setZoomLevel(zoomLevel: number, isTrusted: boolean): void {
26 | if (this._zoomLevel === zoomLevel) {
27 | return;
28 | }
29 |
30 | this._zoomLevel = zoomLevel;
31 | // See https://github.com/Microsoft/vscode/issues/26151
32 | this._lastZoomLevelChangeTime = isTrusted ? 0 : Date.now();
33 | this._onDidChangeZoomLevel.fire(this._zoomLevel);
34 | }
35 |
36 | // --- Zoom Factor
37 | private _zoomFactor: number = 1;
38 |
39 | public getZoomFactor(): number {
40 | return this._zoomFactor;
41 | }
42 | public setZoomFactor(zoomFactor: number): void {
43 | this._zoomFactor = zoomFactor;
44 | }
45 |
46 | // --- Pixel Ratio
47 | public getPixelRatio(): number {
48 | let ctx: any = document.createElement('canvas').getContext('2d');
49 | let dpr = window.devicePixelRatio || 1;
50 | let bsr = ctx.webkitBackingStorePixelRatio ||
51 | ctx.mozBackingStorePixelRatio ||
52 | ctx.msBackingStorePixelRatio ||
53 | ctx.oBackingStorePixelRatio ||
54 | ctx.backingStorePixelRatio || 1;
55 | return dpr / bsr;
56 | }
57 |
58 | // --- Fullscreen
59 | private _fullscreen: boolean = false;
60 | private readonly _onDidChangeFullscreen = new Emitter();
61 |
62 | public readonly onDidChangeFullscreen: Event = this._onDidChangeFullscreen.event;
63 | public setFullscreen(fullscreen: boolean): void {
64 | if (this._fullscreen === fullscreen) {
65 | return;
66 | }
67 |
68 | this._fullscreen = fullscreen;
69 | this._onDidChangeFullscreen.fire();
70 | }
71 | public isFullscreen(): boolean {
72 | return this._fullscreen;
73 | }
74 | }
75 |
76 | /** A zoom index, e.g. 1, 2, 3 */
77 | export function setZoomLevel(zoomLevel: number, isTrusted: boolean): void {
78 | WindowManager.INSTANCE.setZoomLevel(zoomLevel, isTrusted);
79 | }
80 | export function getZoomLevel(): number {
81 | return WindowManager.INSTANCE.getZoomLevel();
82 | }
83 | /** Returns the time (in ms) since the zoom level was changed */
84 | export function getTimeSinceLastZoomLevelChanged(): number {
85 | return WindowManager.INSTANCE.getTimeSinceLastZoomLevelChanged();
86 | }
87 | export function onDidChangeZoomLevel(callback: (zoomLevel: number) => void): IDisposable {
88 | return WindowManager.INSTANCE.onDidChangeZoomLevel(callback);
89 | }
90 |
91 | /** The zoom scale for an index, e.g. 1, 1.2, 1.4 */
92 | export function getZoomFactor(): number {
93 | return WindowManager.INSTANCE.getZoomFactor();
94 | }
95 | export function setZoomFactor(zoomFactor: number): void {
96 | WindowManager.INSTANCE.setZoomFactor(zoomFactor);
97 | }
98 |
99 | export function getPixelRatio(): number {
100 | return WindowManager.INSTANCE.getPixelRatio();
101 | }
102 |
103 | export function setFullscreen(fullscreen: boolean): void {
104 | WindowManager.INSTANCE.setFullscreen(fullscreen);
105 | }
106 | export function isFullscreen(): boolean {
107 | return WindowManager.INSTANCE.isFullscreen();
108 | }
109 | export const onDidChangeFullscreen = WindowManager.INSTANCE.onDidChangeFullscreen;
110 |
111 | const userAgent = navigator.userAgent;
112 |
113 | export const isEdge = (userAgent.indexOf('Edge/') >= 0);
114 | export const isOpera = (userAgent.indexOf('Opera') >= 0);
115 | export const isFirefox = (userAgent.indexOf('Firefox') >= 0);
116 | export const isWebKit = (userAgent.indexOf('AppleWebKit') >= 0);
117 | export const isChrome = (userAgent.indexOf('Chrome') >= 0);
118 | export const isSafari = (!isChrome && (userAgent.indexOf('Safari') >= 0));
119 | export const isWebkitWebView = (!isChrome && !isSafari && isWebKit);
120 | export const isIPad = (userAgent.indexOf('iPad') >= 0 || (isSafari && navigator.maxTouchPoints > 0));
121 | export const isEdgeWebView = isEdge && (userAgent.indexOf('WebView/') >= 0);
122 | export const isStandalone = (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches);
123 |
--------------------------------------------------------------------------------
/src/specs/menuitem.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment jsdom
3 | */
4 | const {KeyCodeUtils} = require("../../dist/vs/base/common/keyCodes");
5 | const {CETMenuItem} = require('../menuitem');
6 | const {Color} = require('..');
7 |
8 | describe('Menuitem creation tests', () => {
9 |
10 | describe('Constructor tests', () => {
11 |
12 | it('it stores the item internally', () => {
13 | const item = {label: 'foo'}
14 | const menuItem = new CETMenuItem(item);
15 |
16 | expect(menuItem.item).toEqual(item);
17 | })
18 |
19 | it('it stores the options internally', () => {
20 | const options = {label: 'foo'}
21 | const menuItem = new CETMenuItem(undefined, options);
22 |
23 | expect(menuItem.options).toEqual(options);
24 | })
25 |
26 | it('it stores the current browser window correctly', () => {
27 | const menuItem = new CETMenuItem(undefined);
28 |
29 | expect(menuItem.currentWindow.id).toEqual(42); // value, see __mock__/electron.js
30 | })
31 |
32 | it('it stores the current browser window correctly', () => {
33 | const closeSubMenu = () => {}
34 | const menuItem = new CETMenuItem(undefined, undefined, closeSubMenu);
35 |
36 | expect(menuItem.closeSubMenu).toEqual(closeSubMenu);
37 | })
38 | })
39 |
40 | describe('Menuitem Mnemonic Tests', () => {
41 |
42 | it('it finds a mnemonic inside the label', () => {
43 | const menuItem = new CETMenuItem(
44 | {
45 | label: 'a&bc'
46 | },
47 | {
48 | enableMnemonics: true
49 | }
50 | );
51 |
52 | expect(menuItem.getMnemonic()).toEqual(KeyCodeUtils.fromString('b'));
53 | })
54 |
55 | it('it finds a mnemonic at the beginning of the label', () => {
56 | const menuItem = new CETMenuItem(
57 | {
58 | label: '&abc'
59 | },
60 | {
61 | enableMnemonics: true
62 | }
63 | );
64 |
65 | expect(menuItem.getMnemonic()).toEqual(KeyCodeUtils.fromString('a'));
66 | })
67 |
68 | it('it does not create a mnemonic if the feature is turned off', () => {
69 | const menuItem = new CETMenuItem(
70 | {
71 | label: '&abc'
72 | },
73 | {
74 | enableMnemonics: false
75 | }
76 | );
77 |
78 | expect(menuItem.getMnemonic()).toBeFalsy();
79 | })
80 |
81 | it('it does not create a mnemonic if there is no label', () => {
82 | const menuItem = new CETMenuItem(
83 | {},
84 | {
85 | enableMnemonics: true
86 | }
87 | );
88 |
89 | expect(menuItem.getMnemonic()).toBeFalsy();
90 | })
91 |
92 | })
93 |
94 | describe('Menuitem styling Tests', () => {
95 |
96 | let menuItem;
97 | let hasFocusedClass = false;
98 |
99 | beforeEach(() => {
100 | menuItem = new CETMenuItem({}, {}, () => {});
101 | menuItem.container = {classList: {contains: jest.fn(c => hasFocusedClass)}};
102 | menuItem.checkElement = {style: {removeProperty: jest.fn(c => {})}};
103 | menuItem.itemElement = {style: {removeProperty: jest.fn(c => {})}};
104 |
105 | })
106 |
107 | it('Stores the applied style internally', () => {
108 | const style = {};
109 | menuItem.style(style);
110 | expect(menuItem.menuStyle).toEqual(style);
111 | })
112 |
113 | it('Stores null or undefined styles but does not crash.', () => {
114 | const containsMock = menuItem.container.classList.contains.mock;
115 | menuItem.menuStyle = {};
116 | const style = null;
117 | menuItem.style(style);
118 |
119 | expect(menuItem.menuStyle).toBeFalsy();
120 | expect(containsMock.calls.length).toBe(0);
121 | })
122 |
123 | it('Checks if the menuitem container has the style class "focused"', () => {
124 | menuItem.style({});
125 | const containsMock = menuItem.container.classList.contains.mock;
126 |
127 | expect(containsMock.calls.length).toBe(1);
128 | expect(containsMock.calls[0][0]).toBe('focused');
129 | })
130 |
131 | it('Correctly sets the foreground color', () => {
132 | hasFocusedClass = true;
133 | menuItem.style({
134 | selectionForegroundColor: undefined,
135 | foregroundColor: Color.black
136 | });
137 |
138 | expect(menuItem.itemElement.style.color).toEqual(Color.black.toString());
139 | expect(menuItem.checkElement.style.backgroundColor).toEqual(Color.black.toString());
140 | });
141 |
142 | it('Correctly sets the foreground color to selection foreground color', () => {
143 | hasFocusedClass = true;
144 | menuItem.style({
145 | selectionForegroundColor: Color.red,
146 | foregroundColor: Color.black
147 | });
148 |
149 | expect(menuItem.itemElement.style.color).toEqual(Color.red.toString());
150 | expect(menuItem.checkElement.style.backgroundColor).toEqual(Color.red.toString());
151 | });
152 |
153 | it('Correctly sets the background color', () => {
154 | hasFocusedClass = true;
155 | menuItem.style({
156 | selectionBackgroundColor: undefined,
157 | backgroundColor: Color.black
158 | });
159 |
160 | expect(menuItem.itemElement.style.backgroundColor).toEqual(Color.black.toString());
161 | });
162 |
163 | it('Correctly sets the background color to selection background color', () => {
164 | hasFocusedClass = true;
165 | menuItem.style({
166 | selectionBackgroundColor: Color.red,
167 | backgroundColor: Color.black
168 | });
169 |
170 | expect(menuItem.itemElement.style.backgroundColor).toEqual(Color.red.toString());
171 | });
172 |
173 | it('Does not crash when itemElement or checkElement are falsy', () => {
174 | menuItem.itemElement = null;
175 | menuItem.checkElement = null;
176 | menuItem.style({});
177 | });
178 |
179 | })
180 | })
181 |
182 |
--------------------------------------------------------------------------------
/src/vs/base/common/decorators.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | export function createDecorator(mapFn: (fn: Function, key: string) => Function): Function {
7 | return (target: any, key: string, descriptor: any) => {
8 | let fnKey: string | null = null;
9 | let fn: Function | null = null;
10 |
11 | if (typeof descriptor.value === 'function') {
12 | fnKey = 'value';
13 | fn = descriptor.value;
14 | } else if (typeof descriptor.get === 'function') {
15 | fnKey = 'get';
16 | fn = descriptor.get;
17 | }
18 |
19 | if (!fn) {
20 | throw new Error('not supported');
21 | }
22 |
23 | descriptor[fnKey!] = mapFn(fn, key);
24 | };
25 | }
26 |
27 | let memoizeId = 0;
28 | export function createMemoizer() {
29 | const memoizeKeyPrefix = `$memoize${memoizeId++}`;
30 | let self: any = undefined;
31 |
32 | const result = function memoize(target: any, key: string, descriptor: any) {
33 | let fnKey: string | null = null;
34 | let fn: Function | null = null;
35 |
36 | if (typeof descriptor.value === 'function') {
37 | fnKey = 'value';
38 | fn = descriptor.value;
39 |
40 | if (fn!.length !== 0) {
41 | console.warn('Memoize should only be used in functions with zero parameters');
42 | }
43 | } else if (typeof descriptor.get === 'function') {
44 | fnKey = 'get';
45 | fn = descriptor.get;
46 | }
47 |
48 | if (!fn) {
49 | throw new Error('not supported');
50 | }
51 |
52 | const memoizeKey = `${memoizeKeyPrefix}:${key}`;
53 | descriptor[fnKey!] = function (...args: any[]) {
54 | self = this;
55 |
56 | if (!this.hasOwnProperty(memoizeKey)) {
57 | Object.defineProperty(this, memoizeKey, {
58 | configurable: true,
59 | enumerable: false,
60 | writable: true,
61 | value: fn!.apply(this, args)
62 | });
63 | }
64 |
65 | return this[memoizeKey];
66 | };
67 | };
68 |
69 | result.clear = () => {
70 | if (typeof self === 'undefined') {
71 | return;
72 | }
73 | Object.getOwnPropertyNames(self).forEach(property => {
74 | if (property.indexOf(memoizeKeyPrefix) === 0) {
75 | delete self[property];
76 | }
77 | });
78 | };
79 |
80 | return result;
81 | }
82 |
83 | export function memoize(target: any, key: string, descriptor: any) {
84 | return createMemoizer()(target, key, descriptor);
85 | }
86 |
87 | export interface IDebounceReducer {
88 | (previousValue: T, ...args: any[]): T;
89 | }
90 |
91 | export function debounce(delay: number, reducer?: IDebounceReducer, initialValueProvider?: () => T): Function {
92 | return createDecorator((fn, key) => {
93 | const timerKey = `$debounce$${key}`;
94 | const resultKey = `$debounce$result$${key}`;
95 |
96 | return function (this: any, ...args: any[]) {
97 | if (!this[resultKey]) {
98 | this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
99 | }
100 |
101 | clearTimeout(this[timerKey]);
102 |
103 | if (reducer) {
104 | this[resultKey] = reducer(this[resultKey], ...args);
105 | args = [this[resultKey]];
106 | }
107 |
108 | this[timerKey] = setTimeout(() => {
109 | fn.apply(this, args);
110 | this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
111 | }, delay);
112 | };
113 | });
114 | }
115 |
116 | export function throttle(delay: number, reducer?: IDebounceReducer, initialValueProvider?: () => T): Function {
117 | return createDecorator((fn, key) => {
118 | const timerKey = `$throttle$timer$${key}`;
119 | const resultKey = `$throttle$result$${key}`;
120 | const lastRunKey = `$throttle$lastRun$${key}`;
121 | const pendingKey = `$throttle$pending$${key}`;
122 |
123 | return function (this: any, ...args: any[]) {
124 | if (!this[resultKey]) {
125 | this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
126 | }
127 | if (this[lastRunKey] === null || this[lastRunKey] === undefined) {
128 | this[lastRunKey] = -Number.MAX_VALUE;
129 | }
130 |
131 | if (reducer) {
132 | this[resultKey] = reducer(this[resultKey], ...args);
133 | }
134 |
135 | if (this[pendingKey]) {
136 | return;
137 | }
138 |
139 | const nextTime = this[lastRunKey] + delay;
140 | if (nextTime <= Date.now()) {
141 | this[lastRunKey] = Date.now();
142 | fn.apply(this, [this[resultKey]]);
143 | this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
144 | } else {
145 | this[pendingKey] = true;
146 | this[timerKey] = setTimeout(() => {
147 | this[pendingKey] = false;
148 | this[lastRunKey] = Date.now();
149 | fn.apply(this, [this[resultKey]]);
150 | this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
151 | }, nextTime - Date.now());
152 | }
153 | };
154 | });
155 | }
--------------------------------------------------------------------------------
/src/vs/base/common/errors.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | export interface ErrorListenerCallback {
7 | (error: any): void;
8 | }
9 |
10 | export interface ErrorListenerUnbind {
11 | (): void;
12 | }
13 |
14 | // Avoid circular dependency on EventEmitter by implementing a subset of the interface.
15 | export class ErrorHandler {
16 | private unexpectedErrorHandler: (e: any) => void;
17 | private listeners: ErrorListenerCallback[];
18 |
19 | constructor() {
20 |
21 | this.listeners = [];
22 |
23 | this.unexpectedErrorHandler = function (e: any) {
24 | setTimeout(() => {
25 | if (e.stack) {
26 | throw new Error(e.message + '\n\n' + e.stack);
27 | }
28 |
29 | throw e;
30 | }, 0);
31 | };
32 | }
33 |
34 | addListener(listener: ErrorListenerCallback): ErrorListenerUnbind {
35 | this.listeners.push(listener);
36 |
37 | return () => {
38 | this._removeListener(listener);
39 | };
40 | }
41 |
42 | private emit(e: any): void {
43 | this.listeners.forEach((listener) => {
44 | listener(e);
45 | });
46 | }
47 |
48 | private _removeListener(listener: ErrorListenerCallback): void {
49 | this.listeners.splice(this.listeners.indexOf(listener), 1);
50 | }
51 |
52 | setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
53 | this.unexpectedErrorHandler = newUnexpectedErrorHandler;
54 | }
55 |
56 | getUnexpectedErrorHandler(): (e: any) => void {
57 | return this.unexpectedErrorHandler;
58 | }
59 |
60 | onUnexpectedError(e: any): void {
61 | this.unexpectedErrorHandler(e);
62 | this.emit(e);
63 | }
64 |
65 | // For external errors, we don't want the listeners to be called
66 | onUnexpectedExternalError(e: any): void {
67 | this.unexpectedErrorHandler(e);
68 | }
69 | }
70 |
71 | export const errorHandler = new ErrorHandler();
72 |
73 | export function setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
74 | errorHandler.setUnexpectedErrorHandler(newUnexpectedErrorHandler);
75 | }
76 |
77 | export function onUnexpectedError(e: any): undefined {
78 | // ignore errors from cancelled promises
79 | if (!isPromiseCanceledError(e)) {
80 | errorHandler.onUnexpectedError(e);
81 | }
82 | return undefined;
83 | }
84 |
85 | export function onUnexpectedExternalError(e: any): undefined {
86 | // ignore errors from cancelled promises
87 | if (!isPromiseCanceledError(e)) {
88 | errorHandler.onUnexpectedExternalError(e);
89 | }
90 | return undefined;
91 | }
92 |
93 | export interface SerializedError {
94 | readonly $isError: true;
95 | readonly name: string;
96 | readonly message: string;
97 | readonly stack: string;
98 | }
99 |
100 | export function transformErrorForSerialization(error: Error): SerializedError;
101 | export function transformErrorForSerialization(error: any): any;
102 | export function transformErrorForSerialization(error: any): any {
103 | if (error instanceof Error) {
104 | let { name, message } = error;
105 | const stack: string = (error).stacktrace || (error).stack;
106 | return {
107 | $isError: true,
108 | name,
109 | message,
110 | stack
111 | };
112 | }
113 |
114 | // return as is
115 | return error;
116 | }
117 |
118 | // see https://github.com/v8/v8/wiki/Stack%20Trace%20API#basic-stack-traces
119 | export interface V8CallSite {
120 | getThis(): any;
121 | getTypeName(): string;
122 | getFunction(): string;
123 | getFunctionName(): string;
124 | getMethodName(): string;
125 | getFileName(): string;
126 | getLineNumber(): number;
127 | getColumnNumber(): number;
128 | getEvalOrigin(): string;
129 | isToplevel(): boolean;
130 | isEval(): boolean;
131 | isNative(): boolean;
132 | isConstructor(): boolean;
133 | toString(): string;
134 | }
135 |
136 | const canceledName = 'Canceled';
137 |
138 | /**
139 | * Checks if the given error is a promise in canceled state
140 | */
141 | export function isPromiseCanceledError(error: any): boolean {
142 | return error instanceof Error && error.name === canceledName && error.message === canceledName;
143 | }
144 |
145 | /**
146 | * Returns an error that signals cancellation.
147 | */
148 | export function canceled(): Error {
149 | const error = new Error(canceledName);
150 | error.name = error.message;
151 | return error;
152 | }
153 |
154 | export function illegalArgument(name?: string): Error {
155 | if (name) {
156 | return new Error(`Illegal argument: ${name}`);
157 | } else {
158 | return new Error('Illegal argument');
159 | }
160 | }
161 |
162 | export function illegalState(name?: string): Error {
163 | if (name) {
164 | return new Error(`Illegal state: ${name}`);
165 | } else {
166 | return new Error('Illegal state');
167 | }
168 | }
169 |
170 | export function readonly(name?: string): Error {
171 | return name
172 | ? new Error(`readonly property '${name} cannot be changed'`)
173 | : new Error('readonly property cannot be changed');
174 | }
175 |
176 | export function disposed(what: string): Error {
177 | const result = new Error(`${what} has been disposed`);
178 | result.name = 'DISPOSED';
179 | return result;
180 | }
181 |
182 | export function getErrorMessage(err: any): string {
183 | if (!err) {
184 | return 'Error';
185 | }
186 |
187 | if (err.message) {
188 | return err.message;
189 | }
190 |
191 | if (err.stack) {
192 | return err.stack.split('\n')[0];
193 | }
194 |
195 | return String(err);
196 | }
197 |
198 | export class NotImplementedError extends Error {
199 | constructor(message?: string) {
200 | super('NotImplemented');
201 | if (message) {
202 | this.message = message;
203 | }
204 | }
205 | }
206 |
207 | export class NotSupportedError extends Error {
208 | constructor(message?: string) {
209 | super('NotSupported');
210 | if (message) {
211 | this.message = message;
212 | }
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/src/vs/base/browser/mouseEvent.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import * as browser from 'vs/base/browser/browser';
7 | import { IframeUtils } from 'vs/base/browser/iframe';
8 | import * as platform from 'vs/base/common/platform';
9 |
10 | export interface IMouseEvent {
11 | readonly browserEvent: MouseEvent;
12 | readonly leftButton: boolean;
13 | readonly middleButton: boolean;
14 | readonly rightButton: boolean;
15 | readonly buttons: number;
16 | readonly target: HTMLElement;
17 | readonly detail: number;
18 | readonly posx: number;
19 | readonly posy: number;
20 | readonly ctrlKey: boolean;
21 | readonly shiftKey: boolean;
22 | readonly altKey: boolean;
23 | readonly metaKey: boolean;
24 | readonly timestamp: number;
25 |
26 | preventDefault(): void;
27 | stopPropagation(): void;
28 | }
29 |
30 | export class StandardMouseEvent implements IMouseEvent {
31 |
32 | public readonly browserEvent: MouseEvent;
33 |
34 | public readonly leftButton: boolean;
35 | public readonly middleButton: boolean;
36 | public readonly rightButton: boolean;
37 | public readonly buttons: number;
38 | public readonly target: HTMLElement;
39 | public detail: number;
40 | public readonly posx: number;
41 | public readonly posy: number;
42 | public readonly ctrlKey: boolean;
43 | public readonly shiftKey: boolean;
44 | public readonly altKey: boolean;
45 | public readonly metaKey: boolean;
46 | public readonly timestamp: number;
47 |
48 | constructor(e: MouseEvent) {
49 | this.timestamp = Date.now();
50 | this.browserEvent = e;
51 | this.leftButton = e.button === 0;
52 | this.middleButton = e.button === 1;
53 | this.rightButton = e.button === 2;
54 | this.buttons = e.buttons;
55 |
56 | this.target = e.target;
57 |
58 | this.detail = e.detail || 1;
59 | if (e.type === 'dblclick') {
60 | this.detail = 2;
61 | }
62 | this.ctrlKey = e.ctrlKey;
63 | this.shiftKey = e.shiftKey;
64 | this.altKey = e.altKey;
65 | this.metaKey = e.metaKey;
66 |
67 | if (typeof e.pageX === 'number') {
68 | this.posx = e.pageX;
69 | this.posy = e.pageY;
70 | } else {
71 | // Probably hit by MSGestureEvent
72 | this.posx = e.clientX + document.body.scrollLeft + document.documentElement!.scrollLeft;
73 | this.posy = e.clientY + document.body.scrollTop + document.documentElement!.scrollTop;
74 | }
75 |
76 | // Find the position of the iframe this code is executing in relative to the iframe where the event was captured.
77 | let iframeOffsets = IframeUtils.getPositionOfChildWindowRelativeToAncestorWindow(self, e.view);
78 | this.posx -= iframeOffsets.left;
79 | this.posy -= iframeOffsets.top;
80 | }
81 |
82 | public preventDefault(): void {
83 | this.browserEvent.preventDefault();
84 | }
85 |
86 | public stopPropagation(): void {
87 | this.browserEvent.stopPropagation();
88 | }
89 | }
90 |
91 | export interface IDataTransfer {
92 | dropEffect: string;
93 | effectAllowed: string;
94 | types: any[];
95 | files: any[];
96 |
97 | setData(type: string, data: string): void;
98 | setDragImage(image: any, x: number, y: number): void;
99 |
100 | getData(type: string): string;
101 | clearData(types?: string[]): void;
102 | }
103 |
104 | export class DragMouseEvent extends StandardMouseEvent {
105 |
106 | public readonly dataTransfer: IDataTransfer;
107 |
108 | constructor(e: MouseEvent) {
109 | super(e);
110 | this.dataTransfer = (e).dataTransfer;
111 | }
112 |
113 | }
114 |
115 | export interface IMouseWheelEvent extends MouseEvent {
116 | readonly wheelDelta: number;
117 | readonly wheelDeltaX: number;
118 | readonly wheelDeltaY: number;
119 |
120 | readonly deltaX: number;
121 | readonly deltaY: number;
122 | readonly deltaZ: number;
123 | readonly deltaMode: number;
124 | }
125 |
126 | interface IWebKitMouseWheelEvent {
127 | wheelDeltaY: number;
128 | wheelDeltaX: number;
129 | }
130 |
131 | interface IGeckoMouseWheelEvent {
132 | HORIZONTAL_AXIS: number;
133 | VERTICAL_AXIS: number;
134 | axis: number;
135 | detail: number;
136 | }
137 |
138 | export class StandardWheelEvent {
139 |
140 | public readonly browserEvent: IMouseWheelEvent | null;
141 | public readonly deltaY: number;
142 | public readonly deltaX: number;
143 | public readonly target: Node;
144 |
145 | constructor(e: IMouseWheelEvent | null, deltaX: number = 0, deltaY: number = 0) {
146 |
147 | this.browserEvent = e || null;
148 | this.target = e ? (e.target || (e).targetNode || e.srcElement) : null;
149 |
150 | this.deltaY = deltaY;
151 | this.deltaX = deltaX;
152 |
153 | if (e) {
154 | // Old (deprecated) wheel events
155 | let e1 = e;
156 | let e2 = e;
157 |
158 | // vertical delta scroll
159 | if (typeof e1.wheelDeltaY !== 'undefined') {
160 | this.deltaY = e1.wheelDeltaY / 120;
161 | } else if (typeof e2.VERTICAL_AXIS !== 'undefined' && e2.axis === e2.VERTICAL_AXIS) {
162 | this.deltaY = -e2.detail / 3;
163 | } else if (e.type === 'wheel') {
164 | // Modern wheel event
165 | // https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent
166 | const ev = e;
167 |
168 | if (ev.deltaMode === ev.DOM_DELTA_LINE) {
169 | // the deltas are expressed in lines
170 | this.deltaY = -e.deltaY;
171 | } else {
172 | this.deltaY = -e.deltaY / 40;
173 | }
174 | }
175 |
176 | // horizontal delta scroll
177 | if (typeof e1.wheelDeltaX !== 'undefined') {
178 | if (browser.isSafari && platform.isWindows) {
179 | this.deltaX = - (e1.wheelDeltaX / 120);
180 | } else {
181 | this.deltaX = e1.wheelDeltaX / 120;
182 | }
183 | } else if (typeof e2.HORIZONTAL_AXIS !== 'undefined' && e2.axis === e2.HORIZONTAL_AXIS) {
184 | this.deltaX = -e.detail / 3;
185 | } else if (e.type === 'wheel') {
186 | // Modern wheel event
187 | // https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent
188 | const ev = e;
189 |
190 | if (ev.deltaMode === ev.DOM_DELTA_LINE) {
191 | // the deltas are expressed in lines
192 | this.deltaX = -e.deltaX;
193 | } else {
194 | this.deltaX = -e.deltaX / 40;
195 | }
196 | }
197 |
198 | // Assume a vertical scroll if nothing else worked
199 | if (this.deltaY === 0 && this.deltaX === 0 && e.wheelDelta) {
200 | this.deltaY = e.wheelDelta / 120;
201 | }
202 | }
203 | }
204 |
205 | public preventDefault(): void {
206 | if (this.browserEvent) {
207 | this.browserEvent.preventDefault();
208 | }
209 | }
210 |
211 | public stopPropagation(): void {
212 | if (this.browserEvent) {
213 | this.browserEvent.stopPropagation();
214 | }
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/src/vs/base/common/platform.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | const LANGUAGE_DEFAULT = 'en';
7 |
8 | let _isWindows = false;
9 | let _isMacintosh = false;
10 | let _isLinux = false;
11 | let _isNative = false;
12 | let _isWeb = false;
13 | let _isIOS = false;
14 | let _locale: string | undefined = undefined;
15 | let _language: string = LANGUAGE_DEFAULT;
16 | let _translationsConfigFile: string | undefined = undefined;
17 | let _userAgent: string | undefined = undefined;
18 |
19 | interface NLSConfig {
20 | locale: string;
21 | availableLanguages: { [key: string]: string; };
22 | _translationsConfigFile: string;
23 | }
24 |
25 | export interface IProcessEnvironment {
26 | [key: string]: string;
27 | }
28 |
29 | interface INodeProcess {
30 | platform: string;
31 | env: IProcessEnvironment;
32 | getuid(): number;
33 | nextTick: Function;
34 | versions?: {
35 | electron?: string;
36 | };
37 | type?: string;
38 | }
39 | declare const process: INodeProcess;
40 | declare const global: any;
41 |
42 | interface INavigator {
43 | userAgent: string;
44 | language: string;
45 | maxTouchPoints?: number;
46 | }
47 | declare const navigator: INavigator;
48 | declare const self: any;
49 |
50 | const isElectronRenderer = (typeof process !== 'undefined' && typeof process.versions !== 'undefined' && typeof process.versions.electron !== 'undefined' && process.type === 'renderer');
51 |
52 | // OS detection
53 | if (typeof navigator === 'object' && !isElectronRenderer) {
54 | _userAgent = navigator.userAgent;
55 | _isWindows = _userAgent.indexOf('Windows') >= 0;
56 | _isMacintosh = _userAgent.indexOf('Macintosh') >= 0;
57 | _isIOS = (_userAgent.indexOf('Macintosh') >= 0 || _userAgent.indexOf('iPad') >= 0 || _userAgent.indexOf('iPhone') >= 0) && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
58 | _isLinux = _userAgent.indexOf('Linux') >= 0;
59 | _isWeb = true;
60 | _locale = navigator.language;
61 | _language = _locale;
62 | } else if (typeof process === 'object') {
63 | // _isWindows = (process.platform === 'darwin'); // uncomment this and comment the next 2 lines to test it on macos
64 | _isWindows = (process.platform === 'win32');
65 | _isMacintosh = (process.platform === 'darwin');
66 | _isLinux = (process.platform === 'linux');
67 | _locale = LANGUAGE_DEFAULT;
68 | _language = LANGUAGE_DEFAULT;
69 | const rawNlsConfig = process.env['VSCODE_NLS_CONFIG'];
70 | if (rawNlsConfig) {
71 | try {
72 | const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
73 | const resolved = nlsConfig.availableLanguages['*'];
74 | _locale = nlsConfig.locale;
75 | // VSCode's default language is 'en'
76 | _language = resolved ? resolved : LANGUAGE_DEFAULT;
77 | _translationsConfigFile = nlsConfig._translationsConfigFile;
78 | } catch (e) {
79 | }
80 | }
81 | _isNative = true;
82 | }
83 |
84 | export const enum Platform {
85 | Web,
86 | Mac,
87 | Linux,
88 | Windows
89 | }
90 | export function PlatformToString(platform: Platform) {
91 | switch (platform) {
92 | case Platform.Web: return 'Web';
93 | case Platform.Mac: return 'Mac';
94 | case Platform.Linux: return 'Linux';
95 | case Platform.Windows: return 'Windows';
96 | }
97 | }
98 |
99 | let _platform: Platform = Platform.Web;
100 | if (_isMacintosh) {
101 | _platform = Platform.Mac;
102 | } else if (_isWindows) {
103 | _platform = Platform.Windows;
104 | } else if (_isLinux) {
105 | _platform = Platform.Linux;
106 | }
107 |
108 | // use the first two lines for development on MacOS only.
109 | // export const isWindows = true || _isWindows;
110 | // export const isMacintosh = false && _isMacintosh;
111 | export const isWindows = _isWindows;
112 | export const isMacintosh = _isMacintosh;
113 |
114 | export const isLinux = _isLinux;
115 | export const isNative = _isNative;
116 | export const isWeb = _isWeb;
117 | export const isIOS = _isIOS;
118 | export const platform = _platform;
119 | export const userAgent = _userAgent;
120 |
121 | export function isRootUser(): boolean {
122 | return _isNative && !_isWindows && (process.getuid() === 0);
123 | }
124 |
125 | /**
126 | * The language used for the user interface. The format of
127 | * the string is all lower case (e.g. zh-tw for Traditional
128 | * Chinese)
129 | */
130 | export const language = _language;
131 |
132 | export namespace Language {
133 |
134 | export function value(): string {
135 | return language;
136 | }
137 |
138 | export function isDefaultVariant(): boolean {
139 | if (language.length === 2) {
140 | return language === 'en';
141 | } else if (language.length >= 3) {
142 | return language[0] === 'e' && language[1] === 'n' && language[2] === '-';
143 | } else {
144 | return false;
145 | }
146 | }
147 |
148 | export function isDefault(): boolean {
149 | return language === 'en';
150 | }
151 | }
152 |
153 | /**
154 | * The OS locale or the locale specified by --locale. The format of
155 | * the string is all lower case (e.g. zh-tw for Traditional
156 | * Chinese). The UI is not necessarily shown in the provided locale.
157 | */
158 | export const locale = _locale;
159 |
160 | /**
161 | * The translatios that are available through language packs.
162 | */
163 | export const translationsConfigFile = _translationsConfigFile;
164 |
165 | const _globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {} as any);
166 | export const globals: any = _globals;
167 |
168 | interface ISetImmediate {
169 | (callback: (...args: any[]) => void): void;
170 | }
171 |
172 | export const setImmediate: ISetImmediate = (function defineSetImmediate() {
173 | if (globals.setImmediate) {
174 | return globals.setImmediate.bind(globals);
175 | }
176 | if (typeof globals.postMessage === 'function' && !globals.importScripts) {
177 | interface IQueueElement {
178 | id: number;
179 | callback: () => void;
180 | }
181 | let pending: IQueueElement[] = [];
182 | globals.addEventListener('message', (e: MessageEvent) => {
183 | if (e.data && e.data.vscodeSetImmediateId) {
184 | for (let i = 0, len = pending.length; i < len; i++) {
185 | const candidate = pending[i];
186 | if (candidate.id === e.data.vscodeSetImmediateId) {
187 | pending.splice(i, 1);
188 | candidate.callback();
189 | return;
190 | }
191 | }
192 | }
193 | });
194 | let lastId = 0;
195 | return (callback: () => void) => {
196 | const myId = ++lastId;
197 | pending.push({
198 | id: myId,
199 | callback: callback
200 | });
201 | globals.postMessage({ vscodeSetImmediateId: myId }, '*');
202 | };
203 | }
204 | if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
205 | return process.nextTick.bind(process);
206 | }
207 | const _promise = Promise.resolve();
208 | return (callback: (...args: any[]) => void) => _promise.then(callback);
209 | })();
210 |
211 | export const enum OperatingSystem {
212 | Windows = 1,
213 | Macintosh = 2,
214 | Linux = 3
215 | }
216 | export const OS = (_isMacintosh || _isIOS ? OperatingSystem.Macintosh : (_isWindows ? OperatingSystem.Windows : OperatingSystem.Linux));
217 |
218 | let _isLittleEndian = true;
219 | let _isLittleEndianComputed = false;
220 | export function isLittleEndian(): boolean {
221 | if (!_isLittleEndianComputed) {
222 | _isLittleEndianComputed = true;
223 | const test = new Uint8Array(2);
224 | test[0] = 1;
225 | test[1] = 2;
226 | const view = new Uint16Array(test.buffer);
227 | _isLittleEndian = (view[0] === (2 << 8) + 1);
228 | }
229 | return _isLittleEndian;
230 | }
231 |
--------------------------------------------------------------------------------
/src/vs/base/common/lifecycle.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { once } from 'vs/base/common/functional';
7 | import { Iterable } from 'vs/base/common/iterator';
8 |
9 | /**
10 | * Enables logging of potentially leaked disposables.
11 | *
12 | * A disposable is considered leaked if it is not disposed or not registered as the child of
13 | * another disposable. This tracking is very simple an only works for classes that either
14 | * extend Disposable or use a DisposableStore. This means there are a lot of false positives.
15 | */
16 | const TRACK_DISPOSABLES = false;
17 |
18 | const __is_disposable_tracked__ = '__is_disposable_tracked__';
19 |
20 | function markTracked(x: T): void {
21 | if (!TRACK_DISPOSABLES) {
22 | return;
23 | }
24 |
25 | if (x && x !== Disposable.None) {
26 | try {
27 | (x as any)[__is_disposable_tracked__] = true;
28 | } catch {
29 | // noop
30 | }
31 | }
32 | }
33 |
34 | function trackDisposable(x: T): T {
35 | if (!TRACK_DISPOSABLES) {
36 | return x;
37 | }
38 |
39 | const stack = new Error('Potentially leaked disposable').stack!;
40 | setTimeout(() => {
41 | if (!(x as any)[__is_disposable_tracked__]) {
42 | console.log(stack);
43 | }
44 | }, 3000);
45 | return x;
46 | }
47 |
48 | export class MultiDisposeError extends Error {
49 | constructor(
50 | public readonly errors: any[]
51 | ) {
52 | super(`Encounter errors while disposing of store. Errors: [${errors.join(', ')}]`);
53 | }
54 | }
55 |
56 | export interface IDisposable {
57 | dispose(): void;
58 | }
59 |
60 | export function isDisposable(thing: E): thing is E & IDisposable {
61 | return typeof (thing).dispose === 'function' && (thing).dispose.length === 0;
62 | }
63 |
64 | export function dispose(disposable: T): T;
65 | export function dispose(disposable: T | undefined): T | undefined;
66 | export function dispose = IterableIterator>(disposables: IterableIterator): A;
67 | export function dispose(disposables: Array): Array;
68 | export function dispose(disposables: ReadonlyArray): ReadonlyArray;
69 | export function dispose(arg: T | IterableIterator | undefined): any {
70 | if (Iterable.is(arg)) {
71 | let errors: any[] = [];
72 |
73 | for (const d of arg) {
74 | if (d) {
75 | markTracked(d);
76 | try {
77 | d.dispose();
78 | } catch (e) {
79 | errors.push(e);
80 | }
81 | }
82 | }
83 |
84 | if (errors.length === 1) {
85 | throw errors[0];
86 | } else if (errors.length > 1) {
87 | throw new MultiDisposeError(errors);
88 | }
89 |
90 | return Array.isArray(arg) ? [] : arg;
91 | } else if (arg) {
92 | markTracked(arg);
93 | arg.dispose();
94 | return arg;
95 | }
96 | }
97 |
98 |
99 | export function combinedDisposable(...disposables: IDisposable[]): IDisposable {
100 | disposables.forEach(markTracked);
101 | return trackDisposable({ dispose: () => dispose(disposables) });
102 | }
103 |
104 | export function toDisposable(fn: () => void): IDisposable {
105 | const self = trackDisposable({
106 | dispose: () => {
107 | markTracked(self);
108 | fn();
109 | }
110 | });
111 | return self;
112 | }
113 |
114 | export class DisposableStore implements IDisposable {
115 |
116 | static DISABLE_DISPOSED_WARNING = false;
117 |
118 | private _toDispose = new Set();
119 | private _isDisposed = false;
120 |
121 | /**
122 | * Dispose of all registered disposables and mark this object as disposed.
123 | *
124 | * Any future disposables added to this object will be disposed of on `add`.
125 | */
126 | public dispose(): void {
127 | if (this._isDisposed) {
128 | return;
129 | }
130 |
131 | markTracked(this);
132 | this._isDisposed = true;
133 | this.clear();
134 | }
135 |
136 | /**
137 | * Dispose of all registered disposables but do not mark this object as disposed.
138 | */
139 | public clear(): void {
140 | try {
141 | dispose(this._toDispose.values());
142 | } finally {
143 | this._toDispose.clear();
144 | }
145 | }
146 |
147 | public add(t: T): T {
148 | if (!t) {
149 | return t;
150 | }
151 | if ((t as unknown as DisposableStore) === this) {
152 | throw new Error('Cannot register a disposable on itself!');
153 | }
154 |
155 | markTracked(t);
156 | if (this._isDisposed) {
157 | if (!DisposableStore.DISABLE_DISPOSED_WARNING) {
158 | console.warn(new Error('Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!').stack);
159 | }
160 | } else {
161 | this._toDispose.add(t);
162 | }
163 |
164 | return t;
165 | }
166 | }
167 |
168 | export abstract class Disposable implements IDisposable {
169 |
170 | static readonly None = Object.freeze({ dispose() { } });
171 |
172 | private readonly _store = new DisposableStore();
173 |
174 | constructor() {
175 | trackDisposable(this);
176 | }
177 |
178 | public dispose(): void {
179 | markTracked(this);
180 |
181 | this._store.dispose();
182 | }
183 |
184 | protected _register(t: T): T {
185 | if ((t as unknown as Disposable) === this) {
186 | throw new Error('Cannot register a disposable on itself!');
187 | }
188 | return this._store.add(t);
189 | }
190 | }
191 |
192 | /**
193 | * Manages the lifecycle of a disposable value that may be changed.
194 | *
195 | * This ensures that when the disposable value is changed, the previously held disposable is disposed of. You can
196 | * also register a `MutableDisposable` on a `Disposable` to ensure it is automatically cleaned up.
197 | */
198 | export class MutableDisposable implements IDisposable {
199 | private _value?: T;
200 | private _isDisposed = false;
201 |
202 | constructor() {
203 | trackDisposable(this);
204 | }
205 |
206 | get value(): T | undefined {
207 | return this._isDisposed ? undefined : this._value;
208 | }
209 |
210 | set value(value: T | undefined) {
211 | if (this._isDisposed || value === this._value) {
212 | return;
213 | }
214 |
215 | if (this._value) {
216 | this._value.dispose();
217 | }
218 | if (value) {
219 | markTracked(value);
220 | }
221 | this._value = value;
222 | }
223 |
224 | clear() {
225 | this.value = undefined;
226 | }
227 |
228 | dispose(): void {
229 | this._isDisposed = true;
230 | markTracked(this);
231 | if (this._value) {
232 | this._value.dispose();
233 | }
234 | this._value = undefined;
235 | }
236 | }
237 |
238 | export interface IReference extends IDisposable {
239 | readonly object: T;
240 | }
241 |
242 | export abstract class ReferenceCollection {
243 |
244 | private readonly references: Map = new Map();
245 |
246 | acquire(key: string, ...args: any[]): IReference {
247 | let reference = this.references.get(key);
248 |
249 | if (!reference) {
250 | reference = { counter: 0, object: this.createReferencedObject(key, ...args) };
251 | this.references.set(key, reference);
252 | }
253 |
254 | const { object } = reference;
255 | const dispose = once(() => {
256 | if (--reference!.counter === 0) {
257 | this.destroyReferencedObject(key, reference!.object);
258 | this.references.delete(key);
259 | }
260 | });
261 |
262 | reference.counter++;
263 |
264 | return { object, dispose };
265 | }
266 |
267 | protected abstract createReferencedObject(key: string, ...args: any[]): T;
268 | protected abstract destroyReferencedObject(key: string, object: T): void;
269 | }
270 |
271 | export class ImmortalReference implements IReference {
272 | constructor(public object: T) { }
273 | dispose(): void { /* noop */ }
274 | }
275 |
--------------------------------------------------------------------------------
/static/theme/common.css:
--------------------------------------------------------------------------------
1 | .titlebar {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | right: 0;
6 | box-sizing: border-box;
7 | width: 100%;
8 | font-size: 13px;
9 | padding: 0 16px;
10 | overflow: hidden;
11 | flex-shrink: 0;
12 | align-items: center;
13 | justify-content: center;
14 | user-select: none;
15 | zoom: 1;
16 | line-height: 22px;
17 | height: 22px;
18 | display: flex;
19 | z-index: 99999;
20 | }
21 |
22 | .titlebar.cet-windows, .titlebar.cet-linux {
23 | padding: 0;
24 | height: 30px;
25 | line-height: 30px;
26 | justify-content: left;
27 | overflow: visible;
28 | }
29 |
30 | .titlebar.inverted, .titlebar.inverted .menubar,
31 | .titlebar.inverted .window-controls-container {
32 | flex-direction: row-reverse;
33 | }
34 |
35 | .titlebar.inverted .window-controls-container {
36 | margin: 0 5px 0 0;
37 | }
38 |
39 | .titlebar.first-buttons .window-controls-container {
40 | order: -1;
41 | margin: 0 5px 0 0;
42 | }
43 | /* Drag region */
44 |
45 | .titlebar .titlebar-drag-region {
46 | top: 0;
47 | left: 0;
48 | display: block;
49 | position: absolute;
50 | width: 100%;
51 | height: 100%;
52 | z-index: -1;
53 | -webkit-app-region: drag;
54 | }
55 |
56 | /* icon app */
57 |
58 | .titlebar > .window-appicon {
59 | width: 35px;
60 | height: 30px;
61 | position: relative;
62 | z-index: 99;
63 | background-repeat: no-repeat;
64 | background-position: center center;
65 | background-size: 16px;
66 | flex-shrink: 0;
67 | }
68 |
69 | /* Menu bar */
70 |
71 | .menubar {
72 | display: flex;
73 | flex-shrink: 1;
74 | box-sizing: border-box;
75 | height: 30px;
76 | overflow: hidden;
77 | flex-wrap: wrap;
78 | }
79 |
80 | .menubar.bottom {
81 | order: 1;
82 | width: 100%;
83 | padding: 0 5px;
84 | }
85 |
86 | .menubar .menubar-menu-button {
87 | align-items: center;
88 | box-sizing: border-box;
89 | padding: 0px 8px;
90 | cursor: default;
91 | -webkit-app-region: no-drag;
92 | zoom: 1;
93 | white-space: nowrap;
94 | outline: 0;
95 | }
96 |
97 | .menubar .menubar-menu-button.disabled {
98 | opacity: 0.4;
99 | }
100 |
101 | .menubar .menubar-menu-button:not(.disabled):focus,
102 | .menubar .menubar-menu-button:not(.disabled).open,
103 | .menubar .menubar-menu-button:not(.disabled):hover {
104 | background-color: rgba(255, 255, 255, .1);
105 | }
106 |
107 | .titlebar.light .menubar .menubar-menu-button:focus,
108 | .titlebar.light .menubar .menubar-menu-button.open,
109 | .titlebar.light .menubar .menubar-menu-button:hover {
110 | background-color: rgba(0, 0, 0, .1);
111 | }
112 |
113 | .menubar-menu-container {
114 | position: absolute;
115 | display: block;
116 | left: 0px;
117 | opacity: 1;
118 | outline: 0;
119 | border: none;
120 | text-align: left;
121 | margin: 0 auto;
122 | padding: .5em 0;
123 | margin-left: 0;
124 | overflow: visible;
125 | justify-content: flex-end;
126 | white-space: nowrap;
127 | box-shadow: 0 5px 5px -3px rgba(0,0,0,.2), 0 8px 10px 1px rgba(0,0,0,.14), 0 3px 14px 2px rgba(0,0,0,.12);
128 | z-index: 99999;
129 | }
130 |
131 | .menubar-menu-container:focus {
132 | outline: 0;
133 | }
134 |
135 | .menubar-menu-container .action-item {
136 | padding: 0;
137 | transform: none;
138 | display: -ms-flexbox;
139 | display: flex;
140 | outline: none;
141 | }
142 |
143 | .menubar-menu-container .action-item.active {
144 | transform: none;
145 | }
146 |
147 | .menubar-menu-container .action-menu-item {
148 | -ms-flex: 1 1 auto;
149 | flex: 1 1 auto;
150 | display: -ms-flexbox;
151 | display: flex;
152 | height: 2.5em;
153 | align-items: center;
154 | position: relative;
155 | }
156 |
157 | .menubar-menu-container .action-label {
158 | -ms-flex: 1 1 auto;
159 | flex: 1 1 auto;
160 | text-decoration: none;
161 | padding: 0 1em;
162 | background: none;
163 | font-size: 12px;
164 | line-height: 1;
165 | }
166 |
167 | .menubar-menu-container .action-label:not(.separator),
168 | .menubar-menu-container .keybinding {
169 | padding: 0 2em 0 1em;
170 | }
171 |
172 | .menubar-menu-container .keybinding,
173 | .menubar-menu-container .submenu-indicator {
174 | display: inline-block;
175 | -ms-flex: 2 1 auto;
176 | flex: 2 1 auto;
177 | padding: 0 2em 0 1em;
178 | text-align: right;
179 | font-size: 12px;
180 | line-height: 1;
181 | }
182 |
183 | .menubar-menu-container .submenu-indicator {
184 | height: 100%;
185 | -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='14' height='14' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.52 12.364L9.879 7 4.52 1.636l.615-.615L11.122 7l-5.986 5.98-.615-.616z' fill='%23000'/%3E%3C/svg%3E") no-repeat right center/13px 13px;
186 | mask: url("data:image/svg+xml;charset=utf-8,%3Csvg width='14' height='14' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.52 12.364L9.879 7 4.52 1.636l.615-.615L11.122 7l-5.986 5.98-.615-.616z' fill='%23000'/%3E%3C/svg%3E") no-repeat right center/13px 13px;
187 | font-size: 60%;
188 | margin: 0 2em 0 1em;
189 | }
190 |
191 | .menubar-menu-container .action-item.disabled .action-menu-item,
192 | .menubar-menu-container .action-label.separator {
193 | opacity: 0.4;
194 | }
195 |
196 | .menubar-menu-container .action-label:not(.separator) {
197 | display: inline-block;
198 | -webkit-box-sizing: border-box;
199 | -o-box-sizing: border-box;
200 | -moz-box-sizing: border-box;
201 | -ms-box-sizing: border-box;
202 | box-sizing: border-box;
203 | margin: 0;
204 | }
205 |
206 | .menubar-menu-container .action-item .submenu {
207 | position: absolute;
208 | }
209 |
210 | .menubar-menu-container .action-label.separator {
211 | font-size: inherit;
212 | padding: .2em 0 0;
213 | margin-left: .8em;
214 | margin-right: .8em;
215 | margin-bottom: .2em;
216 | width: 100%;
217 | border-bottom: 1px solid transparent;
218 | }
219 |
220 | .menubar-menu-container .action-label.separator.text {
221 | padding: 0.7em 1em 0.1em 1em;
222 | font-weight: bold;
223 | opacity: 1;
224 | }
225 |
226 | .menubar-menu-container .action-label:hover {
227 | color: inherit;
228 | }
229 |
230 | .menubar-menu-container .menu-item-check {
231 | position: absolute;
232 | visibility: hidden;
233 | -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='-2 -2 16 16'%3E%3Cpath fill='%23424242' d='M9 0L4.5 9 3 6H0l3 6h3l6-12z'/%3E%3C/svg%3E") no-repeat 50% 56%/15px 15px;
234 | mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='-2 -2 16 16'%3E%3Cpath fill='%23424242' d='M9 0L4.5 9 3 6H0l3 6h3l6-12z'/%3E%3C/svg%3E") no-repeat 50% 56%/15px 15px;
235 | width: 2em;
236 | height: 2em;
237 | margin: 0 0 0 0.7em;
238 | }
239 |
240 | .menubar-menu-container .menu-item-icon {
241 | width: 18px;
242 | height: 18px;
243 | margin: 0 0 0 1.1em;
244 | }
245 |
246 | .menubar-menu-container .menu-item-icon img {
247 | display: inherit;
248 | width: 100%;
249 | height: 100%;
250 | }
251 |
252 | .menubar-menu-container .action-menu-item.checked .menu-item-icon {
253 | visibility: hidden;
254 | }
255 |
256 | .menubar-menu-container .action-menu-item.checked .menu-item-check {
257 | visibility: visible;
258 | }
259 |
260 | /* Title */
261 |
262 | .titlebar .window-title {
263 | flex: 0 1 auto;
264 | font-size: 12px;
265 | overflow: hidden;
266 | white-space: nowrap;
267 | text-overflow: ellipsis;
268 | margin: 0 auto;
269 | zoom: 1;
270 | }
271 |
272 | /* Window controls */
273 |
274 | .titlebar .window-controls-container {
275 | display: flex;
276 | flex-grow: 0;
277 | flex-shrink: 0;
278 | text-align: center;
279 | position: relative;
280 | z-index: 99;
281 | -webkit-app-region: no-drag;
282 | height: 30px;
283 | margin-left: 5px;
284 | }
285 |
286 | /* Resizer */
287 |
288 | .titlebar.cet-windows .resizer, .titlebar.cet-linux .resizer {
289 | -webkit-app-region: no-drag;
290 | position: absolute;
291 | }
292 |
293 | .titlebar.cet-windows .resizer.top, .titlebar.cet-linux .resizer.top {
294 | top: 0;
295 | width: 100%;
296 | height: 6px;
297 | }
298 |
299 | .titlebar.cet-windows .resizer.left, .titlebar.cet-linux .resizer.left {
300 | top: 0;
301 | left: 0;
302 | width: 6px;
303 | height: 100%;
304 | }
--------------------------------------------------------------------------------
/src/vs/base/browser/keyboardEvent.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import * as browser from 'vs/base/browser/browser';
7 | import { KeyCode, KeyCodeUtils, KeyMod, SimpleKeybinding } from 'vs/base/common/keyCodes';
8 | import * as platform from 'vs/base/common/platform';
9 |
10 | let KEY_CODE_MAP: { [keyCode: number]: KeyCode } = new Array(230);
11 | let INVERSE_KEY_CODE_MAP: KeyCode[] = new Array(KeyCode.MAX_VALUE);
12 |
13 | (function () {
14 | for (let i = 0; i < INVERSE_KEY_CODE_MAP.length; i++) {
15 | INVERSE_KEY_CODE_MAP[i] = -1;
16 | }
17 |
18 | function define(code: number, keyCode: KeyCode): void {
19 | KEY_CODE_MAP[code] = keyCode;
20 | INVERSE_KEY_CODE_MAP[keyCode] = code;
21 | }
22 |
23 | define(3, KeyCode.PauseBreak); // VK_CANCEL 0x03 Control-break processing
24 | define(8, KeyCode.Backspace);
25 | define(9, KeyCode.Tab);
26 | define(13, KeyCode.Enter);
27 | define(16, KeyCode.Shift);
28 | define(17, KeyCode.Ctrl);
29 | define(18, KeyCode.Alt);
30 | define(19, KeyCode.PauseBreak);
31 | define(20, KeyCode.CapsLock);
32 | define(27, KeyCode.Escape);
33 | define(32, KeyCode.Space);
34 | define(33, KeyCode.PageUp);
35 | define(34, KeyCode.PageDown);
36 | define(35, KeyCode.End);
37 | define(36, KeyCode.Home);
38 | define(37, KeyCode.LeftArrow);
39 | define(38, KeyCode.UpArrow);
40 | define(39, KeyCode.RightArrow);
41 | define(40, KeyCode.DownArrow);
42 | define(45, KeyCode.Insert);
43 | define(46, KeyCode.Delete);
44 |
45 | define(48, KeyCode.KEY_0);
46 | define(49, KeyCode.KEY_1);
47 | define(50, KeyCode.KEY_2);
48 | define(51, KeyCode.KEY_3);
49 | define(52, KeyCode.KEY_4);
50 | define(53, KeyCode.KEY_5);
51 | define(54, KeyCode.KEY_6);
52 | define(55, KeyCode.KEY_7);
53 | define(56, KeyCode.KEY_8);
54 | define(57, KeyCode.KEY_9);
55 |
56 | define(65, KeyCode.KEY_A);
57 | define(66, KeyCode.KEY_B);
58 | define(67, KeyCode.KEY_C);
59 | define(68, KeyCode.KEY_D);
60 | define(69, KeyCode.KEY_E);
61 | define(70, KeyCode.KEY_F);
62 | define(71, KeyCode.KEY_G);
63 | define(72, KeyCode.KEY_H);
64 | define(73, KeyCode.KEY_I);
65 | define(74, KeyCode.KEY_J);
66 | define(75, KeyCode.KEY_K);
67 | define(76, KeyCode.KEY_L);
68 | define(77, KeyCode.KEY_M);
69 | define(78, KeyCode.KEY_N);
70 | define(79, KeyCode.KEY_O);
71 | define(80, KeyCode.KEY_P);
72 | define(81, KeyCode.KEY_Q);
73 | define(82, KeyCode.KEY_R);
74 | define(83, KeyCode.KEY_S);
75 | define(84, KeyCode.KEY_T);
76 | define(85, KeyCode.KEY_U);
77 | define(86, KeyCode.KEY_V);
78 | define(87, KeyCode.KEY_W);
79 | define(88, KeyCode.KEY_X);
80 | define(89, KeyCode.KEY_Y);
81 | define(90, KeyCode.KEY_Z);
82 |
83 | define(93, KeyCode.ContextMenu);
84 |
85 | define(96, KeyCode.NUMPAD_0);
86 | define(97, KeyCode.NUMPAD_1);
87 | define(98, KeyCode.NUMPAD_2);
88 | define(99, KeyCode.NUMPAD_3);
89 | define(100, KeyCode.NUMPAD_4);
90 | define(101, KeyCode.NUMPAD_5);
91 | define(102, KeyCode.NUMPAD_6);
92 | define(103, KeyCode.NUMPAD_7);
93 | define(104, KeyCode.NUMPAD_8);
94 | define(105, KeyCode.NUMPAD_9);
95 | define(106, KeyCode.NUMPAD_MULTIPLY);
96 | define(107, KeyCode.NUMPAD_ADD);
97 | define(108, KeyCode.NUMPAD_SEPARATOR);
98 | define(109, KeyCode.NUMPAD_SUBTRACT);
99 | define(110, KeyCode.NUMPAD_DECIMAL);
100 | define(111, KeyCode.NUMPAD_DIVIDE);
101 |
102 | define(112, KeyCode.F1);
103 | define(113, KeyCode.F2);
104 | define(114, KeyCode.F3);
105 | define(115, KeyCode.F4);
106 | define(116, KeyCode.F5);
107 | define(117, KeyCode.F6);
108 | define(118, KeyCode.F7);
109 | define(119, KeyCode.F8);
110 | define(120, KeyCode.F9);
111 | define(121, KeyCode.F10);
112 | define(122, KeyCode.F11);
113 | define(123, KeyCode.F12);
114 | define(124, KeyCode.F13);
115 | define(125, KeyCode.F14);
116 | define(126, KeyCode.F15);
117 | define(127, KeyCode.F16);
118 | define(128, KeyCode.F17);
119 | define(129, KeyCode.F18);
120 | define(130, KeyCode.F19);
121 |
122 | define(144, KeyCode.NumLock);
123 | define(145, KeyCode.ScrollLock);
124 |
125 | define(186, KeyCode.US_SEMICOLON);
126 | define(187, KeyCode.US_EQUAL);
127 | define(188, KeyCode.US_COMMA);
128 | define(189, KeyCode.US_MINUS);
129 | define(190, KeyCode.US_DOT);
130 | define(191, KeyCode.US_SLASH);
131 | define(192, KeyCode.US_BACKTICK);
132 | define(193, KeyCode.ABNT_C1);
133 | define(194, KeyCode.ABNT_C2);
134 | define(219, KeyCode.US_OPEN_SQUARE_BRACKET);
135 | define(220, KeyCode.US_BACKSLASH);
136 | define(221, KeyCode.US_CLOSE_SQUARE_BRACKET);
137 | define(222, KeyCode.US_QUOTE);
138 | define(223, KeyCode.OEM_8);
139 |
140 | define(226, KeyCode.OEM_102);
141 |
142 | /**
143 | * https://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html
144 | * If an Input Method Editor is processing key input and the event is keydown, return 229.
145 | */
146 | define(229, KeyCode.KEY_IN_COMPOSITION);
147 |
148 | if (browser.isFirefox) {
149 | define(59, KeyCode.US_SEMICOLON);
150 | define(107, KeyCode.US_EQUAL);
151 | define(109, KeyCode.US_MINUS);
152 | if (platform.isMacintosh) {
153 | define(224, KeyCode.Meta);
154 | }
155 | } else if (browser.isWebKit) {
156 | define(91, KeyCode.Meta);
157 | if (platform.isMacintosh) {
158 | // the two meta keys in the Mac have different key codes (91 and 93)
159 | define(93, KeyCode.Meta);
160 | } else {
161 | define(92, KeyCode.Meta);
162 | }
163 | }
164 | })();
165 |
166 | function extractKeyCode(e: KeyboardEvent): KeyCode {
167 | if (e.charCode) {
168 | // "keypress" events mostly
169 | let char = String.fromCharCode(e.charCode).toUpperCase();
170 | return KeyCodeUtils.fromString(char);
171 | }
172 | return KEY_CODE_MAP[e.keyCode] || KeyCode.Unknown;
173 | }
174 |
175 | export function getCodeForKeyCode(keyCode: KeyCode): number {
176 | return INVERSE_KEY_CODE_MAP[keyCode];
177 | }
178 |
179 | export interface IKeyboardEvent {
180 |
181 | readonly _standardKeyboardEventBrand: true;
182 |
183 | readonly browserEvent: KeyboardEvent;
184 | readonly target: HTMLElement;
185 |
186 | readonly ctrlKey: boolean;
187 | readonly shiftKey: boolean;
188 | readonly altKey: boolean;
189 | readonly metaKey: boolean;
190 | readonly keyCode: KeyCode;
191 | readonly code: string;
192 |
193 | /**
194 | * @internal
195 | */
196 | toKeybinding(): SimpleKeybinding;
197 | equals(keybinding: number): boolean;
198 |
199 | preventDefault(): void;
200 | stopPropagation(): void;
201 | }
202 |
203 | const ctrlKeyMod = (platform.isMacintosh ? KeyMod.WinCtrl : KeyMod.CtrlCmd);
204 | const altKeyMod = KeyMod.Alt;
205 | const shiftKeyMod = KeyMod.Shift;
206 | const metaKeyMod = (platform.isMacintosh ? KeyMod.CtrlCmd : KeyMod.WinCtrl);
207 |
208 | export class StandardKeyboardEvent implements IKeyboardEvent {
209 |
210 | readonly _standardKeyboardEventBrand = true;
211 |
212 | public readonly browserEvent: KeyboardEvent;
213 | public readonly target: HTMLElement;
214 |
215 | public readonly ctrlKey: boolean;
216 | public readonly shiftKey: boolean;
217 | public readonly altKey: boolean;
218 | public readonly metaKey: boolean;
219 | public readonly keyCode: KeyCode;
220 | public readonly code: string;
221 |
222 | private _asKeybinding: number;
223 | private _asRuntimeKeybinding: SimpleKeybinding;
224 |
225 | constructor(source: KeyboardEvent) {
226 | let e = source;
227 |
228 | this.browserEvent = e;
229 | this.target = e.target;
230 |
231 | this.ctrlKey = e.ctrlKey;
232 | this.shiftKey = e.shiftKey;
233 | this.altKey = e.altKey;
234 | this.metaKey = e.metaKey;
235 | this.keyCode = extractKeyCode(e);
236 | this.code = e.code;
237 |
238 | // console.info(e.type + ": keyCode: " + e.keyCode + ", which: " + e.which + ", charCode: " + e.charCode + ", detail: " + e.detail + " ====> " + this.keyCode + ' -- ' + KeyCode[this.keyCode]);
239 |
240 | this.ctrlKey = this.ctrlKey || this.keyCode === KeyCode.Ctrl;
241 | this.altKey = this.altKey || this.keyCode === KeyCode.Alt;
242 | this.shiftKey = this.shiftKey || this.keyCode === KeyCode.Shift;
243 | this.metaKey = this.metaKey || this.keyCode === KeyCode.Meta;
244 |
245 | this._asKeybinding = this._computeKeybinding();
246 | this._asRuntimeKeybinding = this._computeRuntimeKeybinding();
247 |
248 | // console.log(`code: ${e.code}, keyCode: ${e.keyCode}, key: ${e.key}`);
249 | }
250 |
251 | public preventDefault(): void {
252 | if (this.browserEvent && this.browserEvent.preventDefault) {
253 | this.browserEvent.preventDefault();
254 | }
255 | }
256 |
257 | public stopPropagation(): void {
258 | if (this.browserEvent && this.browserEvent.stopPropagation) {
259 | this.browserEvent.stopPropagation();
260 | }
261 | }
262 |
263 | public toKeybinding(): SimpleKeybinding {
264 | return this._asRuntimeKeybinding;
265 | }
266 |
267 | public equals(other: number): boolean {
268 | return this._asKeybinding === other;
269 | }
270 |
271 | private _computeKeybinding(): number {
272 | let key = KeyCode.Unknown;
273 | if (this.keyCode !== KeyCode.Ctrl && this.keyCode !== KeyCode.Shift && this.keyCode !== KeyCode.Alt && this.keyCode !== KeyCode.Meta) {
274 | key = this.keyCode;
275 | }
276 |
277 | let result = 0;
278 | if (this.ctrlKey) {
279 | result |= ctrlKeyMod;
280 | }
281 | if (this.altKey) {
282 | result |= altKeyMod;
283 | }
284 | if (this.shiftKey) {
285 | result |= shiftKeyMod;
286 | }
287 | if (this.metaKey) {
288 | result |= metaKeyMod;
289 | }
290 | result |= key;
291 |
292 | return result;
293 | }
294 |
295 | private _computeRuntimeKeybinding(): SimpleKeybinding {
296 | let key = KeyCode.Unknown;
297 | if (this.keyCode !== KeyCode.Ctrl && this.keyCode !== KeyCode.Shift && this.keyCode !== KeyCode.Alt && this.keyCode !== KeyCode.Meta) {
298 | key = this.keyCode;
299 | }
300 | return new SimpleKeybinding(this.ctrlKey, this.shiftKey, this.altKey, this.metaKey, key);
301 | }
302 | }
303 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Custom Electron Titlebar
2 |
3 | This project is a typescript library for electron that allows you to configure a fully customizable title bar.
4 | The titlebar is based on the solution from Visual Studio Code and uses
5 | some of their code.
6 |
7 | **This library is for electron 10+, it cannot be used on a basic website or with previous versions of electron.**
8 |
9 | [](https://github.com/AlexTorresSk/custom-electron-titlebar/blob/master/LICENSE)
10 |
11 | 
12 |
13 | 
14 |
15 | 
16 |
17 | ## Install
18 |
19 | ```
20 | npm i custom-electron-titlebar
21 | ```
22 |
23 | or use example folder to init basic electron project with this titlebar.
24 |
25 | ## Usage
26 |
27 | #### Step 1
28 | In your **preload** file add:
29 |
30 | ```js
31 | const customTitlebar = require('custom-electron-titlebar');
32 |
33 | window.addEventListener('DOMContentLoaded', () => {
34 | new customTitlebar.Titlebar({
35 | backgroundColor: customTitlebar.Color.fromHex('#ECECEC')
36 | });
37 |
38 | // ...
39 | })
40 | ```
41 |
42 | or, if you are using _typescript_
43 | ```ts
44 | import { Titlebar, Color } from 'custom-electron-titlebar'
45 |
46 | window.addEventListener('DOMContentLoaded', () => {
47 | new Titlebar({
48 | backgroundColor: Color.fromHex('#ECECEC')
49 | });
50 |
51 | // ...
52 | })
53 | ```
54 |
55 | The parameter `backgroundColor: Color` is required, this should be `Color` type.
56 | (View [Update Background](#update-background) for more details).
57 |
58 | #### Step 2
59 | Update the code that launches browser window
60 |
61 | Add the following line to your `main.js` file:
62 |
63 | ```js
64 | require('@treverix/remote/main').initialize()
65 | ```
66 |
67 | Then configure the browser window like this. We need
68 | nodeIntegration and we need to enable the remote module for
69 | electron 10+. Further, we assume that the :
70 |
71 | ```js
72 | var mainWindow = new BrowserWindow({
73 | width: 1000,
74 | height: 600,
75 | frame: false,
76 | webPreferences: {
77 | nodeIntegration: true,
78 | enableRemoteModule: true,
79 | preload: path.join(__dirname, 'preload.js'),
80 | }
81 | });
82 | ```
83 |
84 | #### MacOS / Windows
85 |
86 | Custom Electron Titlebar is not really useful on MacOS platforms
87 | where it only tries to simulate the normal MacOS window
88 | design to some extent and it is recommended to exclude
89 | the custom titlebar when your application is run on
90 | a MacOS platform.
91 |
92 | To switch it of for MacOS platforms, you should configure
93 | your browser window like so
94 |
95 | ```js
96 | var mainWindow = new BrowserWindow({
97 | // ...
98 | frame: process.platform === 'darwin',
99 | // ...
100 | });
101 | ```
102 |
103 | and similiar on the preload:
104 |
105 | ```js
106 |
107 | window.addEventListener('DOMContentLoaded', () => {
108 | if (process.platform !== 'darwin') {
109 | const customTitlebar = require('custom-electron-titlebar');
110 | new customTitlebar.Titlebar({
111 | backgroundColor: customTitlebar.Color.fromHex('#ECECEC')
112 | });
113 | }
114 |
115 | // ...
116 | })
117 | ```
118 |
119 | ## Options
120 |
121 | The interface [`TitleBarOptions`] is managed, which has the following configurable options for the title bar. Some parameters are optional.
122 |
123 | | Parameter | Type | Description | Default |
124 | | ------------------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
125 | | backgroundColor **(required)** | Color | The background color of the titlebar. | #444444 |
126 | | icon | string | The icon shown on the left side of the title bar. | null |
127 | | iconsTheme | Theme | Style of the icons. | Themebar.win |
128 | | shadow | boolean | The shadow of the titlebar. | false |
129 | | drag | boolean | Define whether or not you can drag the window by holding the click on the title bar. | true |
130 | | minimizable | boolean | Enables or disables the option to minimize the window by clicking on the corresponding button in the title bar. | true |
131 | | maximizable | boolean | Enables or disables the option to maximize and un-maximize the window by clicking on the corresponding button in the title bar. | true |
132 | | closeable | boolean | Enables or disables the option of the close window by clicking on the corresponding button in the title bar. | true |
133 | | order | string | Set the order of the elements on the title bar. (`inverted`, `first-buttons`) | null |
134 | | titleHorizontalAlignment | string | Set horizontal alignment of the window title. (`left`, `center`, `right`) | center |
135 | | menu | Electron.Menu | The menu to show in the title bar. | Menu.getApplicationMenu() |
136 | | menuPosition | string | The position of menubar on titlebar. | left |
137 | | enableMnemonics | boolean | Enable the mnemonics on menubar and menu items. | true |
138 | | itemBackgroundColor | Color | The background color when the mouse is over the item. | rgba(0, 0, 0, .14) |
139 | | hideWhenClickingClose | boolean | When the close button is clicked, the window is hidden instead of closed. | false |
140 | | overflow | string | The overflow of the container (`auto`, `visible`, `hidden`) | auto |
141 | | unfocusEffect | boolean | Enables or disables the blur option in the title bar. | false |
142 |
143 | ## Methods
144 |
145 | ### Update Background
146 |
147 | This change the color of titlebar and it's checked whether the color is light or dark, so that the color of the icons adapts to the background of the title bar.
148 |
149 | ```js
150 | titlebar.updateBackground(new Color(new RGBA(0, 0, 0, .7)));
151 | ```
152 |
153 | To assign colors you can use the following options: `Color.fromHex()`, `new Color(new RGBA(r, g, b, a))`, `new Color(new HSLA(h, s, l, a))`, `new Color(new HSVA(h, s, v, a))` or `Color.BLUE`, `Color.RED`, etc.
154 |
155 | ### Update Items Background Color
156 |
157 | This method change background color on hover of items of menubar.
158 |
159 | ```js
160 | titlebar.updateItemBGColor(new Color(new RGBA(0, 0, 0, .7)));
161 | ```
162 |
163 | To assign colors you can use the following options: `Color.fromHex()`, `new Color(new RGBA(r, g, b, a))`, `new Color(new HSLA(h, s, l, a))`, `new Color(new HSVA(h, s, v, a))` or `Color.BLUE`, `Color.RED`, etc.
164 |
165 | ### Update Title
166 |
167 | This method updated the title of the title bar, If you change the content of the `title` tag, you should call this method for update the title.
168 |
169 | ```js
170 | document.title = 'My new title';
171 | titlebar.updateTitle();
172 |
173 | // Or you can do as follows and avoid writing document.title
174 | titlebar.updateTitle('New Title');
175 | ```
176 |
177 | if this method is called and the title parameter is added, the title of the document is changed to that of the parameter.
178 |
179 | ### Update Icon
180 |
181 | With this method you can update the icon. This method receives the url of the image _(it is advisable to use transparent image formats)_
182 |
183 | ```js
184 | titlebar.updateIcon('./images/my-icon.svg');
185 | ```
186 |
187 | ### Update Menu
188 |
189 | This method updates or creates the menu, to create the menu use remote.Menu and remote.MenuItem.
190 |
191 | ```js
192 | const menu = new Menu();
193 | menu.append(new MenuItem({
194 | label: 'Item 1',
195 | submenu: [
196 | {
197 | label: 'Subitem 1',
198 | click: () => console.log('Click on subitem 1')
199 | },
200 | {
201 | type: 'separator'
202 | }
203 | ]
204 | }));
205 |
206 | menu.append(new MenuItem({
207 | label: 'Item 2',
208 | submenu: [
209 | {
210 | label: 'Subitem checkbox',
211 | type: 'checkbox',
212 | checked: true
213 | },
214 | {
215 | type: 'separator'
216 | },
217 | {
218 | label: 'Subitem with submenu',
219 | submenu: [
220 | {
221 | label: 'Submenu &item 1',
222 | accelerator: 'Ctrl+T'
223 | }
224 | ]
225 | }
226 | ]
227 | }));
228 |
229 | titlebar.updateMenu(menu);
230 | ```
231 |
232 | ### Update Menu Position
233 |
234 | You can change the position of the menu bar. `left` and `bottom` are allowed.
235 |
236 | ```js
237 | titlebar.updateMenuPosition('bottom');
238 | ```
239 |
240 | ### Set Horizontal Alignment
241 |
242 | > setHorizontalAlignment method was contributed by [@MairwunNx](https://github.com/MairwunNx) :punch:
243 |
244 | `left`, `center` and `right` are allowed
245 |
246 | ```js
247 | titlebar.setHorizontalAlignment('right');
248 | ```
249 |
250 | ### Dispose
251 |
252 | This method removes the title bar completely and all recorded events.
253 |
254 | ```js
255 | titlebar.dispose();
256 | ```
257 |
258 | ## CSS Classes
259 | The following CSS classes exist and can be used to customize the titlebar
260 |
261 | | Class name | Description |
262 | | --------------------------- | ----------------------------------------------- |
263 | | .titlebar | Styles the titlebar. |
264 | | .window-appicon | Styles the app icon on the titlebar. |
265 | | .window-title | Styles the window title. (Example: font-size) |
266 | | .window-controls-container | Styles the window controls section. |
267 | | .resizer top | Description missing |
268 | | .resizer left | Description missing |
269 | | .menubar | Description missing |
270 | | .menubar-menu-button | Styles the main menu elements. (Example: color) |
271 | | .menubar-menu-button open | Description missing |
272 | | .menubar-menu-title | Description missing |
273 | | .action-item | Description missing |
274 | | .action-menu-item | Styles action menu elements. (Example: color) |
275 |
276 | ## Goal
277 |
278 | Electron is deprecating the remote module. It is deactivated by default and
279 | will be removed with electron 12 in the near future. The great custom-electron-titlebar
280 | by AlexTorresSk has been archived recently and is not maintained anymore.
281 | This fork is a version for electron 10+ and uses the new userland implementation
282 | of the remote module [@treverix/remote](https://www.npmjs.com/package/@electron/remote).
283 |
284 | ## Contributing
285 |
286 | Many thanks to all the people who supported Alex' project through issues and pull request.
287 | If you want to contribute with this electron 10 fork, all the issues and pull request are welcome.
288 |
289 | You can also buy us a coffee:
290 |
291 | | Andreas (Treverix) | Alex |
292 | |--------------------|------|
293 | |
|
294 | |
295 |
296 |
297 | ## License
298 |
299 | This fork of the archived [custom-electron-titlebar project by AlexTorresSk](https://github.com/AlexTorresSk/custom-electron-titlebar) is under the [MIT](https://github.com/Treverix/custom-electron-titlebar/blob/master/LICENSE) license.
300 |
301 | It uses Code from the [Visual Studio Code Project](https://github.com/microsoft/vscode) which is also under the [MIT](https://github.com/Treverix/custom-electron-titlebar/blob/master/LICENSE) license.
302 |
--------------------------------------------------------------------------------
/src/vs/base/browser/touch.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import * as arrays from 'vs/base/common/arrays';
7 | import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
8 | import * as DomUtils from 'vs/base/browser/dom';
9 | import { memoize } from 'vs/base/common/decorators';
10 |
11 | export namespace EventType {
12 | export const Tap = '-monaco-gesturetap';
13 | export const Change = '-monaco-gesturechange';
14 | export const Start = '-monaco-gesturestart';
15 | export const End = '-monaco-gesturesend';
16 | export const Contextmenu = '-monaco-gesturecontextmenu';
17 | }
18 |
19 | interface TouchData {
20 | id: number;
21 | initialTarget: EventTarget;
22 | initialTimeStamp: number;
23 | initialPageX: number;
24 | initialPageY: number;
25 | rollingTimestamps: number[];
26 | rollingPageX: number[];
27 | rollingPageY: number[];
28 | }
29 |
30 | export interface GestureEvent extends MouseEvent {
31 | initialTarget: EventTarget | undefined;
32 | translationX: number;
33 | translationY: number;
34 | pageX: number;
35 | pageY: number;
36 | tapCount: number;
37 | }
38 |
39 | interface Touch {
40 | identifier: number;
41 | screenX: number;
42 | screenY: number;
43 | clientX: number;
44 | clientY: number;
45 | pageX: number;
46 | pageY: number;
47 | radiusX: number;
48 | radiusY: number;
49 | rotationAngle: number;
50 | force: number;
51 | target: Element;
52 | }
53 |
54 | interface TouchList {
55 | [i: number]: Touch;
56 | length: number;
57 | item(index: number): Touch;
58 | identifiedTouch(id: number): Touch;
59 | }
60 |
61 | interface TouchEvent extends Event {
62 | touches: TouchList;
63 | targetTouches: TouchList;
64 | changedTouches: TouchList;
65 | }
66 |
67 | export class Gesture extends Disposable {
68 |
69 | private static readonly SCROLL_FRICTION = -0.005;
70 | private static INSTANCE: Gesture;
71 | private static readonly HOLD_DELAY = 700;
72 |
73 | private dispatched = false;
74 | private targets: HTMLElement[];
75 | private ignoreTargets: HTMLElement[];
76 | private handle: IDisposable | null;
77 |
78 | private activeTouches: { [id: number]: TouchData; };
79 |
80 | private _lastSetTapCountTime: number;
81 |
82 | private static readonly CLEAR_TAP_COUNT_TIME = 400; // ms
83 |
84 |
85 | private constructor() {
86 | super();
87 |
88 | this.activeTouches = {};
89 | this.handle = null;
90 | this.targets = [];
91 | this.ignoreTargets = [];
92 | this._lastSetTapCountTime = 0;
93 | this._register(DomUtils.addDisposableListener(document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e), { passive: false }));
94 | this._register(DomUtils.addDisposableListener(document, 'touchend', (e: TouchEvent) => this.onTouchEnd(e)));
95 | this._register(DomUtils.addDisposableListener(document, 'touchmove', (e: TouchEvent) => this.onTouchMove(e), { passive: false }));
96 | }
97 |
98 | public static addTarget(element: HTMLElement): IDisposable {
99 | if (!Gesture.isTouchDevice()) {
100 | return Disposable.None;
101 | }
102 | if (!Gesture.INSTANCE) {
103 | Gesture.INSTANCE = new Gesture();
104 | }
105 |
106 | Gesture.INSTANCE.targets.push(element);
107 |
108 | return {
109 | dispose: () => {
110 | Gesture.INSTANCE.targets = Gesture.INSTANCE.targets.filter(t => t !== element);
111 | }
112 | };
113 | }
114 |
115 | public static ignoreTarget(element: HTMLElement): IDisposable {
116 | if (!Gesture.isTouchDevice()) {
117 | return Disposable.None;
118 | }
119 | if (!Gesture.INSTANCE) {
120 | Gesture.INSTANCE = new Gesture();
121 | }
122 |
123 | Gesture.INSTANCE.ignoreTargets.push(element);
124 |
125 | return {
126 | dispose: () => {
127 | Gesture.INSTANCE.ignoreTargets = Gesture.INSTANCE.ignoreTargets.filter(t => t !== element);
128 | }
129 | };
130 | }
131 |
132 | @memoize
133 | private static isTouchDevice(): boolean {
134 | // `'ontouchstart' in window` always evaluates to true with typescript's modern typings. This causes `window` to be
135 | // `never` later in `window.navigator`. That's why we need the explicit `window as Window` cast
136 | return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
137 | }
138 |
139 | public dispose(): void {
140 | if (this.handle) {
141 | this.handle.dispose();
142 | this.handle = null;
143 | }
144 |
145 | super.dispose();
146 | }
147 |
148 | private onTouchStart(e: TouchEvent): void {
149 | let timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
150 |
151 | if (this.handle) {
152 | this.handle.dispose();
153 | this.handle = null;
154 | }
155 |
156 | for (let i = 0, len = e.targetTouches.length; i < len; i++) {
157 | let touch = e.targetTouches.item(i);
158 |
159 | this.activeTouches[touch.identifier] = {
160 | id: touch.identifier,
161 | initialTarget: touch.target,
162 | initialTimeStamp: timestamp,
163 | initialPageX: touch.pageX,
164 | initialPageY: touch.pageY,
165 | rollingTimestamps: [timestamp],
166 | rollingPageX: [touch.pageX],
167 | rollingPageY: [touch.pageY]
168 | };
169 |
170 | let evt = this.newGestureEvent(EventType.Start, touch.target);
171 | evt.pageX = touch.pageX;
172 | evt.pageY = touch.pageY;
173 | this.dispatchEvent(evt);
174 | }
175 |
176 | if (this.dispatched) {
177 | e.preventDefault();
178 | e.stopPropagation();
179 | this.dispatched = false;
180 | }
181 | }
182 |
183 | private onTouchEnd(e: TouchEvent): void {
184 | let timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
185 |
186 | let activeTouchCount = Object.keys(this.activeTouches).length;
187 |
188 | for (let i = 0, len = e.changedTouches.length; i < len; i++) {
189 |
190 | let touch = e.changedTouches.item(i);
191 |
192 | if (!this.activeTouches.hasOwnProperty(String(touch.identifier))) {
193 | console.warn('move of an UNKNOWN touch', touch);
194 | continue;
195 | }
196 |
197 | let data = this.activeTouches[touch.identifier],
198 | holdTime = Date.now() - data.initialTimeStamp;
199 |
200 | if (holdTime < Gesture.HOLD_DELAY
201 | && Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)) < 30
202 | && Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)) < 30) {
203 |
204 | let evt = this.newGestureEvent(EventType.Tap, data.initialTarget);
205 | evt.pageX = arrays.tail(data.rollingPageX);
206 | evt.pageY = arrays.tail(data.rollingPageY);
207 | this.dispatchEvent(evt);
208 |
209 | } else if (holdTime >= Gesture.HOLD_DELAY
210 | && Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)) < 30
211 | && Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)) < 30) {
212 |
213 | let evt = this.newGestureEvent(EventType.Contextmenu, data.initialTarget);
214 | evt.pageX = arrays.tail(data.rollingPageX);
215 | evt.pageY = arrays.tail(data.rollingPageY);
216 | this.dispatchEvent(evt);
217 |
218 | } else if (activeTouchCount === 1) {
219 | let finalX = arrays.tail(data.rollingPageX);
220 | let finalY = arrays.tail(data.rollingPageY);
221 |
222 | let deltaT = arrays.tail(data.rollingTimestamps) - data.rollingTimestamps[0];
223 | let deltaX = finalX - data.rollingPageX[0];
224 | let deltaY = finalY - data.rollingPageY[0];
225 |
226 | // We need to get all the dispatch targets on the start of the inertia event
227 | const dispatchTo = this.targets.filter(t => data.initialTarget instanceof Node && t.contains(data.initialTarget));
228 | this.inertia(dispatchTo, timestamp, // time now
229 | Math.abs(deltaX) / deltaT, // speed
230 | deltaX > 0 ? 1 : -1, // x direction
231 | finalX, // x now
232 | Math.abs(deltaY) / deltaT, // y speed
233 | deltaY > 0 ? 1 : -1, // y direction
234 | finalY // y now
235 | );
236 | }
237 |
238 |
239 | this.dispatchEvent(this.newGestureEvent(EventType.End, data.initialTarget));
240 | // forget about this touch
241 | delete this.activeTouches[touch.identifier];
242 | }
243 |
244 | if (this.dispatched) {
245 | e.preventDefault();
246 | e.stopPropagation();
247 | this.dispatched = false;
248 | }
249 | }
250 |
251 | private newGestureEvent(type: string, initialTarget?: EventTarget): GestureEvent {
252 | let event = document.createEvent('CustomEvent') as unknown as GestureEvent;
253 | event.initEvent(type, false, true);
254 | event.initialTarget = initialTarget;
255 | event.tapCount = 0;
256 | return event;
257 | }
258 |
259 | private dispatchEvent(event: GestureEvent): void {
260 | if (event.type === EventType.Tap) {
261 | const currentTime = (new Date()).getTime();
262 | let setTapCount = 0;
263 | if (currentTime - this._lastSetTapCountTime > Gesture.CLEAR_TAP_COUNT_TIME) {
264 | setTapCount = 1;
265 | } else {
266 | setTapCount = 2;
267 | }
268 |
269 | this._lastSetTapCountTime = currentTime;
270 | event.tapCount = setTapCount;
271 | } else if (event.type === EventType.Change || event.type === EventType.Contextmenu) {
272 | // tap is canceled by scrolling or context menu
273 | this._lastSetTapCountTime = 0;
274 | }
275 |
276 | for (let i = 0; i < this.ignoreTargets.length; i++) {
277 | if (event.initialTarget instanceof Node && this.ignoreTargets[i].contains(event.initialTarget)) {
278 | return;
279 | }
280 | }
281 |
282 | this.targets.forEach(target => {
283 | if (event.initialTarget instanceof Node && target.contains(event.initialTarget)) {
284 | target.dispatchEvent(event);
285 | this.dispatched = true;
286 | }
287 | });
288 | }
289 |
290 | private inertia(dispatchTo: EventTarget[], t1: number, vX: number, dirX: number, x: number, vY: number, dirY: number, y: number): void {
291 | this.handle = DomUtils.scheduleAtNextAnimationFrame(() => {
292 | let now = Date.now();
293 |
294 | // velocity: old speed + accel_over_time
295 | let deltaT = now - t1,
296 | delta_pos_x = 0, delta_pos_y = 0,
297 | stopped = true;
298 |
299 | vX += Gesture.SCROLL_FRICTION * deltaT;
300 | vY += Gesture.SCROLL_FRICTION * deltaT;
301 |
302 | if (vX > 0) {
303 | stopped = false;
304 | delta_pos_x = dirX * vX * deltaT;
305 | }
306 |
307 | if (vY > 0) {
308 | stopped = false;
309 | delta_pos_y = dirY * vY * deltaT;
310 | }
311 |
312 | // dispatch translation event
313 | let evt = this.newGestureEvent(EventType.Change);
314 | evt.translationX = delta_pos_x;
315 | evt.translationY = delta_pos_y;
316 | dispatchTo.forEach(d => d.dispatchEvent(evt));
317 |
318 | if (!stopped) {
319 | this.inertia(dispatchTo, now, vX, dirX, x + delta_pos_x, vY, dirY, y + delta_pos_y);
320 | }
321 | });
322 | }
323 |
324 | private onTouchMove(e: TouchEvent): void {
325 | let timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
326 |
327 | for (let i = 0, len = e.changedTouches.length; i < len; i++) {
328 |
329 | let touch = e.changedTouches.item(i);
330 |
331 | if (!this.activeTouches.hasOwnProperty(String(touch.identifier))) {
332 | console.warn('end of an UNKNOWN touch', touch);
333 | continue;
334 | }
335 |
336 | let data = this.activeTouches[touch.identifier];
337 |
338 | let evt = this.newGestureEvent(EventType.Change, data.initialTarget);
339 | evt.translationX = touch.pageX - arrays.tail(data.rollingPageX);
340 | evt.translationY = touch.pageY - arrays.tail(data.rollingPageY);
341 | evt.pageX = touch.pageX;
342 | evt.pageY = touch.pageY;
343 | this.dispatchEvent(evt);
344 |
345 | // only keep a few data points, to average the final speed
346 | if (data.rollingPageX.length > 3) {
347 | data.rollingPageX.shift();
348 | data.rollingPageY.shift();
349 | data.rollingTimestamps.shift();
350 | }
351 |
352 | data.rollingPageX.push(touch.pageX);
353 | data.rollingPageY.push(touch.pageY);
354 | data.rollingTimestamps.push(timestamp);
355 | }
356 |
357 | if (this.dispatched) {
358 | e.preventDefault();
359 | e.stopPropagation();
360 | this.dispatched = false;
361 | }
362 | }
363 | }
--------------------------------------------------------------------------------
/src/menuitem.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------------------------------------
2 | * This file has been modified by @AlexTorresSk (http://github.com/AlexTorresSk)
3 | * to work in custom-electron-titlebar.
4 | *
5 | * The original copy of this file and its respective license are in https://github.com/Microsoft/vscode/
6 | *
7 | * Copyright (c) 2018 Alex Torres
8 | * Licensed under the MIT License. See License in the project root for license information.
9 | *-------------------------------------------------------------------------------------------------------*/
10 |
11 | import {
12 | EventType,
13 | addDisposableListener,
14 | addClass,
15 | removeClass,
16 | removeNode,
17 | append,
18 | $,
19 | hasClass,
20 | EventHelper,
21 | EventLike
22 | } from "vs/base/browser/dom";
23 | import {BrowserWindow, Accelerator, NativeImage, MenuItem} from "electron";
24 | import {getCurrentWindow} from '@electron/remote';
25 | import {MENU_MNEMONIC_REGEX, cleanMnemonic, MENU_ESCAPED_MNEMONIC_REGEX} from "./mnemonic";
26 | import {KeyCode, KeyCodeUtils} from "vs/base/common/keyCodes";
27 | import {Disposable} from "vs/base/common/lifecycle";
28 | import {isMacintosh} from "vs/base/common/platform";
29 | import {IMenuItem, IMenuStyle, IMenuOptions} from './api'
30 | import * as strings from 'vs/base/common/strings';
31 | import {EventType as TouchEventType} from 'vs/base/browser/touch';
32 |
33 | let menuItemId = 0;
34 |
35 | export class CETMenuItem extends Disposable implements IMenuItem {
36 |
37 | protected options: IMenuOptions;
38 | protected menuStyle: IMenuStyle | undefined;
39 | protected container: HTMLElement | undefined;
40 | protected itemElement: HTMLElement | undefined;
41 |
42 | private readonly item: MenuItem;
43 | private labelElement: HTMLElement | undefined;
44 | private checkElement: HTMLElement | undefined;
45 | private iconElement: HTMLElement | undefined;
46 | private readonly mnemonic: KeyCode | undefined;
47 | protected readonly closeSubMenu: () => void;
48 |
49 | private event: Electron.Event | undefined;
50 | private readonly currentWindow: BrowserWindow;
51 |
52 | constructor(item: MenuItem, options: IMenuOptions = {}, closeSubMenu = () => {
53 | }) {
54 | super();
55 |
56 | this.item = item;
57 | this.options = options;
58 | this.options.icon = options.icon !== undefined ? options.icon : false;
59 | this.options.label = options.label !== undefined ? options.label : true;
60 |
61 | this.currentWindow = getCurrentWindow();
62 | this.closeSubMenu = closeSubMenu;
63 |
64 | // Set mnemonic
65 | if (this.options.label && options.enableMnemonics) {
66 | let label = this.item.label;
67 | if (label) {
68 | const matches = MENU_MNEMONIC_REGEX.exec(label);
69 | if (matches) {
70 | this.mnemonic = KeyCodeUtils.fromString((!!matches[1] ? matches[1] : matches[3]).toLocaleUpperCase());
71 | }
72 | }
73 | }
74 |
75 | //this.item._id = menuItemId++;
76 | }
77 |
78 | getContainer() {
79 | return this.container;
80 | }
81 |
82 | isEnabled(): boolean {
83 | return this.item.enabled;
84 | }
85 |
86 | isSeparator(): boolean {
87 | return this.item.type === 'separator';
88 | }
89 |
90 | render(container: HTMLElement): void {
91 | if (!container) return;
92 |
93 | this.container = container;
94 |
95 | this._register(addDisposableListener(this.container!, TouchEventType.Tap, e => this.onClick(e)));
96 |
97 | this._register(addDisposableListener(this.container!, EventType.MOUSE_DOWN, e => {
98 | if (this.item.enabled && e.button === 0) {
99 | this.container!.classList.add('active');
100 | }
101 | }));
102 |
103 | this._register(addDisposableListener(this.container, EventType.CLICK, e => {
104 | if (this.item.enabled) {
105 | this.onClick(e);
106 | }
107 | }));
108 |
109 | this._register(addDisposableListener(this.container, EventType.DBLCLICK, e => {
110 | EventHelper.stop(e, true);
111 | }));
112 |
113 | [EventType.MOUSE_UP, EventType.MOUSE_OUT].forEach(event => {
114 | this._register(addDisposableListener(this.container!, event, e => {
115 | EventHelper.stop(e);
116 | this.container!.classList.remove('active');
117 | }));
118 | });
119 |
120 | this.itemElement = append(this.container, $('a.action-menu-item'));
121 | this.itemElement.setAttribute('role', 'menuitem');
122 |
123 | if (this.mnemonic) {
124 | this.itemElement.setAttribute('aria-keyshortcuts', `${this.mnemonic}`);
125 | }
126 |
127 | this.checkElement = append(this.itemElement, $('span.menu-item-check'));
128 | this.checkElement.setAttribute('role', 'none');
129 |
130 | this.iconElement = append(this.itemElement, $('span.menu-item-icon'));
131 | this.iconElement.setAttribute('role', 'none');
132 |
133 | this.labelElement = append(this.itemElement, $('span.action-label'));
134 |
135 | this.setAccelerator();
136 | this.updateLabel();
137 | this.updateIcon();
138 | this.updateTooltip();
139 | this.updateEnabled();
140 | this.updateChecked();
141 | this.updateVisibility();
142 | }
143 |
144 | onClick(event: EventLike) {
145 | EventHelper.stop(event, true);
146 |
147 | if (this.item.click) {
148 | this.item.click(this.item as MenuItem, this.currentWindow, this.event);
149 | }
150 |
151 | if (this.item.type === 'checkbox') {
152 | this.item.checked = !this.item.checked;
153 | this.updateChecked();
154 | }
155 |
156 | this.closeSubMenu();
157 | }
158 |
159 | focus(): void {
160 | if (this.container) {
161 | this.container.focus();
162 | addClass(this.container, 'focused');
163 | }
164 |
165 | this.applyStyle();
166 | }
167 |
168 | blur(): void {
169 | if (this.container) {
170 | this.container.blur();
171 | removeClass(this.container, 'focused');
172 | }
173 |
174 | this.applyStyle();
175 | }
176 |
177 | setAccelerator(): void {
178 | let accelerator = null;
179 | if (this.item.role) {
180 | switch (this.item.role.toLocaleLowerCase()) {
181 | case 'undo':
182 | accelerator = 'CtrlOrCmd+Z';
183 | break;
184 | case 'redo':
185 | accelerator = 'CtrlOrCmd+Y';
186 | break;
187 | case 'cut':
188 | accelerator = 'CtrlOrCmd+X';
189 | break;
190 | case 'copy':
191 | accelerator = 'CtrlOrCmd+C';
192 | break;
193 | case 'paste':
194 | accelerator = 'CtrlOrCmd+V';
195 | break;
196 | case 'selectall':
197 | accelerator = 'CtrlOrCmd+A';
198 | break;
199 | case 'minimize':
200 | accelerator = 'CtrlOrCmd+M';
201 | break;
202 | case 'close':
203 | accelerator = 'CtrlOrCmd+W';
204 | break;
205 | case 'reload':
206 | accelerator = 'CtrlOrCmd+R';
207 | break;
208 | case 'forcereload':
209 | accelerator = 'CtrlOrCmd+Shift+R';
210 | break;
211 | case 'toggledevtools':
212 | accelerator = 'CtrlOrCmd+Shift+I';
213 | break;
214 | case 'togglefullscreen':
215 | accelerator = 'F11';
216 | break;
217 | case 'resetzoom':
218 | accelerator = 'CtrlOrCmd+0';
219 | break;
220 | case 'zoomin':
221 | accelerator = 'CtrlOrCmd+Shift+=';
222 | break;
223 | case 'zoomout':
224 | accelerator = 'CtrlOrCmd+-';
225 | break;
226 | }
227 | }
228 |
229 | if (this.item.label && this.item.accelerator) {
230 | accelerator = this.item.accelerator;
231 | }
232 |
233 | if (this.itemElement && accelerator !== null) {
234 | append(this.itemElement, $('span.keybinding')).textContent = parseAccelerator(accelerator);
235 | }
236 | }
237 |
238 | updateLabel(): void {
239 | if (this.item.label) {
240 | let label = this.item.label;
241 |
242 | if (label) {
243 | const cleanLabel = cleanMnemonic(label);
244 |
245 | if (!this.options.enableMnemonics) {
246 | label = cleanLabel;
247 | }
248 |
249 | if (this.labelElement) {
250 | this.labelElement.setAttribute('aria-label', cleanLabel.replace(/&&/g, '&'));
251 | }
252 |
253 | const matches = MENU_MNEMONIC_REGEX.exec(label);
254 |
255 | if (matches) {
256 | label = strings.escape(label);
257 |
258 | // This is global, reset it
259 | MENU_ESCAPED_MNEMONIC_REGEX.lastIndex = 0;
260 | let escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(label);
261 |
262 | // We can't use negative lookbehind so if we match our negative and skip
263 | while (escMatch && escMatch[1]) {
264 | escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(label);
265 | }
266 |
267 | const replaceDoubleEscapes = (str: string) => str.replace(/&&/g, '&');
268 |
269 | if (escMatch) {
270 | label = `${label.substr(0, escMatch.index)}${escMatch[3]}${label.substr(escMatch.index + escMatch[0].length)}`;
271 | } else {
272 |
273 | }
274 |
275 | label = label.replace(/&&/g, '&');
276 | if (this.itemElement) {
277 | this.itemElement.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase());
278 | }
279 | } else {
280 | label = label.replace(/&&/g, '&');
281 | }
282 | }
283 |
284 | if (this.labelElement) {
285 | this.labelElement.innerHTML = label.trim();
286 | }
287 | }
288 | }
289 |
290 | updateIcon(): void {
291 | let icon: string | NativeImage | null = null;
292 |
293 | if (this.item.icon) {
294 | icon = this.item.icon;
295 | }
296 |
297 | if (this.iconElement && icon) {
298 | const iconE = append(this.iconElement, $('img'));
299 | iconE.setAttribute('src', icon.toString());
300 | }
301 | }
302 |
303 | updateTooltip(): void {
304 | let title: string | null = null;
305 |
306 | if (this.item.sublabel) {
307 | title = this.item.sublabel;
308 | } else if (!this.item.label && this.item.label && this.item.icon) {
309 | title = this.item.label;
310 |
311 | if (this.item.accelerator) {
312 | title = parseAccelerator(this.item.accelerator);
313 | }
314 | }
315 |
316 | if (this.itemElement && title) {
317 | this.itemElement.title = title;
318 | }
319 | }
320 |
321 | updateEnabled(): void {
322 | if (!this.container) return;
323 |
324 | if (this.item.enabled && this.item.type !== 'separator') {
325 | removeClass(this.container, 'disabled');
326 | this.container.tabIndex = 0;
327 | } else {
328 | addClass(this.container, 'disabled');
329 | }
330 | }
331 |
332 | updateVisibility(): void {
333 | if (!this.item.visible && this.itemElement) {
334 | this.itemElement.remove();
335 | }
336 | }
337 |
338 | updateChecked(): void {
339 | if (!this.itemElement) return
340 |
341 | if (this.item.checked) {
342 | this.itemElement.classList.add('checked')
343 | this.itemElement.setAttribute('role', 'menuitemcheckbox');
344 | this.itemElement.setAttribute('aria-checked', 'true');
345 | } else {
346 | this.itemElement.classList.remove('checked')
347 | this.itemElement.setAttribute('role', 'menuitem');
348 | this.itemElement.setAttribute('aria-checked', 'false');
349 | }
350 | }
351 |
352 | dispose(): void {
353 | if (this.itemElement) {
354 | this.itemElement.remove();
355 | this.itemElement = undefined;
356 | }
357 |
358 | super.dispose();
359 | }
360 |
361 | getMnemonic(): KeyCode | undefined {
362 | return this.mnemonic;
363 | }
364 |
365 | protected applyStyle() {
366 | if (!this.menuStyle) {
367 | return;
368 | }
369 |
370 | const isSelected = this.container && this.container.classList.contains('focused');
371 | const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
372 | const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : this.menuStyle.backgroundColor;
373 |
374 | if (!this.checkElement || !this.itemElement) {
375 | return;
376 | }
377 |
378 | if (fgColor) {
379 | this.itemElement.style.color = fgColor.toString();
380 | this.checkElement.style.backgroundColor = fgColor.toString();
381 | } else {
382 | this.itemElement.style.removeProperty('color');
383 | this.checkElement.style.removeProperty('background-color');
384 | }
385 |
386 | if (bgColor) {
387 | this.itemElement.style.backgroundColor = bgColor.toString();
388 | } else {
389 | this.itemElement.style.removeProperty('background-color');
390 | }
391 | }
392 |
393 | style(style: IMenuStyle): void {
394 | this.menuStyle = style;
395 | this.applyStyle();
396 | }
397 | }
398 |
399 | function parseAccelerator(a: Accelerator): string {
400 | let accelerator;
401 | if (!isMacintosh) {
402 | accelerator = a.replace(/(Cmd)|(Command)/gi, '');
403 | } else {
404 | accelerator = a.replace(/(Ctrl)|(Control)/gi, '');
405 | }
406 |
407 | accelerator = accelerator.replace(/(Or)/gi, '');
408 |
409 | return accelerator;
410 | }
411 |
--------------------------------------------------------------------------------
/src/vs/base/common/keyCodes.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { OperatingSystem } from 'vs/base/common/platform';
7 | import { illegalArgument } from 'vs/base/common/errors';
8 |
9 | /**
10 | * Virtual Key Codes, the value does not hold any inherent meaning.
11 | * Inspired somewhat from https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
12 | * But these are "more general", as they should work across browsers & OS`s.
13 | */
14 | export const enum KeyCode {
15 | /**
16 | * Placed first to cover the 0 value of the enum.
17 | */
18 | Unknown = 0,
19 |
20 | Backspace = 1,
21 | Tab = 2,
22 | Enter = 3,
23 | Shift = 4,
24 | Ctrl = 5,
25 | Alt = 6,
26 | PauseBreak = 7,
27 | CapsLock = 8,
28 | Escape = 9,
29 | Space = 10,
30 | PageUp = 11,
31 | PageDown = 12,
32 | End = 13,
33 | Home = 14,
34 | LeftArrow = 15,
35 | UpArrow = 16,
36 | RightArrow = 17,
37 | DownArrow = 18,
38 | Insert = 19,
39 | Delete = 20,
40 |
41 | KEY_0 = 21,
42 | KEY_1 = 22,
43 | KEY_2 = 23,
44 | KEY_3 = 24,
45 | KEY_4 = 25,
46 | KEY_5 = 26,
47 | KEY_6 = 27,
48 | KEY_7 = 28,
49 | KEY_8 = 29,
50 | KEY_9 = 30,
51 |
52 | KEY_A = 31,
53 | KEY_B = 32,
54 | KEY_C = 33,
55 | KEY_D = 34,
56 | KEY_E = 35,
57 | KEY_F = 36,
58 | KEY_G = 37,
59 | KEY_H = 38,
60 | KEY_I = 39,
61 | KEY_J = 40,
62 | KEY_K = 41,
63 | KEY_L = 42,
64 | KEY_M = 43,
65 | KEY_N = 44,
66 | KEY_O = 45,
67 | KEY_P = 46,
68 | KEY_Q = 47,
69 | KEY_R = 48,
70 | KEY_S = 49,
71 | KEY_T = 50,
72 | KEY_U = 51,
73 | KEY_V = 52,
74 | KEY_W = 53,
75 | KEY_X = 54,
76 | KEY_Y = 55,
77 | KEY_Z = 56,
78 |
79 | Meta = 57,
80 | ContextMenu = 58,
81 |
82 | F1 = 59,
83 | F2 = 60,
84 | F3 = 61,
85 | F4 = 62,
86 | F5 = 63,
87 | F6 = 64,
88 | F7 = 65,
89 | F8 = 66,
90 | F9 = 67,
91 | F10 = 68,
92 | F11 = 69,
93 | F12 = 70,
94 | F13 = 71,
95 | F14 = 72,
96 | F15 = 73,
97 | F16 = 74,
98 | F17 = 75,
99 | F18 = 76,
100 | F19 = 77,
101 |
102 | NumLock = 78,
103 | ScrollLock = 79,
104 |
105 | /**
106 | * Used for miscellaneous characters; it can vary by keyboard.
107 | * For the US standard keyboard, the ';:' key
108 | */
109 | US_SEMICOLON = 80,
110 | /**
111 | * For any country/region, the '+' key
112 | * For the US standard keyboard, the '=+' key
113 | */
114 | US_EQUAL = 81,
115 | /**
116 | * For any country/region, the ',' key
117 | * For the US standard keyboard, the ',<' key
118 | */
119 | US_COMMA = 82,
120 | /**
121 | * For any country/region, the '-' key
122 | * For the US standard keyboard, the '-_' key
123 | */
124 | US_MINUS = 83,
125 | /**
126 | * For any country/region, the '.' key
127 | * For the US standard keyboard, the '.>' key
128 | */
129 | US_DOT = 84,
130 | /**
131 | * Used for miscellaneous characters; it can vary by keyboard.
132 | * For the US standard keyboard, the '/?' key
133 | */
134 | US_SLASH = 85,
135 | /**
136 | * Used for miscellaneous characters; it can vary by keyboard.
137 | * For the US standard keyboard, the '`~' key
138 | */
139 | US_BACKTICK = 86,
140 | /**
141 | * Used for miscellaneous characters; it can vary by keyboard.
142 | * For the US standard keyboard, the '[{' key
143 | */
144 | US_OPEN_SQUARE_BRACKET = 87,
145 | /**
146 | * Used for miscellaneous characters; it can vary by keyboard.
147 | * For the US standard keyboard, the '\|' key
148 | */
149 | US_BACKSLASH = 88,
150 | /**
151 | * Used for miscellaneous characters; it can vary by keyboard.
152 | * For the US standard keyboard, the ']}' key
153 | */
154 | US_CLOSE_SQUARE_BRACKET = 89,
155 | /**
156 | * Used for miscellaneous characters; it can vary by keyboard.
157 | * For the US standard keyboard, the ''"' key
158 | */
159 | US_QUOTE = 90,
160 | /**
161 | * Used for miscellaneous characters; it can vary by keyboard.
162 | */
163 | OEM_8 = 91,
164 | /**
165 | * Either the angle bracket key or the backslash key on the RT 102-key keyboard.
166 | */
167 | OEM_102 = 92,
168 |
169 | NUMPAD_0 = 93, // VK_NUMPAD0, 0x60, Numeric keypad 0 key
170 | NUMPAD_1 = 94, // VK_NUMPAD1, 0x61, Numeric keypad 1 key
171 | NUMPAD_2 = 95, // VK_NUMPAD2, 0x62, Numeric keypad 2 key
172 | NUMPAD_3 = 96, // VK_NUMPAD3, 0x63, Numeric keypad 3 key
173 | NUMPAD_4 = 97, // VK_NUMPAD4, 0x64, Numeric keypad 4 key
174 | NUMPAD_5 = 98, // VK_NUMPAD5, 0x65, Numeric keypad 5 key
175 | NUMPAD_6 = 99, // VK_NUMPAD6, 0x66, Numeric keypad 6 key
176 | NUMPAD_7 = 100, // VK_NUMPAD7, 0x67, Numeric keypad 7 key
177 | NUMPAD_8 = 101, // VK_NUMPAD8, 0x68, Numeric keypad 8 key
178 | NUMPAD_9 = 102, // VK_NUMPAD9, 0x69, Numeric keypad 9 key
179 |
180 | NUMPAD_MULTIPLY = 103, // VK_MULTIPLY, 0x6A, Multiply key
181 | NUMPAD_ADD = 104, // VK_ADD, 0x6B, Add key
182 | NUMPAD_SEPARATOR = 105, // VK_SEPARATOR, 0x6C, Separator key
183 | NUMPAD_SUBTRACT = 106, // VK_SUBTRACT, 0x6D, Subtract key
184 | NUMPAD_DECIMAL = 107, // VK_DECIMAL, 0x6E, Decimal key
185 | NUMPAD_DIVIDE = 108, // VK_DIVIDE, 0x6F,
186 |
187 | /**
188 | * Cover all key codes when IME is processing input.
189 | */
190 | KEY_IN_COMPOSITION = 109,
191 |
192 | ABNT_C1 = 110, // Brazilian (ABNT) Keyboard
193 | ABNT_C2 = 111, // Brazilian (ABNT) Keyboard
194 |
195 | /**
196 | * Placed last to cover the length of the enum.
197 | * Please do not depend on this value!
198 | */
199 | MAX_VALUE
200 | }
201 |
202 | class KeyCodeStrMap {
203 |
204 | private _keyCodeToStr: string[];
205 | private _strToKeyCode: { [str: string]: KeyCode; };
206 |
207 | constructor() {
208 | this._keyCodeToStr = [];
209 | this._strToKeyCode = Object.create(null);
210 | }
211 |
212 | define(keyCode: KeyCode, str: string): void {
213 | this._keyCodeToStr[keyCode] = str;
214 | this._strToKeyCode[str.toLowerCase()] = keyCode;
215 | }
216 |
217 | keyCodeToStr(keyCode: KeyCode): string {
218 | return this._keyCodeToStr[keyCode];
219 | }
220 |
221 | strToKeyCode(str: string): KeyCode {
222 | return this._strToKeyCode[str.toLowerCase()] || KeyCode.Unknown;
223 | }
224 | }
225 |
226 | const uiMap = new KeyCodeStrMap();
227 | const userSettingsUSMap = new KeyCodeStrMap();
228 | const userSettingsGeneralMap = new KeyCodeStrMap();
229 |
230 | (function () {
231 |
232 | function define(keyCode: KeyCode, uiLabel: string, usUserSettingsLabel: string = uiLabel, generalUserSettingsLabel: string = usUserSettingsLabel): void {
233 | uiMap.define(keyCode, uiLabel);
234 | userSettingsUSMap.define(keyCode, usUserSettingsLabel);
235 | userSettingsGeneralMap.define(keyCode, generalUserSettingsLabel);
236 | }
237 |
238 | define(KeyCode.Unknown, 'unknown');
239 |
240 | define(KeyCode.Backspace, 'Backspace');
241 | define(KeyCode.Tab, 'Tab');
242 | define(KeyCode.Enter, 'Enter');
243 | define(KeyCode.Shift, 'Shift');
244 | define(KeyCode.Ctrl, 'Ctrl');
245 | define(KeyCode.Alt, 'Alt');
246 | define(KeyCode.PauseBreak, 'PauseBreak');
247 | define(KeyCode.CapsLock, 'CapsLock');
248 | define(KeyCode.Escape, 'Escape');
249 | define(KeyCode.Space, 'Space');
250 | define(KeyCode.PageUp, 'PageUp');
251 | define(KeyCode.PageDown, 'PageDown');
252 | define(KeyCode.End, 'End');
253 | define(KeyCode.Home, 'Home');
254 |
255 | define(KeyCode.LeftArrow, 'LeftArrow', 'Left');
256 | define(KeyCode.UpArrow, 'UpArrow', 'Up');
257 | define(KeyCode.RightArrow, 'RightArrow', 'Right');
258 | define(KeyCode.DownArrow, 'DownArrow', 'Down');
259 | define(KeyCode.Insert, 'Insert');
260 | define(KeyCode.Delete, 'Delete');
261 |
262 | define(KeyCode.KEY_0, '0');
263 | define(KeyCode.KEY_1, '1');
264 | define(KeyCode.KEY_2, '2');
265 | define(KeyCode.KEY_3, '3');
266 | define(KeyCode.KEY_4, '4');
267 | define(KeyCode.KEY_5, '5');
268 | define(KeyCode.KEY_6, '6');
269 | define(KeyCode.KEY_7, '7');
270 | define(KeyCode.KEY_8, '8');
271 | define(KeyCode.KEY_9, '9');
272 |
273 | define(KeyCode.KEY_A, 'A');
274 | define(KeyCode.KEY_B, 'B');
275 | define(KeyCode.KEY_C, 'C');
276 | define(KeyCode.KEY_D, 'D');
277 | define(KeyCode.KEY_E, 'E');
278 | define(KeyCode.KEY_F, 'F');
279 | define(KeyCode.KEY_G, 'G');
280 | define(KeyCode.KEY_H, 'H');
281 | define(KeyCode.KEY_I, 'I');
282 | define(KeyCode.KEY_J, 'J');
283 | define(KeyCode.KEY_K, 'K');
284 | define(KeyCode.KEY_L, 'L');
285 | define(KeyCode.KEY_M, 'M');
286 | define(KeyCode.KEY_N, 'N');
287 | define(KeyCode.KEY_O, 'O');
288 | define(KeyCode.KEY_P, 'P');
289 | define(KeyCode.KEY_Q, 'Q');
290 | define(KeyCode.KEY_R, 'R');
291 | define(KeyCode.KEY_S, 'S');
292 | define(KeyCode.KEY_T, 'T');
293 | define(KeyCode.KEY_U, 'U');
294 | define(KeyCode.KEY_V, 'V');
295 | define(KeyCode.KEY_W, 'W');
296 | define(KeyCode.KEY_X, 'X');
297 | define(KeyCode.KEY_Y, 'Y');
298 | define(KeyCode.KEY_Z, 'Z');
299 |
300 | define(KeyCode.Meta, 'Meta');
301 | define(KeyCode.ContextMenu, 'ContextMenu');
302 |
303 | define(KeyCode.F1, 'F1');
304 | define(KeyCode.F2, 'F2');
305 | define(KeyCode.F3, 'F3');
306 | define(KeyCode.F4, 'F4');
307 | define(KeyCode.F5, 'F5');
308 | define(KeyCode.F6, 'F6');
309 | define(KeyCode.F7, 'F7');
310 | define(KeyCode.F8, 'F8');
311 | define(KeyCode.F9, 'F9');
312 | define(KeyCode.F10, 'F10');
313 | define(KeyCode.F11, 'F11');
314 | define(KeyCode.F12, 'F12');
315 | define(KeyCode.F13, 'F13');
316 | define(KeyCode.F14, 'F14');
317 | define(KeyCode.F15, 'F15');
318 | define(KeyCode.F16, 'F16');
319 | define(KeyCode.F17, 'F17');
320 | define(KeyCode.F18, 'F18');
321 | define(KeyCode.F19, 'F19');
322 |
323 | define(KeyCode.NumLock, 'NumLock');
324 | define(KeyCode.ScrollLock, 'ScrollLock');
325 |
326 | define(KeyCode.US_SEMICOLON, ';', ';', 'OEM_1');
327 | define(KeyCode.US_EQUAL, '=', '=', 'OEM_PLUS');
328 | define(KeyCode.US_COMMA, ',', ',', 'OEM_COMMA');
329 | define(KeyCode.US_MINUS, '-', '-', 'OEM_MINUS');
330 | define(KeyCode.US_DOT, '.', '.', 'OEM_PERIOD');
331 | define(KeyCode.US_SLASH, '/', '/', 'OEM_2');
332 | define(KeyCode.US_BACKTICK, '`', '`', 'OEM_3');
333 | define(KeyCode.ABNT_C1, 'ABNT_C1');
334 | define(KeyCode.ABNT_C2, 'ABNT_C2');
335 | define(KeyCode.US_OPEN_SQUARE_BRACKET, '[', '[', 'OEM_4');
336 | define(KeyCode.US_BACKSLASH, '\\', '\\', 'OEM_5');
337 | define(KeyCode.US_CLOSE_SQUARE_BRACKET, ']', ']', 'OEM_6');
338 | define(KeyCode.US_QUOTE, '\'', '\'', 'OEM_7');
339 | define(KeyCode.OEM_8, 'OEM_8');
340 | define(KeyCode.OEM_102, 'OEM_102');
341 |
342 | define(KeyCode.NUMPAD_0, 'NumPad0');
343 | define(KeyCode.NUMPAD_1, 'NumPad1');
344 | define(KeyCode.NUMPAD_2, 'NumPad2');
345 | define(KeyCode.NUMPAD_3, 'NumPad3');
346 | define(KeyCode.NUMPAD_4, 'NumPad4');
347 | define(KeyCode.NUMPAD_5, 'NumPad5');
348 | define(KeyCode.NUMPAD_6, 'NumPad6');
349 | define(KeyCode.NUMPAD_7, 'NumPad7');
350 | define(KeyCode.NUMPAD_8, 'NumPad8');
351 | define(KeyCode.NUMPAD_9, 'NumPad9');
352 |
353 | define(KeyCode.NUMPAD_MULTIPLY, 'NumPad_Multiply');
354 | define(KeyCode.NUMPAD_ADD, 'NumPad_Add');
355 | define(KeyCode.NUMPAD_SEPARATOR, 'NumPad_Separator');
356 | define(KeyCode.NUMPAD_SUBTRACT, 'NumPad_Subtract');
357 | define(KeyCode.NUMPAD_DECIMAL, 'NumPad_Decimal');
358 | define(KeyCode.NUMPAD_DIVIDE, 'NumPad_Divide');
359 |
360 | })();
361 |
362 | export namespace KeyCodeUtils {
363 | export function toString(keyCode: KeyCode): string {
364 | return uiMap.keyCodeToStr(keyCode);
365 | }
366 | export function fromString(key: string): KeyCode {
367 | return uiMap.strToKeyCode(key);
368 | }
369 |
370 | export function toUserSettingsUS(keyCode: KeyCode): string {
371 | return userSettingsUSMap.keyCodeToStr(keyCode);
372 | }
373 | export function toUserSettingsGeneral(keyCode: KeyCode): string {
374 | return userSettingsGeneralMap.keyCodeToStr(keyCode);
375 | }
376 | export function fromUserSettings(key: string): KeyCode {
377 | return userSettingsUSMap.strToKeyCode(key) || userSettingsGeneralMap.strToKeyCode(key);
378 | }
379 | }
380 |
381 | /**
382 | * Binary encoding strategy:
383 | * ```
384 | * 1111 11
385 | * 5432 1098 7654 3210
386 | * ---- CSAW KKKK KKKK
387 | * C = bit 11 = ctrlCmd flag
388 | * S = bit 10 = shift flag
389 | * A = bit 9 = alt flag
390 | * W = bit 8 = winCtrl flag
391 | * K = bits 0-7 = key code
392 | * ```
393 | */
394 | const enum BinaryKeybindingsMask {
395 | CtrlCmd = (1 << 11) >>> 0,
396 | Shift = (1 << 10) >>> 0,
397 | Alt = (1 << 9) >>> 0,
398 | WinCtrl = (1 << 8) >>> 0,
399 | KeyCode = 0x000000FF
400 | }
401 |
402 | export const enum KeyMod {
403 | CtrlCmd = (1 << 11) >>> 0,
404 | Shift = (1 << 10) >>> 0,
405 | Alt = (1 << 9) >>> 0,
406 | WinCtrl = (1 << 8) >>> 0,
407 | }
408 |
409 | export function KeyChord(firstPart: number, secondPart: number): number {
410 | const chordPart = ((secondPart & 0x0000FFFF) << 16) >>> 0;
411 | return (firstPart | chordPart) >>> 0;
412 | }
413 |
414 | export function createKeybinding(keybinding: number, OS: OperatingSystem): Keybinding | null {
415 | if (keybinding === 0) {
416 | return null;
417 | }
418 | const firstPart = (keybinding & 0x0000FFFF) >>> 0;
419 | const chordPart = (keybinding & 0xFFFF0000) >>> 16;
420 | if (chordPart !== 0) {
421 | return new ChordKeybinding([
422 | createSimpleKeybinding(firstPart, OS),
423 | createSimpleKeybinding(chordPart, OS)
424 | ]);
425 | }
426 | return new ChordKeybinding([createSimpleKeybinding(firstPart, OS)]);
427 | }
428 |
429 | export function createSimpleKeybinding(keybinding: number, OS: OperatingSystem): SimpleKeybinding {
430 |
431 | const ctrlCmd = (keybinding & BinaryKeybindingsMask.CtrlCmd ? true : false);
432 | const winCtrl = (keybinding & BinaryKeybindingsMask.WinCtrl ? true : false);
433 |
434 | const ctrlKey = (OS === OperatingSystem.Macintosh ? winCtrl : ctrlCmd);
435 | const shiftKey = (keybinding & BinaryKeybindingsMask.Shift ? true : false);
436 | const altKey = (keybinding & BinaryKeybindingsMask.Alt ? true : false);
437 | const metaKey = (OS === OperatingSystem.Macintosh ? ctrlCmd : winCtrl);
438 | const keyCode = (keybinding & BinaryKeybindingsMask.KeyCode);
439 |
440 | return new SimpleKeybinding(ctrlKey, shiftKey, altKey, metaKey, keyCode);
441 | }
442 |
443 | export class SimpleKeybinding {
444 | public readonly ctrlKey: boolean;
445 | public readonly shiftKey: boolean;
446 | public readonly altKey: boolean;
447 | public readonly metaKey: boolean;
448 | public readonly keyCode: KeyCode;
449 |
450 | constructor(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean, keyCode: KeyCode) {
451 | this.ctrlKey = ctrlKey;
452 | this.shiftKey = shiftKey;
453 | this.altKey = altKey;
454 | this.metaKey = metaKey;
455 | this.keyCode = keyCode;
456 | }
457 |
458 | public equals(other: SimpleKeybinding): boolean {
459 | return (
460 | this.ctrlKey === other.ctrlKey
461 | && this.shiftKey === other.shiftKey
462 | && this.altKey === other.altKey
463 | && this.metaKey === other.metaKey
464 | && this.keyCode === other.keyCode
465 | );
466 | }
467 |
468 | public getHashCode(): string {
469 | const ctrl = this.ctrlKey ? '1' : '0';
470 | const shift = this.shiftKey ? '1' : '0';
471 | const alt = this.altKey ? '1' : '0';
472 | const meta = this.metaKey ? '1' : '0';
473 | return `${ctrl}${shift}${alt}${meta}${this.keyCode}`;
474 | }
475 |
476 | public isModifierKey(): boolean {
477 | return (
478 | this.keyCode === KeyCode.Unknown
479 | || this.keyCode === KeyCode.Ctrl
480 | || this.keyCode === KeyCode.Meta
481 | || this.keyCode === KeyCode.Alt
482 | || this.keyCode === KeyCode.Shift
483 | );
484 | }
485 |
486 | public toChord(): ChordKeybinding {
487 | return new ChordKeybinding([this]);
488 | }
489 |
490 | /**
491 | * Does this keybinding refer to the key code of a modifier and it also has the modifier flag?
492 | */
493 | public isDuplicateModifierCase(): boolean {
494 | return (
495 | (this.ctrlKey && this.keyCode === KeyCode.Ctrl)
496 | || (this.shiftKey && this.keyCode === KeyCode.Shift)
497 | || (this.altKey && this.keyCode === KeyCode.Alt)
498 | || (this.metaKey && this.keyCode === KeyCode.Meta)
499 | );
500 | }
501 | }
502 |
503 | export class ChordKeybinding {
504 | public readonly parts: SimpleKeybinding[];
505 |
506 | constructor(parts: SimpleKeybinding[]) {
507 | if (parts.length === 0) {
508 | throw illegalArgument(`parts`);
509 | }
510 | this.parts = parts;
511 | }
512 |
513 | public getHashCode(): string {
514 | let result = '';
515 | for (let i = 0, len = this.parts.length; i < len; i++) {
516 | if (i !== 0) {
517 | result += ';';
518 | }
519 | result += this.parts[i].getHashCode();
520 | }
521 | return result;
522 | }
523 |
524 | public equals(other: ChordKeybinding | null): boolean {
525 | if (other === null) {
526 | return false;
527 | }
528 | if (this.parts.length !== other.parts.length) {
529 | return false;
530 | }
531 | for (let i = 0; i < this.parts.length; i++) {
532 | if (!this.parts[i].equals(other.parts[i])) {
533 | return false;
534 | }
535 | }
536 | return true;
537 | }
538 | }
539 |
540 | export type Keybinding = ChordKeybinding;
541 |
542 | export class ResolvedKeybindingPart {
543 | readonly ctrlKey: boolean;
544 | readonly shiftKey: boolean;
545 | readonly altKey: boolean;
546 | readonly metaKey: boolean;
547 |
548 | readonly keyLabel: string | null;
549 | readonly keyAriaLabel: string | null;
550 |
551 | constructor(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean, kbLabel: string | null, kbAriaLabel: string | null) {
552 | this.ctrlKey = ctrlKey;
553 | this.shiftKey = shiftKey;
554 | this.altKey = altKey;
555 | this.metaKey = metaKey;
556 | this.keyLabel = kbLabel;
557 | this.keyAriaLabel = kbAriaLabel;
558 | }
559 | }
560 |
561 | /**
562 | * A resolved keybinding. Can be a simple keybinding or a chord keybinding.
563 | */
564 | export abstract class ResolvedKeybinding {
565 | /**
566 | * This prints the binding in a format suitable for displaying in the UI.
567 | */
568 | public abstract getLabel(): string | null;
569 | /**
570 | * This prints the binding in a format suitable for ARIA.
571 | */
572 | public abstract getAriaLabel(): string | null;
573 | /**
574 | * This prints the binding in a format suitable for electron's accelerators.
575 | * See https://github.com/electron/electron/blob/master/docs/api/accelerator.md
576 | */
577 | public abstract getElectronAccelerator(): string | null;
578 | /**
579 | * This prints the binding in a format suitable for user settings.
580 | */
581 | public abstract getUserSettingsLabel(): string | null;
582 | /**
583 | * Is the user settings label reflecting the label?
584 | */
585 | public abstract isWYSIWYG(): boolean;
586 |
587 | /**
588 | * Is the binding a chord?
589 | */
590 | public abstract isChord(): boolean;
591 |
592 | /**
593 | * Returns the parts that comprise of the keybinding.
594 | * Simple keybindings return one element.
595 | */
596 | public abstract getParts(): ResolvedKeybindingPart[];
597 |
598 | /**
599 | * Returns the parts that should be used for dispatching.
600 | */
601 | public abstract getDispatchParts(): (string | null)[];
602 | }
603 |
--------------------------------------------------------------------------------
/src/vs/base/common/arrays.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { CancellationToken } from 'vs/base/common/cancellation';
7 | import { canceled } from 'vs/base/common/errors';
8 | import { ISplice } from 'vs/base/common/sequence';
9 |
10 | /**
11 | * Returns the last element of an array.
12 | * @param array The array.
13 | * @param n Which element from the end (default is zero).
14 | */
15 | export function tail(array: ArrayLike, n: number = 0): T {
16 | return array[array.length - (1 + n)];
17 | }
18 |
19 | export function tail2(arr: T[]): [T[], T] {
20 | if (arr.length === 0) {
21 | throw new Error('Invalid tail call');
22 | }
23 |
24 | return [arr.slice(0, arr.length - 1), arr[arr.length - 1]];
25 | }
26 |
27 | export function equals(one: ReadonlyArray | undefined, other: ReadonlyArray | undefined, itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean {
28 | if (one === other) {
29 | return true;
30 | }
31 |
32 | if (!one || !other) {
33 | return false;
34 | }
35 |
36 | if (one.length !== other.length) {
37 | return false;
38 | }
39 |
40 | for (let i = 0, len = one.length; i < len; i++) {
41 | if (!itemEquals(one[i], other[i])) {
42 | return false;
43 | }
44 | }
45 |
46 | return true;
47 | }
48 |
49 | export function binarySearch(array: ReadonlyArray, key: T, comparator: (op1: T, op2: T) => number): number {
50 | let low = 0,
51 | high = array.length - 1;
52 |
53 | while (low <= high) {
54 | const mid = ((low + high) / 2) | 0;
55 | const comp = comparator(array[mid], key);
56 | if (comp < 0) {
57 | low = mid + 1;
58 | } else if (comp > 0) {
59 | high = mid - 1;
60 | } else {
61 | return mid;
62 | }
63 | }
64 | return -(low + 1);
65 | }
66 |
67 | /**
68 | * Takes a sorted array and a function p. The array is sorted in such a way that all elements where p(x) is false
69 | * are located before all elements where p(x) is true.
70 | * @returns the least x for which p(x) is true or array.length if no element fullfills the given function.
71 | */
72 | export function findFirstInSorted(array: ReadonlyArray, p: (x: T) => boolean): number {
73 | let low = 0, high = array.length;
74 | if (high === 0) {
75 | return 0; // no children
76 | }
77 | while (low < high) {
78 | const mid = Math.floor((low + high) / 2);
79 | if (p(array[mid])) {
80 | high = mid;
81 | } else {
82 | low = mid + 1;
83 | }
84 | }
85 | return low;
86 | }
87 |
88 | type Compare = (a: T, b: T) => number;
89 |
90 | /**
91 | * Like `Array#sort` but always stable. Usually runs a little slower `than Array#sort`
92 | * so only use this when actually needing stable sort.
93 | */
94 | export function mergeSort(data: T[], compare: Compare): T[] {
95 | _sort(data, compare, 0, data.length - 1, []);
96 | return data;
97 | }
98 |
99 | function _merge(a: T[], compare: Compare, lo: number, mid: number, hi: number, aux: T[]): void {
100 | let leftIdx = lo, rightIdx = mid + 1;
101 | for (let i = lo; i <= hi; i++) {
102 | aux[i] = a[i];
103 | }
104 | for (let i = lo; i <= hi; i++) {
105 | if (leftIdx > mid) {
106 | // left side consumed
107 | a[i] = aux[rightIdx++];
108 | } else if (rightIdx > hi) {
109 | // right side consumed
110 | a[i] = aux[leftIdx++];
111 | } else if (compare(aux[rightIdx], aux[leftIdx]) < 0) {
112 | // right element is less -> comes first
113 | a[i] = aux[rightIdx++];
114 | } else {
115 | // left element comes first (less or equal)
116 | a[i] = aux[leftIdx++];
117 | }
118 | }
119 | }
120 |
121 | function _sort(a: T[], compare: Compare, lo: number, hi: number, aux: T[]) {
122 | if (hi <= lo) {
123 | return;
124 | }
125 | const mid = lo + ((hi - lo) / 2) | 0;
126 | _sort(a, compare, lo, mid, aux);
127 | _sort(a, compare, mid + 1, hi, aux);
128 | if (compare(a[mid], a[mid + 1]) <= 0) {
129 | // left and right are sorted and if the last-left element is less
130 | // or equals than the first-right element there is nothing else
131 | // to do
132 | return;
133 | }
134 | _merge(a, compare, lo, mid, hi, aux);
135 | }
136 |
137 |
138 | export function groupBy(data: ReadonlyArray, compare: (a: T, b: T) => number): T[][] {
139 | const result: T[][] = [];
140 | let currentGroup: T[] | undefined = undefined;
141 | for (const element of mergeSort(data.slice(0), compare)) {
142 | if (!currentGroup || compare(currentGroup[0], element) !== 0) {
143 | currentGroup = [element];
144 | result.push(currentGroup);
145 | } else {
146 | currentGroup.push(element);
147 | }
148 | }
149 | return result;
150 | }
151 |
152 | interface IMutableSplice extends ISplice {
153 | deleteCount: number;
154 | }
155 |
156 | /**
157 | * Diffs two *sorted* arrays and computes the splices which apply the diff.
158 | */
159 | export function sortedDiff(before: ReadonlyArray, after: ReadonlyArray, compare: (a: T, b: T) => number): ISplice[] {
160 | const result: IMutableSplice[] = [];
161 |
162 | function pushSplice(start: number, deleteCount: number, toInsert: T[]): void {
163 | if (deleteCount === 0 && toInsert.length === 0) {
164 | return;
165 | }
166 |
167 | const latest = result[result.length - 1];
168 |
169 | if (latest && latest.start + latest.deleteCount === start) {
170 | latest.deleteCount += deleteCount;
171 | latest.toInsert.push(...toInsert);
172 | } else {
173 | result.push({ start, deleteCount, toInsert });
174 | }
175 | }
176 |
177 | let beforeIdx = 0;
178 | let afterIdx = 0;
179 |
180 | while (true) {
181 | if (beforeIdx === before.length) {
182 | pushSplice(beforeIdx, 0, after.slice(afterIdx));
183 | break;
184 | }
185 | if (afterIdx === after.length) {
186 | pushSplice(beforeIdx, before.length - beforeIdx, []);
187 | break;
188 | }
189 |
190 | const beforeElement = before[beforeIdx];
191 | const afterElement = after[afterIdx];
192 | const n = compare(beforeElement, afterElement);
193 | if (n === 0) {
194 | // equal
195 | beforeIdx += 1;
196 | afterIdx += 1;
197 | } else if (n < 0) {
198 | // beforeElement is smaller -> before element removed
199 | pushSplice(beforeIdx, 1, []);
200 | beforeIdx += 1;
201 | } else if (n > 0) {
202 | // beforeElement is greater -> after element added
203 | pushSplice(beforeIdx, 0, [afterElement]);
204 | afterIdx += 1;
205 | }
206 | }
207 |
208 | return result;
209 | }
210 |
211 | /**
212 | * Takes two *sorted* arrays and computes their delta (removed, added elements).
213 | * Finishes in `Math.min(before.length, after.length)` steps.
214 | */
215 | export function delta(before: ReadonlyArray, after: ReadonlyArray, compare: (a: T, b: T) => number): { removed: T[], added: T[] } {
216 | const splices = sortedDiff(before, after, compare);
217 | const removed: T[] = [];
218 | const added: T[] = [];
219 |
220 | for (const splice of splices) {
221 | removed.push(...before.slice(splice.start, splice.start + splice.deleteCount));
222 | added.push(...splice.toInsert);
223 | }
224 |
225 | return { removed, added };
226 | }
227 |
228 | /**
229 | * Returns the top N elements from the array.
230 | *
231 | * Faster than sorting the entire array when the array is a lot larger than N.
232 | *
233 | * @param array The unsorted array.
234 | * @param compare A sort function for the elements.
235 | * @param n The number of elements to return.
236 | * @return The first n elemnts from array when sorted with compare.
237 | */
238 | export function top(array: ReadonlyArray, compare: (a: T, b: T) => number, n: number): T[] {
239 | if (n === 0) {
240 | return [];
241 | }
242 | const result = array.slice(0, n).sort(compare);
243 | topStep(array, compare, result, n, array.length);
244 | return result;
245 | }
246 |
247 | /**
248 | * Asynchronous variant of `top()` allowing for splitting up work in batches between which the event loop can run.
249 | *
250 | * Returns the top N elements from the array.
251 | *
252 | * Faster than sorting the entire array when the array is a lot larger than N.
253 | *
254 | * @param array The unsorted array.
255 | * @param compare A sort function for the elements.
256 | * @param n The number of elements to return.
257 | * @param batch The number of elements to examine before yielding to the event loop.
258 | * @return The first n elemnts from array when sorted with compare.
259 | */
260 | export function topAsync(array: T[], compare: (a: T, b: T) => number, n: number, batch: number, token?: CancellationToken): Promise {
261 | if (n === 0) {
262 | return Promise.resolve([]);
263 | }
264 |
265 | return new Promise((resolve, reject) => {
266 | (async () => {
267 | const o = array.length;
268 | const result = array.slice(0, n).sort(compare);
269 | for (let i = n, m = Math.min(n + batch, o); i < o; i = m, m = Math.min(m + batch, o)) {
270 | if (i > n) {
271 | await new Promise(resolve => setTimeout(resolve)); // nextTick() would starve I/O.
272 | }
273 | if (token && token.isCancellationRequested) {
274 | throw canceled();
275 | }
276 | topStep(array, compare, result, i, m);
277 | }
278 | return result;
279 | })()
280 | .then(resolve, reject);
281 | });
282 | }
283 |
284 | function topStep(array: ReadonlyArray, compare: (a: T, b: T) => number, result: T[], i: number, m: number): void {
285 | for (const n = result.length; i < m; i++) {
286 | const element = array[i];
287 | if (compare(element, result[n - 1]) < 0) {
288 | result.pop();
289 | const j = findFirstInSorted(result, e => compare(element, e) < 0);
290 | result.splice(j, 0, element);
291 | }
292 | }
293 | }
294 |
295 | /**
296 | * @returns New array with all falsy values removed. The original array IS NOT modified.
297 | */
298 | export function coalesce(array: ReadonlyArray): T[] {
299 | return array.filter(e => !!e);
300 | }
301 |
302 | /**
303 | * Remove all falsey values from `array`. The original array IS modified.
304 | */
305 | export function coalesceInPlace(array: Array): void {
306 | let to = 0;
307 | for (let i = 0; i < array.length; i++) {
308 | if (!!array[i]) {
309 | array[to] = array[i];
310 | to += 1;
311 | }
312 | }
313 | array.length = to;
314 | }
315 |
316 | /**
317 | * Moves the element in the array for the provided positions.
318 | */
319 | export function move(array: any[], from: number, to: number): void {
320 | array.splice(to, 0, array.splice(from, 1)[0]);
321 | }
322 |
323 | /**
324 | * @returns false if the provided object is an array and not empty.
325 | */
326 | export function isFalsyOrEmpty(obj: any): boolean {
327 | return !Array.isArray(obj) || obj.length === 0;
328 | }
329 |
330 | /**
331 | * @returns True if the provided object is an array and has at least one element.
332 | */
333 | export function isNonEmptyArray(obj: T[] | undefined | null): obj is T[];
334 | export function isNonEmptyArray(obj: readonly T[] | undefined | null): obj is readonly T[];
335 | export function isNonEmptyArray(obj: T[] | readonly T[] | undefined | null): obj is T[] | readonly T[] {
336 | return Array.isArray(obj) && obj.length > 0;
337 | }
338 |
339 | /**
340 | * Removes duplicates from the given array. The optional keyFn allows to specify
341 | * how elements are checked for equalness by returning a unique string for each.
342 | */
343 | export function distinct(array: ReadonlyArray, keyFn?: (t: T) => string): T[] {
344 | if (!keyFn) {
345 | return array.filter((element, position) => {
346 | return array.indexOf(element) === position;
347 | });
348 | }
349 |
350 | const seen: { [key: string]: boolean; } = Object.create(null);
351 | return array.filter((elem) => {
352 | const key = keyFn(elem);
353 | if (seen[key]) {
354 | return false;
355 | }
356 |
357 | seen[key] = true;
358 |
359 | return true;
360 | });
361 | }
362 |
363 | export function distinctES6(array: ReadonlyArray): T[] {
364 | const seen = new Set();
365 | return array.filter(element => {
366 | if (seen.has(element)) {
367 | return false;
368 | }
369 |
370 | seen.add(element);
371 | return true;
372 | });
373 | }
374 |
375 | export function uniqueFilter(keyFn: (t: T) => string): (t: T) => boolean {
376 | const seen: { [key: string]: boolean; } = Object.create(null);
377 |
378 | return element => {
379 | const key = keyFn(element);
380 |
381 | if (seen[key]) {
382 | return false;
383 | }
384 |
385 | seen[key] = true;
386 | return true;
387 | };
388 | }
389 |
390 | export function lastIndex(array: ReadonlyArray, fn: (item: T) => boolean): number {
391 | for (let i = array.length - 1; i >= 0; i--) {
392 | const element = array[i];
393 |
394 | if (fn(element)) {
395 | return i;
396 | }
397 | }
398 |
399 | return -1;
400 | }
401 |
402 | /**
403 | * @deprecated ES6: use `Array.findIndex`
404 | */
405 | export function firstIndex(array: ReadonlyArray, fn: (item: T) => boolean): number {
406 | for (let i = 0; i < array.length; i++) {
407 | const element = array[i];
408 |
409 | if (fn(element)) {
410 | return i;
411 | }
412 | }
413 |
414 | return -1;
415 | }
416 |
417 |
418 | /**
419 | * @deprecated ES6: use `Array.find`
420 | */
421 | export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T): T;
422 | export function first(array: ReadonlyArray, fn: (item: T) => boolean): T | undefined;
423 | export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T | undefined = undefined): T | undefined {
424 | const index = firstIndex(array, fn);
425 | return index < 0 ? notFoundValue : array[index];
426 | }
427 |
428 | export function firstOrDefault(array: ReadonlyArray, notFoundValue: NotFound): T | NotFound;
429 | export function firstOrDefault(array: ReadonlyArray): T | undefined;
430 | export function firstOrDefault(array: ReadonlyArray, notFoundValue?: NotFound): T | NotFound | undefined {
431 | return array.length > 0 ? array[0] : notFoundValue;
432 | }
433 |
434 | export function commonPrefixLength(one: ReadonlyArray, other: ReadonlyArray, equals: (a: T, b: T) => boolean = (a, b) => a === b): number {
435 | let result = 0;
436 |
437 | for (let i = 0, len = Math.min(one.length, other.length); i < len && equals(one[i], other[i]); i++) {
438 | result++;
439 | }
440 |
441 | return result;
442 | }
443 |
444 | export function flatten(arr: T[][]): T[] {
445 | return ([]).concat(...arr);
446 | }
447 |
448 | export function range(to: number): number[];
449 | export function range(from: number, to: number): number[];
450 | export function range(arg: number, to?: number): number[] {
451 | let from = typeof to === 'number' ? arg : 0;
452 |
453 | if (typeof to === 'number') {
454 | from = arg;
455 | } else {
456 | from = 0;
457 | to = arg;
458 | }
459 |
460 | const result: number[] = [];
461 |
462 | if (from <= to) {
463 | for (let i = from; i < to; i++) {
464 | result.push(i);
465 | }
466 | } else {
467 | for (let i = from; i > to; i--) {
468 | result.push(i);
469 | }
470 | }
471 |
472 | return result;
473 | }
474 |
475 | export function index(array: ReadonlyArray, indexer: (t: T) => string): { [key: string]: T; };
476 | export function index(array: ReadonlyArray, indexer: (t: T) => string, mapper: (t: T) => R): { [key: string]: R; };
477 | export function index(array: ReadonlyArray, indexer: (t: T) => string, mapper?: (t: T) => R): { [key: string]: R; } {
478 | return array.reduce((r, t) => {
479 | r[indexer(t)] = mapper ? mapper(t) : t;
480 | return r;
481 | }, Object.create(null));
482 | }
483 |
484 | /**
485 | * Inserts an element into an array. Returns a function which, when
486 | * called, will remove that element from the array.
487 | */
488 | export function insert(array: T[], element: T): () => void {
489 | array.push(element);
490 |
491 | return () => remove(array, element);
492 | }
493 |
494 | /**
495 | * Removes an element from an array if it can be found.
496 | */
497 | export function remove(array: T[], element: T): T | undefined {
498 | const index = array.indexOf(element);
499 | if (index > -1) {
500 | array.splice(index, 1);
501 |
502 | return element;
503 | }
504 |
505 | return undefined;
506 | }
507 |
508 | /**
509 | * Insert `insertArr` inside `target` at `insertIndex`.
510 | * Please don't touch unless you understand https://jsperf.com/inserting-an-array-within-an-array
511 | */
512 | export function arrayInsert(target: T[], insertIndex: number, insertArr: T[]): T[] {
513 | const before = target.slice(0, insertIndex);
514 | const after = target.slice(insertIndex);
515 | return before.concat(insertArr, after);
516 | }
517 |
518 | /**
519 | * Uses Fisher-Yates shuffle to shuffle the given array
520 | */
521 | export function shuffle(array: T[], _seed?: number): void {
522 | let rand: () => number;
523 |
524 | if (typeof _seed === 'number') {
525 | let seed = _seed;
526 | // Seeded random number generator in JS. Modified from:
527 | // https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript
528 | rand = () => {
529 | const x = Math.sin(seed++) * 179426549; // throw away most significant digits and reduce any potential bias
530 | return x - Math.floor(x);
531 | };
532 | } else {
533 | rand = Math.random;
534 | }
535 |
536 | for (let i = array.length - 1; i > 0; i -= 1) {
537 | const j = Math.floor(rand() * (i + 1));
538 | const temp = array[i];
539 | array[i] = array[j];
540 | array[j] = temp;
541 | }
542 | }
543 |
544 | /**
545 | * Pushes an element to the start of the array, if found.
546 | */
547 | export function pushToStart(arr: T[], value: T): void {
548 | const index = arr.indexOf(value);
549 |
550 | if (index > -1) {
551 | arr.splice(index, 1);
552 | arr.unshift(value);
553 | }
554 | }
555 |
556 | /**
557 | * Pushes an element to the end of the array, if found.
558 | */
559 | export function pushToEnd(arr: T[], value: T): void {
560 | const index = arr.indexOf(value);
561 |
562 | if (index > -1) {
563 | arr.splice(index, 1);
564 | arr.push(value);
565 | }
566 | }
567 |
568 |
569 | /**
570 | * @deprecated ES6: use `Array.find`
571 | */
572 | export function find(arr: ArrayLike, predicate: (value: T, index: number, arr: ArrayLike) => any): T | undefined {
573 | for (let i = 0; i < arr.length; i++) {
574 | const element = arr[i];
575 | if (predicate(element, i, arr)) {
576 | return element;
577 | }
578 | }
579 |
580 | return undefined;
581 | }
582 |
583 | export function mapArrayOrNot(items: T | T[], fn: (_: T) => U): U | U[] {
584 | return Array.isArray(items) ?
585 | items.map(fn) :
586 | fn(items);
587 | }
588 |
589 | export function asArray(x: T | T[]): T[] {
590 | return Array.isArray(x) ? x : [x];
591 | }
592 |
593 | /**
594 | * @deprecated Use `Array.from` or `[...iter]`
595 | */
596 | export function toArray(iterable: IterableIterator): T[] {
597 | const result: T[] = [];
598 | for (let element of iterable) {
599 | result.push(element);
600 | }
601 | return result;
602 | }
603 |
604 | export function getRandomElement(arr: T[]): T | undefined {
605 | return arr[Math.floor(Math.random() * arr.length)];
606 | }
607 |
--------------------------------------------------------------------------------