deploy: current vibn theia state
Made-with: Cursor
This commit is contained in:
10
packages/monaco/.eslintrc.js
Normal file
10
packages/monaco/.eslintrc.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
extends: [
|
||||
'../../configs/build.eslintrc.json'
|
||||
],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: 'tsconfig.json'
|
||||
}
|
||||
};
|
||||
113
packages/monaco/README.md
Normal file
113
packages/monaco/README.md
Normal file
@@ -0,0 +1,113 @@
|
||||
<div align='center'>
|
||||
|
||||
<br />
|
||||
|
||||
<img src='https://raw.githubusercontent.com/eclipse-theia/theia/master/logo/theia.svg?sanitize=true' alt='theia-ext-logo' width='100px' />
|
||||
|
||||
<h2>ECLIPSE THEIA - MONACO EXTENSION</h2>
|
||||
|
||||
<hr />
|
||||
|
||||
</div>
|
||||
|
||||
## Description
|
||||
|
||||
The `@theia/monaco` extension contributes the integration of the [monaco-editor](https://microsoft.github.io/monaco-editor/).\
|
||||
This includes:
|
||||
|
||||
- full-feature code editor
|
||||
- diff-editor
|
||||
- code snippets
|
||||
- textmate grammars (theme registry, service)
|
||||
|
||||
## Monaco Uplifts
|
||||
|
||||
This package is intended to be the interface between the `@theia/monaco-editor-core` package, the project's bundling of the `monaco-editor-core` package published by the VSCode
|
||||
team, and the rest of the application. When we uplift to a new version of `monaco-editor-core`, this package will need to be checked particularly thoroughly. To facilitate that
|
||||
process, the steps for undertaking a Monaco uplift are outlined here.
|
||||
|
||||
### Setting up the VSCode side
|
||||
|
||||
1. Clone the VSCode repo and make sure you have the following remotes:
|
||||
|
||||
- <https://github.com/microsoft/vscode.git> - the official VSCode repo.
|
||||
- <https://github.com/theia-ide/vscode.git> - Theia's fork.
|
||||
|
||||
2. Find the latest release tag in the official VSCode repo, and the most recent uplift branch in the Theia fork.
|
||||
|
||||
> At the time of this writing the latest release tag is `1.96.3`, and the uplift branch is `monaco-uplift-1.96.3`
|
||||
|
||||
3. Check out the release tag, cherry pick the tip of the uplift branch, and resolve any conflicts.
|
||||
|
||||
> As you resolve conflicts and make changes to the VSCode repo, make sure you end up with a single commit on the uplift branch to make it easier for the next person to rebase.
|
||||
|
||||
4. Try to build. At the moment, this means running `npm install` and `npm run gulp editor-distro`.
|
||||
5. Fix any build errors that arise.
|
||||
6. Change the version in `build/monaco/package.json`
|
||||
|
||||
#### Current State
|
||||
|
||||
- build/gulpfile.editor.js: various changes to modify the treeshaking and output destinations.
|
||||
- build/lib/standalone.js/ts: changes to output sourcemaps etc. One small change to fix a build error due to having a directory named `model` and a file named `model.ts` in the same folder.
|
||||
- src/vs/base/browser/dompurify/dompurify.js changes for CommonJS rather than ESM
|
||||
- src/vs/base/common/marked/marked.js changes for CommonJS rather than ESM
|
||||
|
||||
### Setting up the Theia side
|
||||
|
||||
For initial testing, it's easier to point dependencies to your local VSCode.
|
||||
|
||||
1. Having built `monaco-editor-core` using the steps [above](#setting-up-the-vscode-side).
|
||||
2. Find all references to `@theia/monaco-editor-core` in `package.json`s and replace their version with `"link:<path to your local build of monaco-editor-core>"`.
|
||||
|
||||
> Using `link:` means that if you subsequently make changes on the VSCode side, you only need to rebuild VSCode and then rebuild Theia to see the effects.
|
||||
|
||||
3. Delete your `node_modules` and `npm install` and build Theia.
|
||||
4. Fix any build errors.
|
||||
5. Uncomment the `bindMonacoPreferenceExtractor` function in `examples/api-samples/src/browser/monaco-editor-preferences/monaco-editor-preference-extractor.ts` and run the commands there. Fix the `EditorGeneratedPreferenceSchema` as necessary, and add or remove validations from the `MonacoFrontendApplicationContribution` as appropriate.
|
||||
6. Look for comments that indicate forced types or other code smells that would prevent a build error from being thrown where it should be thrown and check that the assertion still applies.
|
||||
|
||||
> If you add these, mark them with @monaco-uplift - that'll make them easier to find in the future. Better: if you can remove them, do! Typically, the cause is mixing imports from
|
||||
private API and public API. Often public API fails to satisfy private declarations.
|
||||
|
||||
7. Test the application thoroughly - make sure everything's still working.
|
||||
|
||||
> It may also be necessary to update our various `vscode` dependencies to match the current state of VSCode. It may not be necessary to upgrade all (or any) of these to successfully adopt a new Monaco version, but if something is misbehaving inexplicably, checking dependencies is a reasonable place to start. Check on:
|
||||
>
|
||||
> - `vscode-debugprotocol`
|
||||
> - `vscode-languageserver-protocol`
|
||||
> - `vscode-oniguruma`
|
||||
> - `vscode-proxy-agent`
|
||||
> - `vscode-ripgrep`
|
||||
> - `vscode-textmate`
|
||||
> - `vscode-uri`
|
||||
|
||||
### Publishing for testing
|
||||
|
||||
Once you believe that everything is in working order, you'll need to publish the new `@theia/monaco-editor-core` for testing. The instructions for doing so are
|
||||
[here](https://github.com/theia-ide/vscode/wiki/Publish-%60@theia-monaco-editor-core%60). Once the package is published, point your `package.json`s at the testing version and make
|
||||
sure everything still works, then make a PR.
|
||||
|
||||
## Additional Information
|
||||
|
||||
- [API documentation for `@theia/monaco`](https://eclipse-theia.github.io/theia/docs/next/modules/_theia_monaco.html)
|
||||
- [Theia - GitHub](https://github.com/eclipse-theia/theia)
|
||||
- [Theia - Website](https://theia-ide.org/)
|
||||
|
||||
## License
|
||||
|
||||
- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
|
||||
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
|
||||
|
||||
## Trademark
|
||||
|
||||
"Theia" is a trademark of the Eclipse Foundation
|
||||
<https://www.eclipse.org/theia>
|
||||
|
||||
# Theia - Monaco Extension
|
||||
|
||||
See [here](https://www.theia-ide.org/doc/index.html) for a detailed documentation.
|
||||
|
||||
## License
|
||||
|
||||
- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
|
||||
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
|
||||
205
packages/monaco/data/monaco-themes/vscode/dark_plus.json
Normal file
205
packages/monaco/data/monaco-themes/vscode/dark_plus.json
Normal file
@@ -0,0 +1,205 @@
|
||||
{
|
||||
"$schema": "vscode://schemas/color-theme",
|
||||
"name": "Dark+",
|
||||
"include": "./dark_vs.json",
|
||||
"tokenColors": [
|
||||
{
|
||||
"name": "Function declarations",
|
||||
"scope": [
|
||||
"entity.name.function",
|
||||
"support.function",
|
||||
"support.constant.handlebars",
|
||||
"source.powershell variable.other.member",
|
||||
"entity.name.operator.custom-literal"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#DCDCAA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Types declaration and references",
|
||||
"scope": [
|
||||
"support.class",
|
||||
"support.type",
|
||||
"entity.name.type",
|
||||
"entity.name.namespace",
|
||||
"entity.other.attribute",
|
||||
"entity.name.scope-resolution",
|
||||
"entity.name.class",
|
||||
"storage.type.numeric.go",
|
||||
"storage.type.byte.go",
|
||||
"storage.type.boolean.go",
|
||||
"storage.type.string.go",
|
||||
"storage.type.uintptr.go",
|
||||
"storage.type.error.go",
|
||||
"storage.type.rune.go",
|
||||
"storage.type.cs",
|
||||
"storage.type.generic.cs",
|
||||
"storage.type.modifier.cs",
|
||||
"storage.type.variable.cs",
|
||||
"storage.type.annotation.java",
|
||||
"storage.type.generic.java",
|
||||
"storage.type.java",
|
||||
"storage.type.object.array.java",
|
||||
"storage.type.primitive.array.java",
|
||||
"storage.type.primitive.java",
|
||||
"storage.type.token.java",
|
||||
"storage.type.groovy",
|
||||
"storage.type.annotation.groovy",
|
||||
"storage.type.parameters.groovy",
|
||||
"storage.type.generic.groovy",
|
||||
"storage.type.object.array.groovy",
|
||||
"storage.type.primitive.array.groovy",
|
||||
"storage.type.primitive.groovy"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#4EC9B0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Types declaration and references, TS grammar specific",
|
||||
"scope": [
|
||||
"meta.type.cast.expr",
|
||||
"meta.type.new.expr",
|
||||
"support.constant.math",
|
||||
"support.constant.dom",
|
||||
"support.constant.json",
|
||||
"entity.other.inherited-class",
|
||||
"punctuation.separator.namespace.ruby"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#4EC9B0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Control flow / Special keywords",
|
||||
"scope": [
|
||||
"keyword.control",
|
||||
"source.cpp keyword.operator.new",
|
||||
"keyword.operator.delete",
|
||||
"keyword.other.using",
|
||||
"keyword.other.directive.using",
|
||||
"keyword.other.operator",
|
||||
"entity.name.operator"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#C586C0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Variable and parameter name",
|
||||
"scope": [
|
||||
"variable",
|
||||
"meta.definition.variable.name",
|
||||
"support.variable",
|
||||
"entity.name.variable",
|
||||
"constant.other.placeholder"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#9CDCFE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Constants and enums",
|
||||
"scope": [
|
||||
"variable.other.constant",
|
||||
"variable.other.enummember"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#4FC1FF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Object keys, TS grammar specific",
|
||||
"scope": [
|
||||
"meta.object-literal.key"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#9CDCFE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CSS property value",
|
||||
"scope": [
|
||||
"support.constant.property-value",
|
||||
"support.constant.font-name",
|
||||
"support.constant.media-type",
|
||||
"support.constant.media",
|
||||
"constant.other.color.rgb-value",
|
||||
"constant.other.rgb-value",
|
||||
"support.constant.color"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#CE9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Regular expression groups",
|
||||
"scope": [
|
||||
"punctuation.definition.group.regexp",
|
||||
"punctuation.definition.group.assertion.regexp",
|
||||
"punctuation.definition.character-class.regexp",
|
||||
"punctuation.character.set.begin.regexp",
|
||||
"punctuation.character.set.end.regexp",
|
||||
"keyword.operator.negation.regexp",
|
||||
"support.other.parenthesis.regexp"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#CE9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant.character.character-class.regexp",
|
||||
"constant.other.character-class.set.regexp",
|
||||
"constant.other.character-class.regexp",
|
||||
"constant.character.set.regexp"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#d16969"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword.operator.or.regexp",
|
||||
"keyword.control.anchor.regexp"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#DCDCAA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.operator.quantifier.regexp",
|
||||
"settings": {
|
||||
"foreground": "#d7ba7d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant.character",
|
||||
"constant.other.option"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.character.escape",
|
||||
"settings": {
|
||||
"foreground": "#d7ba7d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.name.label",
|
||||
"settings": {
|
||||
"foreground": "#C8C8C8"
|
||||
}
|
||||
}
|
||||
],
|
||||
"semanticTokenColors": {
|
||||
"newOperator": "#C586C0",
|
||||
"stringLiteral": "#ce9178",
|
||||
"customLiteral": "#DCDCAA",
|
||||
"numberLiteral": "#b5cea8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "vscode://schemas/color-theme",
|
||||
"name": "Dark (Theia)",
|
||||
"include": "./dark_plus.json"
|
||||
}
|
||||
411
packages/monaco/data/monaco-themes/vscode/dark_vs.json
Normal file
411
packages/monaco/data/monaco-themes/vscode/dark_vs.json
Normal file
@@ -0,0 +1,411 @@
|
||||
{
|
||||
"$schema": "vscode://schemas/color-theme",
|
||||
"name": "Dark (Visual Studio)",
|
||||
"colors": {
|
||||
"checkbox.border": "#6B6B6B",
|
||||
"editor.background": "#1E1E1E",
|
||||
"editor.foreground": "#D4D4D4",
|
||||
"editor.inactiveSelectionBackground": "#3A3D41",
|
||||
"editorIndentGuide.background1": "#404040",
|
||||
"editorIndentGuide.activeBackground1": "#707070",
|
||||
"editor.selectionHighlightBackground": "#ADD6FF26",
|
||||
"list.dropBackground": "#383B3D",
|
||||
"activityBarBadge.background": "#007ACC",
|
||||
"sideBarTitle.foreground": "#BBBBBB",
|
||||
"input.placeholderForeground": "#A6A6A6",
|
||||
"menu.background": "#252526",
|
||||
"menu.foreground": "#CCCCCC",
|
||||
"menu.separatorBackground": "#454545",
|
||||
"menu.border": "#454545",
|
||||
"menu.selectionBackground": "#0078d4",
|
||||
"statusBarItem.remoteForeground": "#FFF",
|
||||
"statusBarItem.remoteBackground": "#16825D",
|
||||
"ports.iconRunningProcessForeground": "#369432",
|
||||
"sideBarSectionHeader.background": "#0000",
|
||||
"sideBarSectionHeader.border": "#ccc3",
|
||||
"tab.selectedBackground": "#222222",
|
||||
"tab.selectedForeground": "#ffffffa0",
|
||||
"tab.lastPinnedBorder": "#ccc3",
|
||||
"list.activeSelectionIconForeground": "#FFF",
|
||||
"terminal.inactiveSelectionBackground": "#3A3D41",
|
||||
"widget.border": "#303031",
|
||||
"actionBar.toggledBackground": "#383a49"
|
||||
},
|
||||
"tokenColors": [
|
||||
{
|
||||
"scope": [
|
||||
"meta.embedded",
|
||||
"source.groovy.embedded",
|
||||
"string meta.image.inline.markdown",
|
||||
"variable.legacy.builtin.python"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#D4D4D4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "emphasis",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "strong",
|
||||
"settings": {
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "header",
|
||||
"settings": {
|
||||
"foreground": "#000080"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "comment",
|
||||
"settings": {
|
||||
"foreground": "#6A9955"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.language",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant.numeric",
|
||||
"variable.other.enummember",
|
||||
"keyword.operator.plus.exponent",
|
||||
"keyword.operator.minus.exponent"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#b5cea8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.regexp",
|
||||
"settings": {
|
||||
"foreground": "#646695"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.name.tag",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.name.tag.css",
|
||||
"entity.name.tag.less"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#d7ba7d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.other.attribute-name",
|
||||
"settings": {
|
||||
"foreground": "#9cdcfe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.other.attribute-name.class.css",
|
||||
"source.css entity.other.attribute-name.class",
|
||||
"entity.other.attribute-name.id.css",
|
||||
"entity.other.attribute-name.parent-selector.css",
|
||||
"entity.other.attribute-name.parent.less",
|
||||
"source.css entity.other.attribute-name.pseudo-class",
|
||||
"entity.other.attribute-name.pseudo-element.css",
|
||||
"source.css.less entity.other.attribute-name.id",
|
||||
"entity.other.attribute-name.scss"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#d7ba7d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "invalid",
|
||||
"settings": {
|
||||
"foreground": "#f44747"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.underline",
|
||||
"settings": {
|
||||
"fontStyle": "underline"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.bold",
|
||||
"settings": {
|
||||
"fontStyle": "bold",
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.heading",
|
||||
"settings": {
|
||||
"fontStyle": "bold",
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.italic",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.strikethrough",
|
||||
"settings": {
|
||||
"fontStyle": "strikethrough"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.inserted",
|
||||
"settings": {
|
||||
"foreground": "#b5cea8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.deleted",
|
||||
"settings": {
|
||||
"foreground": "#ce9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.changed",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "punctuation.definition.quote.begin.markdown",
|
||||
"settings": {
|
||||
"foreground": "#6A9955"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "punctuation.definition.list.begin.markdown",
|
||||
"settings": {
|
||||
"foreground": "#6796e6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.inline.raw",
|
||||
"settings": {
|
||||
"foreground": "#ce9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "brackets of XML/HTML tags",
|
||||
"scope": "punctuation.definition.tag",
|
||||
"settings": {
|
||||
"foreground": "#808080"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"meta.preprocessor",
|
||||
"entity.name.function.preprocessor"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.preprocessor.string",
|
||||
"settings": {
|
||||
"foreground": "#ce9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.preprocessor.numeric",
|
||||
"settings": {
|
||||
"foreground": "#b5cea8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.structure.dictionary.key.python",
|
||||
"settings": {
|
||||
"foreground": "#9cdcfe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.diff.header",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "storage",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "storage.type",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"storage.modifier",
|
||||
"keyword.operator.noexcept"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"string",
|
||||
"meta.embedded.assembly"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#ce9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "string.tag",
|
||||
"settings": {
|
||||
"foreground": "#ce9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "string.value",
|
||||
"settings": {
|
||||
"foreground": "#ce9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "string.regexp",
|
||||
"settings": {
|
||||
"foreground": "#d16969"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "String interpolation",
|
||||
"scope": [
|
||||
"punctuation.definition.template-expression.begin",
|
||||
"punctuation.definition.template-expression.end",
|
||||
"punctuation.section.embedded"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Reset JavaScript string interpolation expression",
|
||||
"scope": [
|
||||
"meta.template.expression"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#d4d4d4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.type.vendored.property-name",
|
||||
"support.type.property-name",
|
||||
"source.css variable",
|
||||
"source.coffee.embedded"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#9cdcfe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.control",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.operator",
|
||||
"settings": {
|
||||
"foreground": "#d4d4d4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword.operator.new",
|
||||
"keyword.operator.expression",
|
||||
"keyword.operator.cast",
|
||||
"keyword.operator.sizeof",
|
||||
"keyword.operator.alignof",
|
||||
"keyword.operator.typeid",
|
||||
"keyword.operator.alignas",
|
||||
"keyword.operator.instanceof",
|
||||
"keyword.operator.logical.python",
|
||||
"keyword.operator.wordlike"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.other.unit",
|
||||
"settings": {
|
||||
"foreground": "#b5cea8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"punctuation.section.embedded.begin.php",
|
||||
"punctuation.section.embedded.end.php"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "support.function.git-rebase",
|
||||
"settings": {
|
||||
"foreground": "#9cdcfe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.sha.git-rebase",
|
||||
"settings": {
|
||||
"foreground": "#b5cea8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "coloring of the Java import and package identifiers",
|
||||
"scope": [
|
||||
"storage.modifier.import.java",
|
||||
"variable.language.wildcard.java",
|
||||
"storage.modifier.package.java"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#d4d4d4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "this.self",
|
||||
"scope": "variable.language",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
}
|
||||
],
|
||||
"semanticHighlighting": true,
|
||||
"semanticTokenColors": {
|
||||
"newOperator": "#d4d4d4",
|
||||
"stringLiteral": "#ce9178",
|
||||
"customLiteral": "#D4D4D4",
|
||||
"numberLiteral": "#b5cea8"
|
||||
}
|
||||
}
|
||||
469
packages/monaco/data/monaco-themes/vscode/hc_black.json
Normal file
469
packages/monaco/data/monaco-themes/vscode/hc_black.json
Normal file
@@ -0,0 +1,469 @@
|
||||
{
|
||||
"$schema": "vscode://schemas/color-theme",
|
||||
"name": "Dark High Contrast",
|
||||
"colors": {
|
||||
"editor.background": "#000000",
|
||||
"editor.foreground": "#FFFFFF",
|
||||
"editorIndentGuide.background1": "#FFFFFF",
|
||||
"editorIndentGuide.activeBackground1": "#FFFFFF",
|
||||
"sideBarTitle.foreground": "#FFFFFF",
|
||||
"selection.background": "#008000",
|
||||
"editor.selectionBackground": "#FFFFFF",
|
||||
"statusBarItem.remoteBackground": "#00000000",
|
||||
"ports.iconRunningProcessForeground": "#FFFFFF",
|
||||
"editorWhitespace.foreground": "#7c7c7c",
|
||||
"actionBar.toggledBackground": "#383a49"
|
||||
},
|
||||
"tokenColors": [
|
||||
{
|
||||
"scope": [
|
||||
"meta.embedded",
|
||||
"source.groovy.embedded",
|
||||
"string meta.image.inline.markdown",
|
||||
"variable.legacy.builtin.python"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#FFFFFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "emphasis",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "strong",
|
||||
"settings": {
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.diff.header",
|
||||
"settings": {
|
||||
"foreground": "#000080"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "comment",
|
||||
"settings": {
|
||||
"foreground": "#7ca668"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.language",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant.numeric",
|
||||
"constant.other.color.rgb-value",
|
||||
"constant.other.rgb-value",
|
||||
"support.constant.color"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#b5cea8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.regexp",
|
||||
"settings": {
|
||||
"foreground": "#b46695"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.character",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.name.tag",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.name.tag.css",
|
||||
"entity.name.tag.less"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#d7ba7d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.other.attribute-name",
|
||||
"settings": {
|
||||
"foreground": "#9cdcfe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.other.attribute-name.class.css",
|
||||
"source.css entity.other.attribute-name.class",
|
||||
"entity.other.attribute-name.id.css",
|
||||
"entity.other.attribute-name.parent-selector.css",
|
||||
"entity.other.attribute-name.parent.less",
|
||||
"source.css entity.other.attribute-name.pseudo-class",
|
||||
"entity.other.attribute-name.pseudo-element.css",
|
||||
"source.css.less entity.other.attribute-name.id",
|
||||
"entity.other.attribute-name.scss"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#d7ba7d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "invalid",
|
||||
"settings": {
|
||||
"foreground": "#f44747"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.underline",
|
||||
"settings": {
|
||||
"fontStyle": "underline"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.bold",
|
||||
"settings": {
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.heading",
|
||||
"settings": {
|
||||
"fontStyle": "bold",
|
||||
"foreground": "#6796e6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.italic",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.strikethrough",
|
||||
"settings": {
|
||||
"fontStyle": "strikethrough"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.inserted",
|
||||
"settings": {
|
||||
"foreground": "#b5cea8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.deleted",
|
||||
"settings": {
|
||||
"foreground": "#ce9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.changed",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "brackets of XML/HTML tags",
|
||||
"scope": [
|
||||
"punctuation.definition.tag"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#808080"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.preprocessor",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.preprocessor.string",
|
||||
"settings": {
|
||||
"foreground": "#ce9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.preprocessor.numeric",
|
||||
"settings": {
|
||||
"foreground": "#b5cea8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.structure.dictionary.key.python",
|
||||
"settings": {
|
||||
"foreground": "#9cdcfe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "storage",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "storage.type",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "storage.modifier",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "string",
|
||||
"settings": {
|
||||
"foreground": "#ce9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "string.tag",
|
||||
"settings": {
|
||||
"foreground": "#ce9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "string.value",
|
||||
"settings": {
|
||||
"foreground": "#ce9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "string.regexp",
|
||||
"settings": {
|
||||
"foreground": "#d16969"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "String interpolation",
|
||||
"scope": [
|
||||
"punctuation.definition.template-expression.begin",
|
||||
"punctuation.definition.template-expression.end",
|
||||
"punctuation.section.embedded"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Reset JavaScript string interpolation expression",
|
||||
"scope": [
|
||||
"meta.template.expression"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#ffffff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.type.vendored.property-name",
|
||||
"support.type.property-name",
|
||||
"source.css variable",
|
||||
"source.coffee.embedded"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#d4d4d4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.control",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.operator",
|
||||
"settings": {
|
||||
"foreground": "#d4d4d4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword.operator.new",
|
||||
"keyword.operator.expression",
|
||||
"keyword.operator.cast",
|
||||
"keyword.operator.sizeof",
|
||||
"keyword.operator.logical.python"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.other.unit",
|
||||
"settings": {
|
||||
"foreground": "#b5cea8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "support.function.git-rebase",
|
||||
"settings": {
|
||||
"foreground": "#d4d4d4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.sha.git-rebase",
|
||||
"settings": {
|
||||
"foreground": "#b5cea8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "coloring of the Java import and package identifiers",
|
||||
"scope": [
|
||||
"storage.modifier.import.java",
|
||||
"variable.language.wildcard.java",
|
||||
"storage.modifier.package.java"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#d4d4d4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "coloring of the TS this",
|
||||
"scope": "variable.language.this",
|
||||
"settings": {
|
||||
"foreground": "#569cd6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Function declarations",
|
||||
"scope": [
|
||||
"entity.name.function",
|
||||
"support.function",
|
||||
"support.constant.handlebars",
|
||||
"source.powershell variable.other.member"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#DCDCAA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Types declaration and references",
|
||||
"scope": [
|
||||
"support.class",
|
||||
"support.type",
|
||||
"entity.name.type",
|
||||
"entity.name.namespace",
|
||||
"entity.name.scope-resolution",
|
||||
"entity.name.class",
|
||||
"storage.type.cs",
|
||||
"storage.type.generic.cs",
|
||||
"storage.type.modifier.cs",
|
||||
"storage.type.variable.cs",
|
||||
"storage.type.annotation.java",
|
||||
"storage.type.generic.java",
|
||||
"storage.type.java",
|
||||
"storage.type.object.array.java",
|
||||
"storage.type.primitive.array.java",
|
||||
"storage.type.primitive.java",
|
||||
"storage.type.token.java",
|
||||
"storage.type.groovy",
|
||||
"storage.type.annotation.groovy",
|
||||
"storage.type.parameters.groovy",
|
||||
"storage.type.generic.groovy",
|
||||
"storage.type.object.array.groovy",
|
||||
"storage.type.primitive.array.groovy",
|
||||
"storage.type.primitive.groovy"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#4EC9B0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Types declaration and references, TS grammar specific",
|
||||
"scope": [
|
||||
"meta.type.cast.expr",
|
||||
"meta.type.new.expr",
|
||||
"support.constant.math",
|
||||
"support.constant.dom",
|
||||
"support.constant.json",
|
||||
"entity.other.inherited-class",
|
||||
"punctuation.separator.namespace.ruby"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#4EC9B0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Control flow / Special keywords",
|
||||
"scope": [
|
||||
"keyword.control",
|
||||
"source.cpp keyword.operator.new",
|
||||
"source.cpp keyword.operator.delete",
|
||||
"keyword.other.using",
|
||||
"keyword.other.directive.using",
|
||||
"keyword.other.operator"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#C586C0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Variable and parameter name",
|
||||
"scope": [
|
||||
"variable",
|
||||
"meta.definition.variable.name",
|
||||
"support.variable"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#9CDCFE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Object keys, TS grammar specific",
|
||||
"scope": [
|
||||
"meta.object-literal.key"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#9CDCFE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CSS property value",
|
||||
"scope": [
|
||||
"support.constant.property-value",
|
||||
"support.constant.font-name",
|
||||
"support.constant.media-type",
|
||||
"support.constant.media",
|
||||
"constant.other.color.rgb-value",
|
||||
"constant.other.rgb-value",
|
||||
"support.constant.color"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#CE9178"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "HC Search Editor context line override",
|
||||
"scope": "meta.resultLinePrefix.contextLinePrefix.search",
|
||||
"settings": {
|
||||
"foreground": "#CBEDCB"
|
||||
}
|
||||
}
|
||||
],
|
||||
"semanticHighlighting": true,
|
||||
"semanticTokenColors": {
|
||||
"newOperator": "#FFFFFF",
|
||||
"stringLiteral": "#ce9178",
|
||||
"customLiteral": "#DCDCAA",
|
||||
"numberLiteral": "#b5cea8"
|
||||
}
|
||||
}
|
||||
597
packages/monaco/data/monaco-themes/vscode/hc_light.json
Normal file
597
packages/monaco/data/monaco-themes/vscode/hc_light.json
Normal file
@@ -0,0 +1,597 @@
|
||||
{
|
||||
"$schema": "vscode://schemas/color-theme",
|
||||
"name": "Light High Contrast",
|
||||
"tokenColors": [
|
||||
{
|
||||
"scope": [
|
||||
"meta.embedded",
|
||||
"source.groovy.embedded",
|
||||
"variable.legacy.builtin.python"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#292929"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "emphasis",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "strong",
|
||||
"settings": {
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.diff.header",
|
||||
"settings": {
|
||||
"foreground": "#062F4A"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "comment",
|
||||
"settings": {
|
||||
"foreground": "#515151"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.language",
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant.numeric",
|
||||
"variable.other.enummember",
|
||||
"keyword.operator.plus.exponent",
|
||||
"keyword.operator.minus.exponent"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#096d48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.regexp",
|
||||
"settings": {
|
||||
"foreground": "#811F3F"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.name.tag",
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.name.selector",
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.other.attribute-name",
|
||||
"settings": {
|
||||
"foreground": "#264F78"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.other.attribute-name.class.css",
|
||||
"source.css entity.other.attribute-name.class",
|
||||
"entity.other.attribute-name.id.css",
|
||||
"entity.other.attribute-name.parent-selector.css",
|
||||
"entity.other.attribute-name.parent.less",
|
||||
"source.css entity.other.attribute-name.pseudo-class",
|
||||
"entity.other.attribute-name.pseudo-element.css",
|
||||
"source.css.less entity.other.attribute-name.id",
|
||||
"entity.other.attribute-name.scss"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "invalid",
|
||||
"settings": {
|
||||
"foreground": "#B5200D"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.underline",
|
||||
"settings": {
|
||||
"fontStyle": "underline"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.bold",
|
||||
"settings": {
|
||||
"foreground": "#000080",
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.heading",
|
||||
"settings": {
|
||||
"foreground": "#0F4A85",
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.italic",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.strikethrough",
|
||||
"settings": {
|
||||
"fontStyle": "strikethrough"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.inserted",
|
||||
"settings": {
|
||||
"foreground": "#096d48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.deleted",
|
||||
"settings": {
|
||||
"foreground": "#5A5A5A"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.changed",
|
||||
"settings": {
|
||||
"foreground": "#0451A5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"punctuation.definition.quote.begin.markdown",
|
||||
"punctuation.definition.list.begin.markdown"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0451A5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.inline.raw",
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "punctuation.definition.tag",
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"meta.preprocessor",
|
||||
"entity.name.function.preprocessor"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.preprocessor.string",
|
||||
"settings": {
|
||||
"foreground": "#b5200d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.preprocessor.numeric",
|
||||
"settings": {
|
||||
"foreground": "#096d48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.structure.dictionary.key.python",
|
||||
"settings": {
|
||||
"foreground": "#0451A5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "storage",
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "storage.type",
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"storage.modifier",
|
||||
"keyword.operator.noexcept"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"string",
|
||||
"meta.embedded.assembly"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"string.comment.buffered.block.pug",
|
||||
"string.quoted.pug",
|
||||
"string.interpolated.pug",
|
||||
"string.unquoted.plain.in.yaml",
|
||||
"string.unquoted.plain.out.yaml",
|
||||
"string.unquoted.block.yaml",
|
||||
"string.quoted.single.yaml",
|
||||
"string.quoted.double.xml",
|
||||
"string.quoted.single.xml",
|
||||
"string.unquoted.cdata.xml",
|
||||
"string.quoted.double.html",
|
||||
"string.quoted.single.html",
|
||||
"string.unquoted.html",
|
||||
"string.quoted.single.handlebars",
|
||||
"string.quoted.double.handlebars"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "string.regexp",
|
||||
"settings": {
|
||||
"foreground": "#811F3F"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"punctuation.definition.template-expression.begin",
|
||||
"punctuation.definition.template-expression.end",
|
||||
"punctuation.section.embedded"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"meta.template.expression"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.constant.property-value",
|
||||
"support.constant.font-name",
|
||||
"support.constant.media-type",
|
||||
"support.constant.media",
|
||||
"constant.other.color.rgb-value",
|
||||
"constant.other.rgb-value",
|
||||
"support.constant.color"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0451A5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.type.vendored.property-name",
|
||||
"support.type.property-name",
|
||||
"source.css variable",
|
||||
"source.coffee.embedded"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#264F78"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.type.property-name.json"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0451A5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword",
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.control",
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.operator",
|
||||
"settings": {
|
||||
"foreground": "#000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword.operator.new",
|
||||
"keyword.operator.expression",
|
||||
"keyword.operator.cast",
|
||||
"keyword.operator.sizeof",
|
||||
"keyword.operator.alignof",
|
||||
"keyword.operator.typeid",
|
||||
"keyword.operator.alignas",
|
||||
"keyword.operator.instanceof",
|
||||
"keyword.operator.logical.python",
|
||||
"keyword.operator.wordlike"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.other.unit",
|
||||
"settings": {
|
||||
"foreground": "#096d48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"punctuation.section.embedded.begin.php",
|
||||
"punctuation.section.embedded.end.php"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "support.function.git-rebase",
|
||||
"settings": {
|
||||
"foreground": "#0451A5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.sha.git-rebase",
|
||||
"settings": {
|
||||
"foreground": "#096d48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"storage.modifier.import.java",
|
||||
"variable.language.wildcard.java",
|
||||
"storage.modifier.package.java"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "variable.language",
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.name.function",
|
||||
"support.function",
|
||||
"support.constant.handlebars",
|
||||
"source.powershell variable.other.member",
|
||||
"entity.name.operator.custom-literal"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#5e2cbc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.class",
|
||||
"support.type",
|
||||
"entity.name.type",
|
||||
"entity.name.namespace",
|
||||
"entity.other.attribute",
|
||||
"entity.name.scope-resolution",
|
||||
"entity.name.class",
|
||||
"storage.type.numeric.go",
|
||||
"storage.type.byte.go",
|
||||
"storage.type.boolean.go",
|
||||
"storage.type.string.go",
|
||||
"storage.type.uintptr.go",
|
||||
"storage.type.error.go",
|
||||
"storage.type.rune.go",
|
||||
"storage.type.cs",
|
||||
"storage.type.generic.cs",
|
||||
"storage.type.modifier.cs",
|
||||
"storage.type.variable.cs",
|
||||
"storage.type.annotation.java",
|
||||
"storage.type.generic.java",
|
||||
"storage.type.java",
|
||||
"storage.type.object.array.java",
|
||||
"storage.type.primitive.array.java",
|
||||
"storage.type.primitive.java",
|
||||
"storage.type.token.java",
|
||||
"storage.type.groovy",
|
||||
"storage.type.annotation.groovy",
|
||||
"storage.type.parameters.groovy",
|
||||
"storage.type.generic.groovy",
|
||||
"storage.type.object.array.groovy",
|
||||
"storage.type.primitive.array.groovy",
|
||||
"storage.type.primitive.groovy"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#185E73"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"meta.type.cast.expr",
|
||||
"meta.type.new.expr",
|
||||
"support.constant.math",
|
||||
"support.constant.dom",
|
||||
"support.constant.json",
|
||||
"entity.other.inherited-class",
|
||||
"punctuation.separator.namespace.ruby"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#185E73"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword.control",
|
||||
"source.cpp keyword.operator.new",
|
||||
"source.cpp keyword.operator.delete",
|
||||
"keyword.other.using",
|
||||
"keyword.other.directive.using",
|
||||
"keyword.other.operator",
|
||||
"entity.name.operator"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#b5200d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"variable",
|
||||
"meta.definition.variable.name",
|
||||
"support.variable",
|
||||
"entity.name.variable",
|
||||
"constant.other.placeholder"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#001080"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"variable.other.constant",
|
||||
"variable.other.enummember"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#02715D"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"meta.object-literal.key"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#001080"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.constant.property-value",
|
||||
"support.constant.font-name",
|
||||
"support.constant.media-type",
|
||||
"support.constant.media",
|
||||
"constant.other.color.rgb-value",
|
||||
"constant.other.rgb-value",
|
||||
"support.constant.color"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0451A5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"punctuation.definition.group.regexp",
|
||||
"punctuation.definition.group.assertion.regexp",
|
||||
"punctuation.definition.character-class.regexp",
|
||||
"punctuation.character.set.begin.regexp",
|
||||
"punctuation.character.set.end.regexp",
|
||||
"keyword.operator.negation.regexp",
|
||||
"support.other.parenthesis.regexp"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#D16969"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant.character.character-class.regexp",
|
||||
"constant.other.character-class.set.regexp",
|
||||
"constant.other.character-class.regexp",
|
||||
"constant.character.set.regexp"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#811F3F"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.operator.quantifier.regexp",
|
||||
"settings": {
|
||||
"foreground": "#000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword.operator.or.regexp",
|
||||
"keyword.control.anchor.regexp"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#EE0000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.character",
|
||||
"settings": {
|
||||
"foreground": "#0F4A85"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.character.escape",
|
||||
"settings": {
|
||||
"foreground": "#EE0000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.name.label",
|
||||
"settings": {
|
||||
"foreground": "#000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "token.info-token",
|
||||
"settings": {
|
||||
"foreground": "#316BCD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "token.warn-token",
|
||||
"settings": {
|
||||
"foreground": "#CD9731"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "token.error-token",
|
||||
"settings": {
|
||||
"foreground": "#CD3131"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "token.debug-token",
|
||||
"settings": {
|
||||
"foreground": "#800080"
|
||||
}
|
||||
}
|
||||
],
|
||||
"colors": {
|
||||
"actionBar.toggledBackground": "#dddddd",
|
||||
"statusBarItem.remoteBackground": "#FFFFFF",
|
||||
"statusBarItem.remoteForeground": "#000000"
|
||||
}
|
||||
}
|
||||
5
packages/monaco/data/monaco-themes/vscode/hc_theia.json
Normal file
5
packages/monaco/data/monaco-themes/vscode/hc_theia.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "vscode://schemas/color-theme",
|
||||
"name": "Dark High Contrast (Theia)",
|
||||
"include": "./hc_black.json"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "vscode://schemas/color-theme",
|
||||
"name": "Light High Contrast (Theia)",
|
||||
"include": "./hc_light.json"
|
||||
}
|
||||
206
packages/monaco/data/monaco-themes/vscode/light_plus.json
Normal file
206
packages/monaco/data/monaco-themes/vscode/light_plus.json
Normal file
@@ -0,0 +1,206 @@
|
||||
{
|
||||
"$schema": "vscode://schemas/color-theme",
|
||||
"name": "Light+",
|
||||
"include": "./light_vs.json",
|
||||
"tokenColors": [
|
||||
{
|
||||
"name": "Function declarations",
|
||||
"scope": [
|
||||
"entity.name.function",
|
||||
"support.function",
|
||||
"support.constant.handlebars",
|
||||
"source.powershell variable.other.member",
|
||||
"entity.name.operator.custom-literal"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#795E26"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Types declaration and references",
|
||||
"scope": [
|
||||
"support.class",
|
||||
"support.type",
|
||||
"entity.name.type",
|
||||
"entity.name.namespace",
|
||||
"entity.other.attribute",
|
||||
"entity.name.scope-resolution",
|
||||
"entity.name.class",
|
||||
"storage.type.numeric.go",
|
||||
"storage.type.byte.go",
|
||||
"storage.type.boolean.go",
|
||||
"storage.type.string.go",
|
||||
"storage.type.uintptr.go",
|
||||
"storage.type.error.go",
|
||||
"storage.type.rune.go",
|
||||
"storage.type.cs",
|
||||
"storage.type.generic.cs",
|
||||
"storage.type.modifier.cs",
|
||||
"storage.type.variable.cs",
|
||||
"storage.type.annotation.java",
|
||||
"storage.type.generic.java",
|
||||
"storage.type.java",
|
||||
"storage.type.object.array.java",
|
||||
"storage.type.primitive.array.java",
|
||||
"storage.type.primitive.java",
|
||||
"storage.type.token.java",
|
||||
"storage.type.groovy",
|
||||
"storage.type.annotation.groovy",
|
||||
"storage.type.parameters.groovy",
|
||||
"storage.type.generic.groovy",
|
||||
"storage.type.object.array.groovy",
|
||||
"storage.type.primitive.array.groovy",
|
||||
"storage.type.primitive.groovy"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#267f99"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Types declaration and references, TS grammar specific",
|
||||
"scope": [
|
||||
"meta.type.cast.expr",
|
||||
"meta.type.new.expr",
|
||||
"support.constant.math",
|
||||
"support.constant.dom",
|
||||
"support.constant.json",
|
||||
"entity.other.inherited-class",
|
||||
"punctuation.separator.namespace.ruby"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#267f99"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Control flow / Special keywords",
|
||||
"scope": [
|
||||
"keyword.control",
|
||||
"source.cpp keyword.operator.new",
|
||||
"source.cpp keyword.operator.delete",
|
||||
"keyword.other.using",
|
||||
"keyword.other.directive.using",
|
||||
"keyword.other.operator",
|
||||
"entity.name.operator"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#AF00DB"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Variable and parameter name",
|
||||
"scope": [
|
||||
"variable",
|
||||
"meta.definition.variable.name",
|
||||
"support.variable",
|
||||
"entity.name.variable",
|
||||
"constant.other.placeholder"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#001080"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Constants and enums",
|
||||
"scope": [
|
||||
"variable.other.constant",
|
||||
"variable.other.enummember"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0070C1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Object keys, TS grammar specific",
|
||||
"scope": [
|
||||
"meta.object-literal.key"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#001080"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CSS property value",
|
||||
"scope": [
|
||||
"support.constant.property-value",
|
||||
"support.constant.font-name",
|
||||
"support.constant.media-type",
|
||||
"support.constant.media",
|
||||
"constant.other.color.rgb-value",
|
||||
"constant.other.rgb-value",
|
||||
"support.constant.color"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0451a5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Regular expression groups",
|
||||
"scope": [
|
||||
"punctuation.definition.group.regexp",
|
||||
"punctuation.definition.group.assertion.regexp",
|
||||
"punctuation.definition.character-class.regexp",
|
||||
"punctuation.character.set.begin.regexp",
|
||||
"punctuation.character.set.end.regexp",
|
||||
"keyword.operator.negation.regexp",
|
||||
"support.other.parenthesis.regexp"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#d16969"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant.character.character-class.regexp",
|
||||
"constant.other.character-class.set.regexp",
|
||||
"constant.other.character-class.regexp",
|
||||
"constant.character.set.regexp"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#811f3f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.operator.quantifier.regexp",
|
||||
"settings": {
|
||||
"foreground": "#000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword.operator.or.regexp",
|
||||
"keyword.control.anchor.regexp"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#EE0000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant.character",
|
||||
"constant.other.option"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0000ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.character.escape",
|
||||
"settings": {
|
||||
"foreground": "#EE0000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.name.label",
|
||||
"settings": {
|
||||
"foreground": "#000000"
|
||||
}
|
||||
}
|
||||
],
|
||||
"semanticHighlighting": true,
|
||||
"semanticTokenColors": {
|
||||
"newOperator": "#AF00DB",
|
||||
"stringLiteral": "#a31515",
|
||||
"customLiteral": "#795E26",
|
||||
"numberLiteral": "#098658"
|
||||
}
|
||||
}
|
||||
10
packages/monaco/data/monaco-themes/vscode/light_theia.json
Normal file
10
packages/monaco/data/monaco-themes/vscode/light_theia.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "vscode://schemas/color-theme",
|
||||
"name": "Light (Theia)",
|
||||
"include": "./light_plus.json",
|
||||
"colors": {
|
||||
"activityBar.background": "#ececec",
|
||||
"activityBar.activeBorder": "#000000",
|
||||
"activityBar.foreground": "#000000"
|
||||
}
|
||||
}
|
||||
437
packages/monaco/data/monaco-themes/vscode/light_vs.json
Normal file
437
packages/monaco/data/monaco-themes/vscode/light_vs.json
Normal file
@@ -0,0 +1,437 @@
|
||||
{
|
||||
"$schema": "vscode://schemas/color-theme",
|
||||
"name": "Light (Visual Studio)",
|
||||
"colors": {
|
||||
"checkbox.border": "#919191",
|
||||
"editor.background": "#FFFFFF",
|
||||
"editor.foreground": "#000000",
|
||||
"editor.inactiveSelectionBackground": "#E5EBF1",
|
||||
"editorIndentGuide.background1": "#D3D3D3",
|
||||
"editorIndentGuide.activeBackground1": "#939393",
|
||||
"editor.selectionHighlightBackground": "#ADD6FF80",
|
||||
"editorSuggestWidget.background": "#F3F3F3",
|
||||
"activityBarBadge.background": "#007ACC",
|
||||
"sideBarTitle.foreground": "#6F6F6F",
|
||||
"list.hoverBackground": "#E8E8E8",
|
||||
"menu.border": "#D4D4D4",
|
||||
"input.placeholderForeground": "#767676",
|
||||
"searchEditor.textInputBorder": "#CECECE",
|
||||
"settings.textInputBorder": "#CECECE",
|
||||
"settings.numberInputBorder": "#CECECE",
|
||||
"statusBarItem.remoteForeground": "#FFF",
|
||||
"statusBarItem.remoteBackground": "#16825D",
|
||||
"ports.iconRunningProcessForeground": "#369432",
|
||||
"sideBarSectionHeader.background": "#0000",
|
||||
"sideBarSectionHeader.border": "#61616130",
|
||||
"tab.selectedForeground": "#333333b3",
|
||||
"tab.selectedBackground": "#ffffffa5",
|
||||
"tab.lastPinnedBorder": "#61616130",
|
||||
"notebook.cellBorderColor": "#E8E8E8",
|
||||
"notebook.selectedCellBackground": "#c8ddf150",
|
||||
"statusBarItem.errorBackground": "#c72e0f",
|
||||
"list.activeSelectionIconForeground": "#FFF",
|
||||
"list.focusAndSelectionOutline": "#90C2F9",
|
||||
"terminal.inactiveSelectionBackground": "#E5EBF1",
|
||||
"widget.border": "#d4d4d4",
|
||||
"actionBar.toggledBackground": "#dddddd",
|
||||
"diffEditor.unchangedRegionBackground": "#f8f8f8"
|
||||
},
|
||||
"tokenColors": [
|
||||
{
|
||||
"scope": [
|
||||
"meta.embedded",
|
||||
"source.groovy.embedded",
|
||||
"string meta.image.inline.markdown",
|
||||
"variable.legacy.builtin.python"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000000ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "emphasis",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "strong",
|
||||
"settings": {
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.diff.header",
|
||||
"settings": {
|
||||
"foreground": "#000080"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "comment",
|
||||
"settings": {
|
||||
"foreground": "#008000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.language",
|
||||
"settings": {
|
||||
"foreground": "#0000ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant.numeric",
|
||||
"variable.other.enummember",
|
||||
"keyword.operator.plus.exponent",
|
||||
"keyword.operator.minus.exponent"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#098658"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.regexp",
|
||||
"settings": {
|
||||
"foreground": "#811f3f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "css tags in selectors, xml tags",
|
||||
"scope": "entity.name.tag",
|
||||
"settings": {
|
||||
"foreground": "#800000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.name.selector",
|
||||
"settings": {
|
||||
"foreground": "#800000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.other.attribute-name",
|
||||
"settings": {
|
||||
"foreground": "#e50000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.other.attribute-name.class.css",
|
||||
"source.css entity.other.attribute-name.class",
|
||||
"entity.other.attribute-name.id.css",
|
||||
"entity.other.attribute-name.parent-selector.css",
|
||||
"entity.other.attribute-name.parent.less",
|
||||
"source.css entity.other.attribute-name.pseudo-class",
|
||||
"entity.other.attribute-name.pseudo-element.css",
|
||||
"source.css.less entity.other.attribute-name.id",
|
||||
"entity.other.attribute-name.scss"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#800000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "invalid",
|
||||
"settings": {
|
||||
"foreground": "#cd3131"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.underline",
|
||||
"settings": {
|
||||
"fontStyle": "underline"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.bold",
|
||||
"settings": {
|
||||
"fontStyle": "bold",
|
||||
"foreground": "#000080"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.heading",
|
||||
"settings": {
|
||||
"fontStyle": "bold",
|
||||
"foreground": "#800000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.italic",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.strikethrough",
|
||||
"settings": {
|
||||
"fontStyle": "strikethrough"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.inserted",
|
||||
"settings": {
|
||||
"foreground": "#098658"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.deleted",
|
||||
"settings": {
|
||||
"foreground": "#a31515"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.changed",
|
||||
"settings": {
|
||||
"foreground": "#0451a5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"punctuation.definition.quote.begin.markdown",
|
||||
"punctuation.definition.list.begin.markdown"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0451a5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.inline.raw",
|
||||
"settings": {
|
||||
"foreground": "#800000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "brackets of XML/HTML tags",
|
||||
"scope": "punctuation.definition.tag",
|
||||
"settings": {
|
||||
"foreground": "#800000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"meta.preprocessor",
|
||||
"entity.name.function.preprocessor"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0000ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.preprocessor.string",
|
||||
"settings": {
|
||||
"foreground": "#a31515"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.preprocessor.numeric",
|
||||
"settings": {
|
||||
"foreground": "#098658"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.structure.dictionary.key.python",
|
||||
"settings": {
|
||||
"foreground": "#0451a5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "storage",
|
||||
"settings": {
|
||||
"foreground": "#0000ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "storage.type",
|
||||
"settings": {
|
||||
"foreground": "#0000ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"storage.modifier",
|
||||
"keyword.operator.noexcept"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0000ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"string",
|
||||
"meta.embedded.assembly"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#a31515"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"string.comment.buffered.block.pug",
|
||||
"string.quoted.pug",
|
||||
"string.interpolated.pug",
|
||||
"string.unquoted.plain.in.yaml",
|
||||
"string.unquoted.plain.out.yaml",
|
||||
"string.unquoted.block.yaml",
|
||||
"string.quoted.single.yaml",
|
||||
"string.quoted.double.xml",
|
||||
"string.quoted.single.xml",
|
||||
"string.unquoted.cdata.xml",
|
||||
"string.quoted.double.html",
|
||||
"string.quoted.single.html",
|
||||
"string.unquoted.html",
|
||||
"string.quoted.single.handlebars",
|
||||
"string.quoted.double.handlebars"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0000ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "string.regexp",
|
||||
"settings": {
|
||||
"foreground": "#811f3f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "String interpolation",
|
||||
"scope": [
|
||||
"punctuation.definition.template-expression.begin",
|
||||
"punctuation.definition.template-expression.end",
|
||||
"punctuation.section.embedded"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0000ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Reset JavaScript string interpolation expression",
|
||||
"scope": [
|
||||
"meta.template.expression"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.constant.property-value",
|
||||
"support.constant.font-name",
|
||||
"support.constant.media-type",
|
||||
"support.constant.media",
|
||||
"constant.other.color.rgb-value",
|
||||
"constant.other.rgb-value",
|
||||
"support.constant.color"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0451a5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.type.vendored.property-name",
|
||||
"support.type.property-name",
|
||||
"source.css variable",
|
||||
"source.coffee.embedded"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#e50000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.type.property-name.json"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0451a5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword",
|
||||
"settings": {
|
||||
"foreground": "#0000ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.control",
|
||||
"settings": {
|
||||
"foreground": "#0000ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.operator",
|
||||
"settings": {
|
||||
"foreground": "#000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword.operator.new",
|
||||
"keyword.operator.expression",
|
||||
"keyword.operator.cast",
|
||||
"keyword.operator.sizeof",
|
||||
"keyword.operator.alignof",
|
||||
"keyword.operator.typeid",
|
||||
"keyword.operator.alignas",
|
||||
"keyword.operator.instanceof",
|
||||
"keyword.operator.logical.python",
|
||||
"keyword.operator.wordlike"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0000ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.other.unit",
|
||||
"settings": {
|
||||
"foreground": "#098658"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"punctuation.section.embedded.begin.php",
|
||||
"punctuation.section.embedded.end.php"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#800000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "support.function.git-rebase",
|
||||
"settings": {
|
||||
"foreground": "#0451a5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.sha.git-rebase",
|
||||
"settings": {
|
||||
"foreground": "#098658"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "coloring of the Java import and package identifiers",
|
||||
"scope": [
|
||||
"storage.modifier.import.java",
|
||||
"variable.language.wildcard.java",
|
||||
"storage.modifier.package.java"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "this.self",
|
||||
"scope": "variable.language",
|
||||
"settings": {
|
||||
"foreground": "#0000ff"
|
||||
}
|
||||
}
|
||||
],
|
||||
"semanticHighlighting": true,
|
||||
"semanticTokenColors": {
|
||||
"newOperator": "#0000ff",
|
||||
"stringLiteral": "#a31515",
|
||||
"customLiteral": "#000000",
|
||||
"numberLiteral": "#098658"
|
||||
}
|
||||
}
|
||||
61
packages/monaco/package.json
Normal file
61
packages/monaco/package.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "@theia/monaco",
|
||||
"version": "1.68.0",
|
||||
"description": "Theia - Monaco Extension",
|
||||
"dependencies": {
|
||||
"@theia/core": "1.68.0",
|
||||
"@theia/editor": "1.68.0",
|
||||
"@theia/filesystem": "1.68.0",
|
||||
"@theia/markers": "1.68.0",
|
||||
"@theia/monaco-editor-core": "1.96.302",
|
||||
"@theia/outline-view": "1.68.0",
|
||||
"@theia/workspace": "1.68.0",
|
||||
"fast-plist": "^0.1.2",
|
||||
"idb": "^4.0.5",
|
||||
"jsonc-parser": "^2.2.0",
|
||||
"tslib": "^2.6.2",
|
||||
"vscode-oniguruma": "2.0.1",
|
||||
"vscode-textmate": "^9.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"theiaExtensions": [
|
||||
{
|
||||
"frontend": "lib/browser/monaco-frontend-module",
|
||||
"secondaryWindow": "lib/browser/monaco-frontend-module"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"theia-extension"
|
||||
],
|
||||
"license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/eclipse-theia/theia.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/eclipse-theia/theia/issues"
|
||||
},
|
||||
"homepage": "https://github.com/eclipse-theia/theia",
|
||||
"files": [
|
||||
"lib",
|
||||
"src",
|
||||
"data"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "theiaext build",
|
||||
"clean": "theiaext clean",
|
||||
"compile": "theiaext compile",
|
||||
"lint": "theiaext lint",
|
||||
"test": "theiaext test",
|
||||
"watch": "theiaext watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@theia/ext-scripts": "1.68.0"
|
||||
},
|
||||
"nyc": {
|
||||
"extends": "../../configs/nyc.json"
|
||||
},
|
||||
"gitHead": "21358137e41342742707f660b8e222f940a27652"
|
||||
}
|
||||
74
packages/monaco/src/browser/content-hover-widget-patcher.ts
Normal file
74
packages/monaco/src/browser/content-hover-widget-patcher.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { IPosition } from '@theia/monaco-editor-core/esm/vs/editor/common/core/position';
|
||||
import { ContentHoverWidget } from '@theia/monaco-editor-core/esm/vs/editor/contrib/hover/browser/contentHoverWidget';
|
||||
|
||||
// https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L13-L14
|
||||
const VSCODE_TOP_HEIGHT = 30;
|
||||
const VSCODE_BOTTOM_HEIGHT = 24;
|
||||
|
||||
export interface SetActualHeightForContentHoverWidgetParams {
|
||||
topHeight?: number;
|
||||
bottomHeight?: number;
|
||||
}
|
||||
|
||||
export interface ContentHoverWidgetPatcher {
|
||||
setActualHeightForContentHoverWidget(params: SetActualHeightForContentHoverWidgetParams): void;
|
||||
}
|
||||
|
||||
export function createContentHoverWidgetPatcher(): ContentHoverWidgetPatcher {
|
||||
let actualTopDiff: number | undefined;
|
||||
let actualBottomDiff: number | undefined;
|
||||
|
||||
const originalAvailableVerticalSpaceAbove = ContentHoverWidget.prototype['_availableVerticalSpaceAbove'];
|
||||
ContentHoverWidget.prototype['_availableVerticalSpaceAbove'] = function (position: IPosition): number | undefined {
|
||||
const originalValue = originalAvailableVerticalSpaceAbove.call(this, position);
|
||||
if (typeof originalValue !== 'number' || !actualTopDiff) {
|
||||
return originalValue;
|
||||
}
|
||||
// The original implementation deducts the height of the top panel from the total available space.
|
||||
// https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L71
|
||||
// However, in Theia, the top panel has generally different size (especially when the toolbar is visible).
|
||||
// This additional height must be further subtracted from the computed height for accurate positioning.
|
||||
return originalValue - actualTopDiff;
|
||||
};
|
||||
|
||||
const originalAvailableVerticalSpaceBelow = ContentHoverWidget.prototype['_availableVerticalSpaceBelow'];
|
||||
ContentHoverWidget.prototype['_availableVerticalSpaceBelow'] = function (position: IPosition): number | undefined {
|
||||
const originalValue = originalAvailableVerticalSpaceBelow.call(this, position);
|
||||
if (typeof originalValue !== 'number' || !actualBottomDiff) {
|
||||
return originalValue;
|
||||
}
|
||||
// The original method subtracts the height of the bottom panel from the overall available height.
|
||||
// https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L83
|
||||
// In Theia, the status bar has different height than in VS Code, which means this difference
|
||||
// should be also removed to ensure the calculated available space is accurate.
|
||||
// Note that removing negative value will increase the available space.
|
||||
return originalValue - actualBottomDiff;
|
||||
};
|
||||
|
||||
return {
|
||||
setActualHeightForContentHoverWidget(params): void {
|
||||
if (typeof params.topHeight === 'number') {
|
||||
actualTopDiff = params.topHeight - VSCODE_TOP_HEIGHT;
|
||||
}
|
||||
if (typeof params.bottomHeight === 'number') {
|
||||
actualBottomDiff = params.bottomHeight - VSCODE_BOTTOM_HEIGHT;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { ApplicationShell, FrontendApplication, FrontendApplicationContribution } from '@theia/core/lib/browser';
|
||||
import { SetActualHeightForContentHoverWidgetParams } from './content-hover-widget-patcher';
|
||||
import { contentHoverWidgetPatcher } from './monaco-init';
|
||||
|
||||
@injectable()
|
||||
export class DefaultContentHoverWidgetPatcher implements FrontendApplicationContribution {
|
||||
onStart(app: FrontendApplication): void {
|
||||
const shell = app.shell;
|
||||
|
||||
this.updateContentHoverWidgetHeight({
|
||||
topHeight: this.getTopPanelHeight(shell),
|
||||
bottomHeight: this.getStatusBarHeight(shell)
|
||||
});
|
||||
|
||||
shell['statusBar'].onDidChangeVisibility(() => {
|
||||
this.updateContentHoverWidgetHeight({
|
||||
bottomHeight: this.getStatusBarHeight(shell)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected updateContentHoverWidgetHeight(params: SetActualHeightForContentHoverWidgetParams): void {
|
||||
contentHoverWidgetPatcher.setActualHeightForContentHoverWidget(params);
|
||||
}
|
||||
|
||||
protected getTopPanelHeight(shell: ApplicationShell): number {
|
||||
return shell.topPanel.node.getBoundingClientRect().height;
|
||||
}
|
||||
|
||||
protected getStatusBarHeight(shell: ApplicationShell): number {
|
||||
return shell['statusBar'].node.getBoundingClientRect().height;
|
||||
}
|
||||
}
|
||||
18
packages/monaco/src/browser/index.ts
Normal file
18
packages/monaco/src/browser/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
export * from './monaco-frontend-module';
|
||||
export * from './monaco-code-action-service';
|
||||
@@ -0,0 +1,110 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2022 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
||||
import { ILanguageService } from '@theia/monaco-editor-core/esm/vs/editor/common/languages/language';
|
||||
import { MarkdownRenderer as CodeMarkdownRenderer, IMarkdownRendererOptions }
|
||||
from '@theia/monaco-editor-core/esm/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { OpenerService, WidgetOpenerOptions, open } from '@theia/core/lib/browser';
|
||||
import { IOpenerService, OpenExternalOptions, OpenInternalOptions } from '@theia/monaco-editor-core/esm/vs/platform/opener/common/opener';
|
||||
import { HttpOpenHandlerOptions } from '@theia/core/lib/browser/http-open-handler';
|
||||
import { URI } from '@theia/core/lib/common/uri';
|
||||
import { MarkdownRenderer, MarkdownRenderOptions, MarkdownRenderResult } from '@theia/core/lib/browser/markdown-rendering/markdown-renderer';
|
||||
import { MarkedOptions, MarkdownRenderOptions as MonacoMarkdownRenderOptions } from '@theia/monaco-editor-core/esm/vs/base/browser/markdownRenderer';
|
||||
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
|
||||
import { DisposableStore } from '@theia/monaco-editor-core/esm/vs/base/common/lifecycle';
|
||||
import { DisposableCollection, DisposableGroup, PreferenceService } from '@theia/core';
|
||||
|
||||
@injectable()
|
||||
export class MonacoMarkdownRenderer implements MarkdownRenderer {
|
||||
@inject(OpenerService) protected readonly openerService: OpenerService;
|
||||
@inject(PreferenceService) protected readonly preferences: PreferenceService;
|
||||
|
||||
protected delegate: CodeMarkdownRenderer;
|
||||
protected _openerService: OpenerService | undefined;
|
||||
|
||||
render(markdown: MarkdownString | undefined, options?: MarkdownRenderOptions, markedOptions?: MarkedOptions): MarkdownRenderResult {
|
||||
return this.delegate.render(markdown, this.transformOptions(options), markedOptions);
|
||||
}
|
||||
|
||||
protected transformOptions(options?: MarkdownRenderOptions): MonacoMarkdownRenderOptions | undefined {
|
||||
if (!options?.actionHandler) {
|
||||
return options as MarkdownRenderOptions & { actionHandler: undefined } | undefined;
|
||||
}
|
||||
const monacoActionHandler: MonacoMarkdownRenderOptions['actionHandler'] = {
|
||||
disposables: this.toDisposableStore(options.actionHandler.disposables),
|
||||
callback: (content, e) => options.actionHandler!.callback(content, e?.browserEvent)
|
||||
};
|
||||
return { ...options, actionHandler: monacoActionHandler };
|
||||
}
|
||||
|
||||
protected toDisposableStore(current: DisposableGroup): DisposableStore {
|
||||
if (current instanceof DisposableStore) {
|
||||
return current;
|
||||
} else if (current instanceof DisposableCollection) {
|
||||
const store = new DisposableStore();
|
||||
current['disposables'].forEach(disposable => store.add(disposable));
|
||||
return store;
|
||||
} else {
|
||||
return new DisposableStore();
|
||||
}
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
const languages = StandaloneServices.get(ILanguageService);
|
||||
const openerService = StandaloneServices.get(IOpenerService);
|
||||
openerService.registerOpener({
|
||||
open: (u, options) => this.interceptOpen(u, options)
|
||||
});
|
||||
const that = this;
|
||||
const prefs = new class implements IMarkdownRendererOptions {
|
||||
get codeBlockFontFamily(): string | undefined {
|
||||
return that.preferences.get<string>('editor.fontFamily');
|
||||
}
|
||||
};
|
||||
|
||||
this.delegate = new CodeMarkdownRenderer(prefs, languages, openerService);
|
||||
}
|
||||
|
||||
protected async interceptOpen(monacoUri: monaco.Uri | string, monacoOptions?: OpenInternalOptions | OpenExternalOptions): Promise<boolean> {
|
||||
let options = undefined;
|
||||
if (monacoOptions) {
|
||||
if ('openToSide' in monacoOptions && monacoOptions.openToSide) {
|
||||
options = Object.assign(options || {}, <WidgetOpenerOptions>{
|
||||
widgetOptions: {
|
||||
mode: 'split-right'
|
||||
}
|
||||
});
|
||||
}
|
||||
if ('openExternal' in monacoOptions && monacoOptions.openExternal) {
|
||||
options = Object.assign(options || {}, <HttpOpenHandlerOptions>{
|
||||
openExternal: true
|
||||
});
|
||||
}
|
||||
}
|
||||
const uri = new URI(monacoUri.toString());
|
||||
try {
|
||||
await open(this.openerService, uri, options);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(`Fail to open '${uri.toString()}':`, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
64
packages/monaco/src/browser/monaco-bulk-edit-service.ts
Normal file
64
packages/monaco/src/browser/monaco-bulk-edit-service.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import { MonacoWorkspace } from './monaco-workspace';
|
||||
import {
|
||||
IBulkEditOptions, IBulkEditPreviewHandler, IBulkEditResult, IBulkEditService, ResourceEdit
|
||||
} from '@theia/monaco-editor-core/esm/vs/editor/browser/services/bulkEditService';
|
||||
import { IDisposable } from '@theia/monaco-editor-core/esm/vs/base/common/lifecycle';
|
||||
import { WorkspaceEdit } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
|
||||
|
||||
@injectable()
|
||||
export class MonacoBulkEditService implements IBulkEditService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
@inject(MonacoWorkspace)
|
||||
protected readonly workspace: MonacoWorkspace;
|
||||
|
||||
private _previewHandler?: IBulkEditPreviewHandler;
|
||||
|
||||
async apply(editsIn: ResourceEdit[] | WorkspaceEdit, options?: IBulkEditOptions): Promise<IBulkEditResult> {
|
||||
const edits = Array.isArray(editsIn) ? editsIn : ResourceEdit.convert(editsIn);
|
||||
|
||||
if (this._previewHandler && (options?.showPreview || edits.some(value => value.metadata?.needsConfirmation))) {
|
||||
editsIn = await this._previewHandler(edits, options);
|
||||
return { ariaSummary: '', isApplied: true };
|
||||
} else {
|
||||
return this.workspace.applyBulkEdit(edits, options);
|
||||
}
|
||||
}
|
||||
|
||||
hasPreviewHandler(): boolean {
|
||||
return Boolean(this._previewHandler);
|
||||
}
|
||||
|
||||
setPreviewHandler(handler: IBulkEditPreviewHandler): IDisposable {
|
||||
this._previewHandler = handler;
|
||||
|
||||
const disposePreviewHandler = () => {
|
||||
if (this._previewHandler === handler) {
|
||||
this._previewHandler = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
dispose(): void {
|
||||
disposePreviewHandler();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 STMicroelectronics and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { CancellationToken } from '@theia/core';
|
||||
import { SaveOptions, SaveReason } from '@theia/core/lib/browser';
|
||||
import { MonacoEditor } from './monaco-editor';
|
||||
import { SaveParticipant, SAVE_PARTICIPANT_DEFAULT_ORDER } from './monaco-editor-provider';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { MonacoCodeActionService } from './monaco-code-action-service';
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// Partially copied from https://github.com/microsoft/vscode/blob/f66e839a38dfe39ee66a86619a790f9c2336d698/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts#L272
|
||||
@injectable()
|
||||
export class MonacoCodeActionSaveParticipant implements SaveParticipant {
|
||||
@inject(MonacoCodeActionService)
|
||||
protected readonly codeActionService: MonacoCodeActionService;
|
||||
|
||||
readonly order = SAVE_PARTICIPANT_DEFAULT_ORDER;
|
||||
|
||||
async applyChangesOnSave(editor: MonacoEditor, cancellationToken: CancellationToken, options?: SaveOptions): Promise<void> {
|
||||
if (options?.saveReason !== SaveReason.Manual) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
await this.codeActionService.applyOnSaveCodeActions(
|
||||
editor.document.textEditorModel,
|
||||
editor.document.textEditorModel.getLanguageId(),
|
||||
editor.document.textEditorModel.uri.toString(),
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
187
packages/monaco/src/browser/monaco-code-action-service.ts
Normal file
187
packages/monaco/src/browser/monaco-code-action-service.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 STMicroelectronics and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { CancellationToken } from '@theia/core';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { ILanguageFeaturesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageFeatures';
|
||||
import { CodeActionKind, CodeActionSet, CodeActionTriggerSource } from '@theia/monaco-editor-core/esm/vs/editor/contrib/codeAction/common/types';
|
||||
import { applyCodeAction, ApplyCodeActionReason, getCodeActions } from '@theia/monaco-editor-core/esm/vs/editor/contrib/codeAction/browser/codeAction';
|
||||
import { HierarchicalKind } from '@theia/monaco-editor-core/esm/vs/base/common/hierarchicalKind';
|
||||
import { EditorPreferences } from '@theia/editor/lib/common/editor-preferences';
|
||||
import { ITextModel } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
|
||||
import { CodeActionProvider, CodeActionTriggerType } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
|
||||
import { IProgress } from '@theia/monaco-editor-core/esm/vs/platform/progress/common/progress';
|
||||
import { IInstantiationService } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const MonacoCodeActionService = Symbol('MonacoCodeActionService');
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// Partially copied from https://github.com/microsoft/vscode/blob/f66e839a38dfe39ee66a86619a790f9c2336d698/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts#L272
|
||||
|
||||
export interface MonacoCodeActionService {
|
||||
/**
|
||||
* Gets all code actions that should be applied on save for the given model and language identifier.
|
||||
* @param model The text model to get code actions for
|
||||
* @param languageId The language identifier for preference lookup
|
||||
* @param uri The URI string for preference scoping
|
||||
* @param token Cancellation token
|
||||
* @returns Array of code action sets to apply, or undefined if no actions should be applied
|
||||
*/
|
||||
getAllCodeActionsOnSave(model: ITextModel, languageId: string, uri: string, token: CancellationToken): Promise<CodeActionSet[] | undefined>;
|
||||
|
||||
/**
|
||||
* Applies the provided code actions for the given model.
|
||||
* @param model The text model to apply code actions to
|
||||
* @param codeActionSets Array of code action sets to apply
|
||||
* @param token Cancellation token
|
||||
*/
|
||||
applyCodeActions(model: ITextModel, codeActionSets: CodeActionSet[], token: CancellationToken): Promise<void>;
|
||||
|
||||
/**
|
||||
* Applies all code actions that should be run on save for the given model and language identifier.
|
||||
* This is a convenience method that retrieves all on-save code actions and applies them.
|
||||
* @param model The text model to apply code actions to
|
||||
* @param languageId The language identifier for preference lookup
|
||||
* @param uri The URI string for preference scoping
|
||||
* @param token Cancellation token
|
||||
*/
|
||||
applyOnSaveCodeActions(model: ITextModel, languageId: string, uri: string, token: CancellationToken): Promise<void>;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MonacoCodeActionServiceImpl implements MonacoCodeActionService {
|
||||
@inject(EditorPreferences)
|
||||
protected readonly editorPreferences: EditorPreferences;
|
||||
|
||||
async applyOnSaveCodeActions(model: ITextModel, languageId: string, uri: string, token: CancellationToken): Promise<void> {
|
||||
const codeActionSets = await this.getAllCodeActionsOnSave(model, languageId, uri, token);
|
||||
|
||||
if (!codeActionSets || token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.applyCodeActions(model, codeActionSets, token);
|
||||
}
|
||||
|
||||
async getAllCodeActionsOnSave(model: ITextModel, languageId: string, uri: string, token: CancellationToken): Promise<CodeActionSet[] | undefined> {
|
||||
const setting = this.editorPreferences.get({
|
||||
preferenceName: 'editor.codeActionsOnSave',
|
||||
overrideIdentifier: languageId
|
||||
}, undefined, uri);
|
||||
|
||||
if (!setting) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const settingItems: string[] = Array.isArray(setting)
|
||||
? setting
|
||||
: Object.keys(setting).filter(x => setting[x]);
|
||||
|
||||
const codeActionsOnSave = this.createCodeActionsOnSave(settingItems);
|
||||
|
||||
if (!codeActionsOnSave.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!Array.isArray(setting)) {
|
||||
codeActionsOnSave.sort((a, b) => {
|
||||
if (CodeActionKind.SourceFixAll.contains(a)) {
|
||||
if (CodeActionKind.SourceFixAll.contains(b)) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (CodeActionKind.SourceFixAll.contains(b)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
const excludedActions = Array.isArray(setting)
|
||||
? []
|
||||
: Object.keys(setting)
|
||||
.filter(x => setting[x] === false)
|
||||
.map(x => new HierarchicalKind(x));
|
||||
|
||||
const codeActionSets: CodeActionSet[] = [];
|
||||
|
||||
for (const codeActionKind of codeActionsOnSave) {
|
||||
const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludedActions, token);
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
actionsToRun.dispose();
|
||||
break;
|
||||
}
|
||||
|
||||
codeActionSets.push(actionsToRun);
|
||||
}
|
||||
|
||||
return codeActionSets;
|
||||
}
|
||||
|
||||
async applyCodeActions(model: ITextModel, codeActionSets: CodeActionSet[], token: CancellationToken): Promise<void> {
|
||||
|
||||
const instantiationService = StandaloneServices.get(IInstantiationService);
|
||||
|
||||
for (const codeActionSet of codeActionSets) {
|
||||
if (token.isCancellationRequested) {
|
||||
codeActionSet.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
for (const action of codeActionSet.validActions) {
|
||||
await instantiationService.invokeFunction(applyCodeAction, action, ApplyCodeActionReason.OnSave, {}, token);
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Failure to apply a code action should not block other on save actions
|
||||
} finally {
|
||||
codeActionSet.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createCodeActionsOnSave(settingItems: readonly string[]): HierarchicalKind[] {
|
||||
const kinds = settingItems.map(x => new HierarchicalKind(x));
|
||||
|
||||
// Remove subsets
|
||||
return kinds.filter(kind => kinds.every(otherKind => otherKind.equals(kind) || !otherKind.contains(kind)));
|
||||
}
|
||||
|
||||
private getActionsToRun(model: ITextModel, codeActionKind: HierarchicalKind, excludes: readonly HierarchicalKind[], token: CancellationToken): Promise<CodeActionSet> {
|
||||
const { codeActionProvider } = StandaloneServices.get(ILanguageFeaturesService);
|
||||
|
||||
const progress: IProgress<CodeActionProvider> = {
|
||||
report(item): void {
|
||||
// empty
|
||||
},
|
||||
};
|
||||
|
||||
return getCodeActions(codeActionProvider, model, model.getFullModelRange(), {
|
||||
type: CodeActionTriggerType.Auto,
|
||||
triggerAction: CodeActionTriggerSource.OnSave,
|
||||
filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true },
|
||||
}, progress, token);
|
||||
}
|
||||
}
|
||||
73
packages/monaco/src/browser/monaco-color-registry.ts
Normal file
73
packages/monaco/src/browser/monaco-color-registry.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
||||
import { Color, ColorDefaults as TheiaColorDefaults, ColorDefinition } from '@theia/core/lib/common/color';
|
||||
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||
import { ColorDefaults, ColorValue, getColorRegistry } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/colorRegistry';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { IStandaloneThemeService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/common/standaloneTheme';
|
||||
import { Color as MonacoColor, HSLA, RGBA } from '@theia/monaco-editor-core/esm/vs/base/common/color';
|
||||
import * as Colors from '@theia/monaco-editor-core/esm/vs/platform/theme/common/colorRegistry';
|
||||
|
||||
@injectable()
|
||||
export class MonacoColorRegistry extends ColorRegistry {
|
||||
|
||||
protected readonly monacoThemeService = StandaloneServices.get(IStandaloneThemeService);
|
||||
protected readonly monacoColorRegistry = getColorRegistry();
|
||||
|
||||
override *getColors(): IterableIterator<string> {
|
||||
for (const { id } of this.monacoColorRegistry.getColors()) {
|
||||
yield id;
|
||||
}
|
||||
}
|
||||
|
||||
override getCurrentColor(id: string): string | undefined {
|
||||
return this.monacoThemeService.getColorTheme().getColor(id)?.toString();
|
||||
}
|
||||
|
||||
getColor(id: string): MonacoColor | undefined {
|
||||
return this.monacoThemeService.getColorTheme().getColor(id);
|
||||
}
|
||||
|
||||
protected override doRegister(definition: ColorDefinition): Disposable {
|
||||
const defaults: ColorDefaults = {
|
||||
dark: this.toColor(TheiaColorDefaults.getDark(definition.defaults)),
|
||||
light: this.toColor(TheiaColorDefaults.getLight(definition.defaults)),
|
||||
hcDark: this.toColor(TheiaColorDefaults.getHCDark(definition.defaults)),
|
||||
hcLight: this.toColor(TheiaColorDefaults.getHCLight(definition.defaults)),
|
||||
};
|
||||
const identifier = this.monacoColorRegistry.registerColor(definition.id, defaults, definition.description);
|
||||
return Disposable.create(() => this.monacoColorRegistry.deregisterColor(identifier));
|
||||
}
|
||||
|
||||
protected toColor(value: Color | undefined): ColorValue | null {
|
||||
if (!value || typeof value === 'string') {
|
||||
return value ?? null; // eslint-disable-line no-null/no-null
|
||||
}
|
||||
if ('kind' in value) {
|
||||
return Colors[value.kind](value.v, value.f);
|
||||
} else if ('r' in value) {
|
||||
const { r, g, b, a } = value;
|
||||
return new MonacoColor(new RGBA(r, g, b, a));
|
||||
} else {
|
||||
const { h, s, l, a } = value;
|
||||
return new MonacoColor(new HSLA(h, s, l, a));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
85
packages/monaco/src/browser/monaco-command-registry.ts
Normal file
85
packages/monaco/src/browser/monaco-command-registry.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import { Command, CommandHandler, CommandRegistry, SelectionService } from '@theia/core';
|
||||
import { TextEditorSelection } from '@theia/editor/lib/browser';
|
||||
import { MonacoEditor } from './monaco-editor';
|
||||
import { MonacoEditorProvider } from './monaco-editor-provider';
|
||||
|
||||
export interface MonacoEditorCommandHandler {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
execute(editor: MonacoEditor, ...args: any[]): any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
isEnabled?(editor: MonacoEditor, ...args: any[]): boolean;
|
||||
}
|
||||
@injectable()
|
||||
export class MonacoCommandRegistry {
|
||||
|
||||
@inject(MonacoEditorProvider)
|
||||
protected readonly monacoEditors: MonacoEditorProvider;
|
||||
|
||||
@inject(CommandRegistry) protected readonly commands: CommandRegistry;
|
||||
|
||||
@inject(SelectionService) protected readonly selectionService: SelectionService;
|
||||
|
||||
validate(command: string | undefined): string | undefined {
|
||||
if (!command) {
|
||||
return undefined;
|
||||
}
|
||||
return this.commands.commandIds.indexOf(command) !== -1 ? command : undefined;
|
||||
}
|
||||
|
||||
registerCommand(command: Command, handler: MonacoEditorCommandHandler): void {
|
||||
this.commands.registerCommand({
|
||||
...command,
|
||||
id: command.id
|
||||
}, this.newHandler(handler));
|
||||
}
|
||||
|
||||
registerHandler(command: string, handler: MonacoEditorCommandHandler): void {
|
||||
this.commands.registerHandler(command, this.newHandler(handler));
|
||||
}
|
||||
|
||||
protected newHandler(monacoHandler: MonacoEditorCommandHandler): CommandHandler {
|
||||
return {
|
||||
execute: (...args) => this.execute(monacoHandler, ...args),
|
||||
isEnabled: (...args) => this.isEnabled(monacoHandler, ...args),
|
||||
isVisible: (...args) => this.isVisible(monacoHandler, ...args)
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
protected execute(monacoHandler: MonacoEditorCommandHandler, ...args: any[]): any {
|
||||
const editor = this.monacoEditors.current;
|
||||
if (editor) {
|
||||
return Promise.resolve(monacoHandler.execute(editor, ...args));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
protected isEnabled(monacoHandler: MonacoEditorCommandHandler, ...args: any[]): boolean {
|
||||
const editor = this.monacoEditors.current;
|
||||
return !!editor && (!monacoHandler.isEnabled || monacoHandler.isEnabled(editor, ...args));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
protected isVisible(monacoHandler: MonacoEditorCommandHandler, ...args: any[]): boolean {
|
||||
return TextEditorSelection.is(this.selectionService.selection);
|
||||
}
|
||||
|
||||
}
|
||||
90
packages/monaco/src/browser/monaco-command-service.ts
Normal file
90
packages/monaco/src/browser/monaco-command-service.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { ICommandEvent, ICommandService } from '@theia/monaco-editor-core/esm/vs/platform/commands/common/commands';
|
||||
import { StandaloneCommandService, StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { IInstantiationService } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/instantiation';
|
||||
|
||||
@injectable()
|
||||
export class MonacoCommandService implements ICommandService, Disposable {
|
||||
declare readonly _serviceBrand: undefined; // Required for type compliance.
|
||||
protected readonly onWillExecuteCommandEmitter = new Emitter<ICommandEvent>();
|
||||
protected readonly onDidExecuteCommandEmitter = new Emitter<ICommandEvent>();
|
||||
protected readonly toDispose = new DisposableCollection(
|
||||
this.onWillExecuteCommandEmitter,
|
||||
this.onDidExecuteCommandEmitter
|
||||
);
|
||||
|
||||
protected delegate: StandaloneCommandService | undefined;
|
||||
|
||||
constructor(
|
||||
@inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry
|
||||
) {
|
||||
this.toDispose.push(this.commandRegistry.onWillExecuteCommand(e => this.onWillExecuteCommandEmitter.fire(e)));
|
||||
this.toDispose.push(this.commandRegistry.onDidExecuteCommand(e => this.onDidExecuteCommandEmitter.fire(e)));
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
init(): void {
|
||||
this.delegate = new StandaloneCommandService(StandaloneServices.get(IInstantiationService));
|
||||
if (this.delegate) {
|
||||
this.toDispose.push(this.delegate.onWillExecuteCommand(event =>
|
||||
this.onWillExecuteCommandEmitter.fire(event)
|
||||
));
|
||||
this.toDispose.push(this.delegate.onDidExecuteCommand(event =>
|
||||
this.onDidExecuteCommandEmitter.fire(event)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
get onWillExecuteCommand(): monaco.IEvent<ICommandEvent> {
|
||||
return this.onWillExecuteCommandEmitter.event;
|
||||
}
|
||||
|
||||
get onDidExecuteCommand(): monaco.IEvent<ICommandEvent> {
|
||||
return this.onDidExecuteCommandEmitter.event;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async executeCommand(commandId: any, ...args: any[]): Promise<any> {
|
||||
try {
|
||||
await this.commandRegistry.executeCommand(commandId, ...args);
|
||||
} catch (e) {
|
||||
if (e.code === 'NO_ACTIVE_HANDLER') {
|
||||
return this.executeMonacoCommand(commandId, ...args);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async executeMonacoCommand(commandId: any, ...args: any[]): Promise<any> {
|
||||
if (this.delegate) {
|
||||
return this.delegate.executeCommand(commandId, ...args);
|
||||
}
|
||||
throw new Error(`command '${commandId}' not found`);
|
||||
}
|
||||
|
||||
}
|
||||
324
packages/monaco/src/browser/monaco-command.ts
Normal file
324
packages/monaco/src/browser/monaco-command.ts
Normal file
@@ -0,0 +1,324 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject, optional } from '@theia/core/shared/inversify';
|
||||
import { Position, Location } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import { URI as CodeURI } from '@theia/core/shared/vscode-uri';
|
||||
import { cloneAndChange, URI } from '@theia/core';
|
||||
import { CommandContribution, CommandRegistry, CommandHandler } from '@theia/core/lib/common/command';
|
||||
import { CommonCommands, QuickInputService, ApplicationShell } from '@theia/core/lib/browser';
|
||||
import { EditorCommands, EditorManager, EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { MonacoEditor } from './monaco-editor';
|
||||
import { MonacoCommandRegistry, MonacoEditorCommandHandler } from './monaco-command-registry';
|
||||
import { ProtocolToMonacoConverter } from './protocol-to-monaco-converter';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { EditorExtensionsRegistry } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorExtensions';
|
||||
import { CommandsRegistry, ICommandService } from '@theia/monaco-editor-core/esm/vs/platform/commands/common/commands';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { EndOfLineSequence } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { IInstantiationService } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/instantiation';
|
||||
import { ICodeEditorService } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/codeEditorService';
|
||||
|
||||
export namespace MonacoCommands {
|
||||
|
||||
export const COMMON_ACTIONS = new Map<string, string>([
|
||||
['editor.action.selectAll', CommonCommands.SELECT_ALL.id],
|
||||
['actions.find', CommonCommands.FIND.id],
|
||||
['editor.action.startFindReplaceAction', CommonCommands.REPLACE.id],
|
||||
['editor.action.clipboardCutAction', CommonCommands.CUT.id],
|
||||
['editor.action.clipboardCopyAction', CommonCommands.COPY.id],
|
||||
['editor.action.clipboardPasteAction', CommonCommands.PASTE.id]
|
||||
]);
|
||||
|
||||
export const GO_TO_DEFINITION = 'editor.action.revealDefinition';
|
||||
|
||||
export const EXCLUDE_ACTIONS = new Set([
|
||||
'editor.action.quickCommand',
|
||||
'editor.action.toggleStickyScroll', // Handled by `editor` package.
|
||||
'undo',
|
||||
'redo',
|
||||
'_setContext'
|
||||
]);
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MonacoEditorCommandHandlers implements CommandContribution {
|
||||
|
||||
@inject(MonacoCommandRegistry)
|
||||
protected readonly monacoCommandRegistry: MonacoCommandRegistry;
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@inject(ProtocolToMonacoConverter)
|
||||
protected readonly p2m: ProtocolToMonacoConverter;
|
||||
|
||||
@inject(QuickInputService) @optional()
|
||||
protected readonly quickInputService: QuickInputService;
|
||||
|
||||
@inject(ApplicationShell)
|
||||
protected readonly shell: ApplicationShell;
|
||||
|
||||
@inject(EditorManager)
|
||||
protected editorManager: EditorManager;
|
||||
|
||||
registerCommands(): void {
|
||||
this.registerMonacoCommands();
|
||||
this.registerEditorCommandHandlers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register commands from Monaco to Theia registry.
|
||||
*
|
||||
* Monaco has different kind of commands which should be handled differently by Theia.
|
||||
*
|
||||
* ### Editor Actions
|
||||
*
|
||||
* They should be registered with a label to be visible in the quick command palette.
|
||||
*
|
||||
* Such actions should be enabled only if the current editor is available and
|
||||
* it supports such action in the current context.
|
||||
*
|
||||
* ### Editor Commands
|
||||
*
|
||||
* Such actions should be enabled only if the current editor is available.
|
||||
*
|
||||
* `actions.find` and `editor.action.startFindReplaceAction` are registered as handlers for `find` and `replace`.
|
||||
* If handlers are not enabled then the core should prevent the default browser behavior.
|
||||
* Other Theia extensions can register alternative implementations using custom enablement.
|
||||
*
|
||||
* ### Global Commands
|
||||
*
|
||||
* These commands are not necessary dependent on the current editor and enabled always.
|
||||
* But they depend on services which are global in VS Code, but bound to the editor in Monaco,
|
||||
* i.e. `ICodeEditorService` or `IContextKeyService`. We should take care of providing Theia implementations for such services.
|
||||
*
|
||||
* #### Global Native or Editor Commands
|
||||
*
|
||||
* Namely: `undo`, `redo` and `editor.action.selectAll`. They depend on `ICodeEditorService`.
|
||||
* They will try to delegate to the current editor and if it is not available delegate to the browser.
|
||||
* They are registered as handlers for corresponding core commands always.
|
||||
* Other Theia extensions can provide alternative implementations by introducing a dependency to `@theia/monaco` extension.
|
||||
*
|
||||
* #### Global Language Commands
|
||||
*
|
||||
* Like `_executeCodeActionProvider`, they depend on `ICodeEditorService` and `ITextModelService`.
|
||||
*
|
||||
* #### Global Context Commands
|
||||
*
|
||||
* It is `setContext`. It depends on `IContextKeyService`.
|
||||
*
|
||||
* #### Global Editor Commands
|
||||
*
|
||||
* Like `openReferenceToSide` and `openReference`, they depend on `IListService`.
|
||||
* We treat all commands which don't match any other category of global commands as global editor commands
|
||||
* and execute them using the instantiation service of the current editor.
|
||||
*/
|
||||
protected registerMonacoCommands(): void {
|
||||
const editorActions = new Map([...EditorExtensionsRegistry.getEditorActions()].map(({ id, label, alias }) => [id, { label, alias }]));
|
||||
|
||||
const codeEditorService = StandaloneServices.get(ICodeEditorService);
|
||||
const globalInstantiationService = StandaloneServices.get(IInstantiationService);
|
||||
const monacoCommands = CommandsRegistry.getCommands();
|
||||
for (const id of monacoCommands.keys()) {
|
||||
if (MonacoCommands.EXCLUDE_ACTIONS.has(id)) {
|
||||
continue;
|
||||
}
|
||||
const handler: CommandHandler = {
|
||||
execute: (...args) => {
|
||||
/*
|
||||
* We check monaco focused code editor first since they can contain inline like the debug console and embedded editors like in the peek reference.
|
||||
* If there is not such then we check last focused editor tracked by us.
|
||||
*/
|
||||
const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor();
|
||||
if (editorActions.has(id)) {
|
||||
const action = editor && editor.getAction(id);
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
return action.run();
|
||||
}
|
||||
if (!globalInstantiationService) {
|
||||
return;
|
||||
}
|
||||
return globalInstantiationService.invokeFunction(
|
||||
monacoCommands.get(id)!.handler,
|
||||
...args
|
||||
);
|
||||
},
|
||||
isEnabled: () => {
|
||||
const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor();
|
||||
|
||||
if (editorActions.has(id)) {
|
||||
const action = editor && editor.getAction(id);
|
||||
return !!action && action.isSupported();
|
||||
}
|
||||
if (!!EditorExtensionsRegistry.getEditorCommand(id) || MonacoCommands.COMMON_ACTIONS.has(id)) {
|
||||
return !!editor;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
const commandAction = editorActions.get(id);
|
||||
this.commandRegistry.registerCommand({ id, label: commandAction?.label, originalLabel: commandAction?.alias }, handler);
|
||||
const coreCommand = MonacoCommands.COMMON_ACTIONS.get(id);
|
||||
if (coreCommand) {
|
||||
this.commandRegistry.registerHandler(coreCommand, handler);
|
||||
}
|
||||
}
|
||||
|
||||
// the _setContext command stringifies all URIs in contextValue and needs to be overriden to handle all URI types in Theia
|
||||
const setContext = monacoCommands.get('_setContext');
|
||||
if (setContext) {
|
||||
this.commandRegistry.registerCommand({ id: setContext.id }, {
|
||||
execute: (contextKey, contextValue, ...args) => globalInstantiationService.invokeFunction(setContext.handler,
|
||||
contextKey,
|
||||
cloneAndChange(contextValue, orig => {
|
||||
if (orig instanceof URI || CodeURI.isUri(orig)) {
|
||||
return orig.toString();
|
||||
}
|
||||
return undefined;
|
||||
}),
|
||||
...args
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected registerEditorCommandHandlers(): void {
|
||||
this.monacoCommandRegistry.registerHandler(EditorCommands.SHOW_REFERENCES.id, this.newShowReferenceHandler());
|
||||
this.monacoCommandRegistry.registerHandler(EditorCommands.CONFIG_INDENTATION.id, this.newConfigIndentationHandler());
|
||||
this.monacoCommandRegistry.registerHandler(EditorCommands.CONFIG_EOL.id, this.newConfigEolHandler());
|
||||
this.monacoCommandRegistry.registerHandler(EditorCommands.INDENT_USING_SPACES.id, this.newConfigTabSizeHandler(true));
|
||||
this.monacoCommandRegistry.registerHandler(EditorCommands.INDENT_USING_TABS.id, this.newConfigTabSizeHandler(false));
|
||||
this.monacoCommandRegistry.registerHandler(EditorCommands.REVERT_EDITOR.id, this.newRevertActiveEditorHandler());
|
||||
this.monacoCommandRegistry.registerHandler(EditorCommands.REVERT_AND_CLOSE.id, this.newRevertAndCloseActiveEditorHandler());
|
||||
}
|
||||
|
||||
protected newShowReferenceHandler(): MonacoEditorCommandHandler {
|
||||
return {
|
||||
execute: (editor: MonacoEditor, uri: string, position: Position, locations: Location[]) => {
|
||||
StandaloneServices.get(ICommandService).executeCommand(
|
||||
'editor.action.showReferences',
|
||||
monaco.Uri.parse(uri),
|
||||
this.p2m.asPosition(position),
|
||||
locations.map(l => this.p2m.asLocation(l))
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected newConfigIndentationHandler(): MonacoEditorCommandHandler {
|
||||
return {
|
||||
execute: editor => this.configureIndentation(editor)
|
||||
};
|
||||
}
|
||||
protected configureIndentation(editor: MonacoEditor): void {
|
||||
const items = [true, false].map(useSpaces => ({
|
||||
label: nls.localizeByDefault(`Indent Using ${useSpaces ? 'Spaces' : 'Tabs'}`),
|
||||
execute: () => this.configureTabSize(editor, useSpaces)
|
||||
}));
|
||||
this.quickInputService?.showQuickPick(items, { placeholder: nls.localizeByDefault('Select Action') });
|
||||
}
|
||||
|
||||
protected newConfigEolHandler(): MonacoEditorCommandHandler {
|
||||
return {
|
||||
execute: editor => this.configureEol(editor)
|
||||
};
|
||||
}
|
||||
|
||||
protected configureEol(editor: MonacoEditor): void {
|
||||
const items = ['LF', 'CRLF'].map(lineEnding =>
|
||||
({
|
||||
label: lineEnding,
|
||||
execute: () => this.setEol(editor, lineEnding)
|
||||
})
|
||||
);
|
||||
this.quickInputService?.showQuickPick(items, { placeholder: nls.localizeByDefault('Select End of Line Sequence') });
|
||||
}
|
||||
|
||||
protected setEol(editor: MonacoEditor, lineEnding: string): void {
|
||||
const model = editor.document && editor.document.textEditorModel;
|
||||
if (model) {
|
||||
if (lineEnding === 'CRLF' || lineEnding === '\r\n') {
|
||||
model.pushEOL(EndOfLineSequence.CRLF);
|
||||
} else {
|
||||
model.pushEOL(EndOfLineSequence.LF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected newConfigTabSizeHandler(useSpaces: boolean): MonacoEditorCommandHandler {
|
||||
return {
|
||||
execute: editor => this.configureTabSize(editor, useSpaces)
|
||||
};
|
||||
}
|
||||
protected configureTabSize(editor: MonacoEditor, useSpaces: boolean): void {
|
||||
const model = editor.document && editor.document.textEditorModel;
|
||||
if (model) {
|
||||
const { tabSize } = model.getOptions();
|
||||
const sizes = Array.from(Array(8), (_, x) => x + 1);
|
||||
const tabSizeOptions = sizes.map(size =>
|
||||
({
|
||||
label: size === tabSize ? size + ' ' + nls.localizeByDefault('Configured Tab Size') : size.toString(),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
execute: () =>
|
||||
model.updateOptions({
|
||||
tabSize: size || tabSize,
|
||||
indentSize: size || tabSize,
|
||||
insertSpaces: useSpaces
|
||||
})
|
||||
})
|
||||
);
|
||||
this.quickInputService?.showQuickPick(tabSizeOptions, { placeholder: nls.localizeByDefault('Select Tab Size for Current File') });
|
||||
}
|
||||
}
|
||||
|
||||
protected newRevertActiveEditorHandler(): MonacoEditorCommandHandler {
|
||||
return {
|
||||
execute: () => this.revertEditor(this.getActiveEditor().editor),
|
||||
};
|
||||
}
|
||||
|
||||
protected newRevertAndCloseActiveEditorHandler(): MonacoEditorCommandHandler {
|
||||
return {
|
||||
execute: async () => this.revertAndCloseActiveEditor(this.getActiveEditor())
|
||||
};
|
||||
}
|
||||
|
||||
protected getActiveEditor(): { widget?: EditorWidget, editor?: MonacoEditor } {
|
||||
const widget = this.editorManager.currentEditor;
|
||||
return { widget, editor: widget && MonacoEditor.getCurrent(this.editorManager) };
|
||||
}
|
||||
|
||||
protected async revertEditor(editor?: MonacoEditor): Promise<void> {
|
||||
if (editor) {
|
||||
return editor.document.revert();
|
||||
}
|
||||
}
|
||||
|
||||
protected async revertAndCloseActiveEditor(current: { widget?: EditorWidget, editor?: MonacoEditor }): Promise<void> {
|
||||
if (current.editor && current.widget) {
|
||||
try {
|
||||
await this.revertEditor(current.editor);
|
||||
current.widget.close();
|
||||
} catch (error) {
|
||||
await this.shell.closeWidget(current.widget.id, { save: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
169
packages/monaco/src/browser/monaco-context-key-service.ts
Normal file
169
packages/monaco/src/browser/monaco-context-key-service.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, postConstruct } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
ContextKeyService as TheiaContextKeyService, ContextKey, ContextKeyChangeEvent,
|
||||
ScopedValueStore, ContextMatcher, ContextKeyValue, Context
|
||||
} from '@theia/core/lib/browser/context-key-service';
|
||||
import { Emitter, Event } from '@theia/core';
|
||||
import { AbstractContextKeyService, Context as MonacoContext } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/browser/contextKeyService';
|
||||
import { ContextKeyExpr, ContextKeyExpression, IContext, IContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
|
||||
@injectable()
|
||||
export class MonacoContextKeyService implements TheiaContextKeyService {
|
||||
protected readonly onDidChangeEmitter = new Emitter<ContextKeyChangeEvent>();
|
||||
get onDidChange(): Event<ContextKeyChangeEvent> {
|
||||
if (this.activeContext && 'onDidChange' in this.activeContext && this.activeContext.onDidChange) {
|
||||
return this.activeContext.onDidChange;
|
||||
}
|
||||
return this.onDidChangeEmitter.event;
|
||||
}
|
||||
|
||||
get contextKeyService(): AbstractContextKeyService {
|
||||
return StandaloneServices.get(IContextKeyService) as AbstractContextKeyService;
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.contextKeyService.onDidChangeContext(e =>
|
||||
this.onDidChangeEmitter.fire({
|
||||
affects: keys => e.affectsSome(keys)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
createKey<T extends ContextKeyValue>(key: string, defaultValue: T | undefined): ContextKey<T> {
|
||||
return this.contextKeyService.createKey(key, defaultValue);
|
||||
}
|
||||
|
||||
activeContext?: HTMLElement | IContext | Context;
|
||||
|
||||
match(expression: string, context?: HTMLElement): boolean {
|
||||
const parsed = this.parse(expression);
|
||||
if (parsed) {
|
||||
const ctx = this.identifyContext(context);
|
||||
if (!ctx) {
|
||||
return this.contextKeyService.contextMatchesRules(parsed);
|
||||
}
|
||||
return parsed.evaluate(ctx);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected identifyContext(callersContext?: HTMLElement | IContext, service: IContextKeyService = this.contextKeyService): IContext | undefined {
|
||||
if (callersContext && 'getValue' in callersContext) {
|
||||
return callersContext;
|
||||
} else if (this.activeContext && 'getValue' in this.activeContext) {
|
||||
return this.activeContext;
|
||||
}
|
||||
const browserContext = callersContext ?? this.activeContext ?? (document.activeElement instanceof HTMLElement ? document.activeElement : undefined);
|
||||
if (browserContext) {
|
||||
return service.getContext(browserContext);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected readonly expressions = new Map<string, ContextKeyExpression>();
|
||||
parse(when: string): ContextKeyExpression | undefined {
|
||||
let expression = this.expressions.get(when);
|
||||
if (!expression) {
|
||||
expression = ContextKeyExpr.deserialize(when);
|
||||
if (expression) {
|
||||
this.expressions.set(when, expression);
|
||||
}
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
parseKeys(expression: string): Set<string> | undefined {
|
||||
const expr = ContextKeyExpr.deserialize(expression);
|
||||
return expr ? new Set<string>(expr.keys()) : expr;
|
||||
}
|
||||
|
||||
with<T>(values: Record<string, unknown>, callback: () => T): T {
|
||||
const id = this.contextKeyService.createChildContext();
|
||||
const child = this.contextKeyService.getContextValuesContainer(id);
|
||||
for (const [key, value] of Object.entries(values)) {
|
||||
child.setValue(key, value);
|
||||
}
|
||||
try {
|
||||
return this.withContext(child, callback);
|
||||
} finally {
|
||||
this.contextKeyService.disposeContext(id);
|
||||
}
|
||||
}
|
||||
|
||||
withContext<T>(context: Context, callback: () => T): T {
|
||||
const oldActive = this.activeContext;
|
||||
this.activeContext = context;
|
||||
try {
|
||||
return callback();
|
||||
} finally {
|
||||
this.activeContext = oldActive;
|
||||
}
|
||||
}
|
||||
|
||||
createScoped(target: HTMLElement): ScopedValueStore {
|
||||
const scoped = this.contextKeyService.createScoped(target);
|
||||
if (scoped instanceof AbstractContextKeyService) {
|
||||
return scoped as unknown as ScopedValueStore;
|
||||
}
|
||||
throw new Error('Could not created scoped value store');
|
||||
}
|
||||
|
||||
createOverlay(overlay: Iterable<[string, unknown]>): ContextMatcher {
|
||||
const delegate = this.contextKeyService.createOverlay(overlay);
|
||||
return {
|
||||
match: (expression: string, context?: HTMLElement) => {
|
||||
const parsed = this.parse(expression);
|
||||
if (parsed) {
|
||||
const ctx = this.identifyContext(context, delegate);
|
||||
if (!ctx) {
|
||||
return delegate.contextMatchesRules(parsed);
|
||||
}
|
||||
return parsed.evaluate(ctx);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
setContext(key: string, value: unknown): void {
|
||||
this.contextKeyService.setContext(key, value);
|
||||
}
|
||||
|
||||
getLocalContextKeys(element: HTMLElement): Set<string> {
|
||||
const context = this.contextKeyService.getContext(element);
|
||||
// Get local values
|
||||
if (context && context instanceof MonacoContext) {
|
||||
const localValues = context.value;
|
||||
// Filter out the internal '_contextId' key
|
||||
const res = new Set(Object.keys(localValues));
|
||||
res.delete('_contextId');
|
||||
return res;
|
||||
}
|
||||
// For other IContext implementations (like NullContext), return empty set
|
||||
return new Set<string>();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.activeContext = undefined;
|
||||
this.onDidChangeEmitter.dispose();
|
||||
this.contextKeyService.dispose();
|
||||
}
|
||||
}
|
||||
132
packages/monaco/src/browser/monaco-context-menu.ts
Normal file
132
packages/monaco/src/browser/monaco-context-menu.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import { MenuPath } from '@theia/core/lib/common/menu';
|
||||
import { EDITOR_CONTEXT_MENU } from '@theia/editor/lib/browser';
|
||||
import { Anchor, ContextMenuAccess, ContextMenuRenderer, Coordinate } from '@theia/core/lib/browser';
|
||||
import { Menu } from '@theia/core/shared/@lumino/widgets';
|
||||
import { CommandRegistry } from '@theia/core/shared/@lumino/commands';
|
||||
import { IContextMenuService } from '@theia/monaco-editor-core/esm/vs/platform/contextview/browser/contextView';
|
||||
import { IContextMenuDelegate } from '@theia/monaco-editor-core/esm/vs/base/browser/contextmenu';
|
||||
import { IAction, Separator, SubmenuAction } from '@theia/monaco-editor-core/esm/vs/base/common/actions';
|
||||
import { MenuItemAction } from '@theia/monaco-editor-core/esm/vs/platform/actions/common/actions';
|
||||
import { Event, Emitter } from '@theia/monaco-editor-core/esm/vs/base/common/event';
|
||||
import { StandardMouseEvent } from '@theia/monaco-editor-core/esm/vs/base/browser/mouseEvent';
|
||||
|
||||
@injectable()
|
||||
export class MonacoContextMenuService implements IContextMenuService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
protected readonly onDidShowContextMenuEmitter = new Emitter<void>();
|
||||
get onDidShowContextMenu(): Event<void> {
|
||||
return this.onDidShowContextMenuEmitter.event;
|
||||
};
|
||||
protected readonly onDidHideContextMenuEmitter = new Emitter<void>();
|
||||
get onDidHideContextMenu(): Event<void> {
|
||||
return this.onDidShowContextMenuEmitter.event;
|
||||
};
|
||||
|
||||
@inject(ContextMenuRenderer) protected readonly contextMenuRenderer: ContextMenuRenderer;
|
||||
|
||||
toAnchor(anchor: HTMLElement | Coordinate | StandardMouseEvent): Anchor {
|
||||
if (anchor instanceof HTMLElement) {
|
||||
return { x: anchor.offsetLeft, y: anchor.offsetTop };
|
||||
} else if (anchor instanceof StandardMouseEvent) {
|
||||
return { x: anchor.posx, y: anchor.posy };
|
||||
} else {
|
||||
return anchor;
|
||||
}
|
||||
}
|
||||
|
||||
private getContext(delegate: IContextMenuDelegate): HTMLElement {
|
||||
const anchor = delegate.getAnchor();
|
||||
if (anchor instanceof HTMLElement) {
|
||||
return anchor;
|
||||
} else if (anchor instanceof StandardMouseEvent) {
|
||||
return anchor.target;
|
||||
} else {
|
||||
return window.document.body; // last resort
|
||||
}
|
||||
}
|
||||
|
||||
showContextMenu(delegate: IContextMenuDelegate): void {
|
||||
const anchor = this.toAnchor(delegate.getAnchor());
|
||||
const actions = delegate.getActions();
|
||||
const context = this.getContext(delegate);
|
||||
const onHide = () => {
|
||||
delegate.onHide?.(false);
|
||||
this.onDidHideContextMenuEmitter.fire();
|
||||
};
|
||||
|
||||
// Actions for editor context menu come as 'MenuItemAction' items
|
||||
// In case of 'Quick Fix' actions come as 'CodeActionAction' items
|
||||
if (actions.length > 0 && actions[0] instanceof MenuItemAction) {
|
||||
this.contextMenuRenderer.render({
|
||||
context: context,
|
||||
menuPath: this.menuPath(),
|
||||
anchor,
|
||||
onHide
|
||||
});
|
||||
} else {
|
||||
const menu = new Menu({ commands: new CommandRegistry() });
|
||||
this.populateMenu(menu, actions);
|
||||
menu.aboutToClose.connect(() => onHide());
|
||||
menu.open(anchor.x, anchor.y);
|
||||
this.contextMenuRenderer.current = new ContextMenuAccess(menu);
|
||||
}
|
||||
this.onDidShowContextMenuEmitter.fire();
|
||||
}
|
||||
|
||||
protected populateMenu(menu: Menu, actions: readonly IAction[]): void {
|
||||
for (const action of actions) {
|
||||
if (action instanceof SubmenuAction) {
|
||||
const submenu = new Menu({ commands: new CommandRegistry() });
|
||||
submenu.title.label = action.label;
|
||||
submenu.title.caption = action.tooltip;
|
||||
if (action.class) {
|
||||
submenu.addClass(action.class);
|
||||
}
|
||||
this.populateMenu(submenu, action.actions);
|
||||
menu.addItem({
|
||||
type: 'submenu',
|
||||
submenu
|
||||
});
|
||||
} else if (action instanceof Separator) {
|
||||
menu.addItem({
|
||||
type: 'separator'
|
||||
});
|
||||
} else {
|
||||
menu.commands.addCommand(action.id, {
|
||||
label: action.label,
|
||||
className: action.class,
|
||||
isToggled: () => Boolean(action.checked),
|
||||
isEnabled: () => action.enabled,
|
||||
execute: () => action.run()
|
||||
});
|
||||
menu.addItem({
|
||||
type: 'command',
|
||||
command: action.id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected menuPath(): MenuPath {
|
||||
return EDITOR_CONTEXT_MENU;
|
||||
}
|
||||
|
||||
}
|
||||
62
packages/monaco/src/browser/monaco-diff-computer.ts
Normal file
62
packages/monaco/src/browser/monaco-diff-computer.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 1C-Soft LLC and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { Diff, DiffComputer } from '@theia/core/lib/common/diff';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Range } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { IEditorWorkerService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/editorWorker';
|
||||
|
||||
@injectable()
|
||||
export class MonacoDiffComputer implements DiffComputer {
|
||||
|
||||
async computeDiff(left: URI, right: URI): Promise<Diff | undefined> {
|
||||
const diff = await StandaloneServices.get(IEditorWorkerService).computeDiff(
|
||||
left['codeUri'],
|
||||
right['codeUri'],
|
||||
{
|
||||
ignoreTrimWhitespace: false,
|
||||
maxComputationTimeMs: 0,
|
||||
computeMoves: false,
|
||||
},
|
||||
'advanced'
|
||||
);
|
||||
|
||||
if (!diff) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const convertLineRange = (r: { startLineNumber: number; endLineNumberExclusive: number }) => ({
|
||||
start: r.startLineNumber - 1,
|
||||
end: r.endLineNumberExclusive - 1
|
||||
});
|
||||
|
||||
const convertRange = (r: { startLineNumber: number; startColumn: number; endLineNumber: number; endColumn: number }) =>
|
||||
Range.create(r.startLineNumber - 1, r.startColumn - 1, r.endLineNumber - 1, r.endColumn - 1);
|
||||
|
||||
const changes = diff.changes.map(c => ({
|
||||
left: convertLineRange(c.original),
|
||||
right: convertLineRange(c.modified),
|
||||
innerChanges: c.innerChanges?.map(ic => ({
|
||||
left: convertRange(ic.originalRange),
|
||||
right: convertRange(ic.modifiedRange)
|
||||
}))
|
||||
}));
|
||||
|
||||
return { changes };
|
||||
}
|
||||
}
|
||||
175
packages/monaco/src/browser/monaco-diff-editor.ts
Normal file
175
packages/monaco/src/browser/monaco-diff-editor.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Disposable } from '@theia/core/lib/common';
|
||||
import { Dimension, DiffNavigator, DeltaDecorationParams } from '@theia/editor/lib/browser';
|
||||
import { MonacoEditorModel } from './monaco-editor-model';
|
||||
import { EditorServiceOverrides, MonacoEditor, MonacoEditorServices } from './monaco-editor';
|
||||
import { MonacoDiffNavigatorFactory } from './monaco-diff-navigator-factory';
|
||||
import { DiffUris } from '@theia/core/lib/browser/diff-uris';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { ICodeEditor, IDiffEditorConstructionOptions } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
|
||||
import { IActionDescriptor, IStandaloneCodeEditor, IStandaloneDiffEditor, StandaloneCodeEditor, StandaloneDiffEditor2 }
|
||||
from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneCodeEditor';
|
||||
import { IEditorConstructionOptions } from '@theia/monaco-editor-core/esm/vs/editor/browser/config/editorConfiguration';
|
||||
import { EmbeddedDiffEditorWidget } from '@theia/monaco-editor-core/esm/vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget';
|
||||
import { IInstantiationService } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/instantiation';
|
||||
import { ContextKeyValue, IContextKey } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';
|
||||
import { IDisposable } from '@theia/monaco-editor-core/esm/vs/base/common/lifecycle';
|
||||
import { ICommandHandler } from '@theia/monaco-editor-core/esm/vs/platform/commands/common/commands';
|
||||
import { EditorContextKeys } from '@theia/monaco-editor-core/esm/vs/editor/common/editorContextKeys';
|
||||
import { IEditorOptions } from '@theia/monaco-editor-core/esm/vs/editor/common/config/editorOptions';
|
||||
import { ILineChange } from '@theia/monaco-editor-core/esm/vs/editor/common/diff/legacyLinesDiffComputer';
|
||||
|
||||
export namespace MonacoDiffEditor {
|
||||
export interface IOptions extends MonacoEditor.ICommonOptions, IDiffEditorConstructionOptions {
|
||||
}
|
||||
}
|
||||
|
||||
export class MonacoDiffEditor extends MonacoEditor {
|
||||
protected _diffEditor: IStandaloneDiffEditor;
|
||||
protected _diffNavigator: DiffNavigator;
|
||||
protected readonly diffEditorModel: monaco.editor.IDiffEditorModel;
|
||||
|
||||
constructor(
|
||||
uri: URI,
|
||||
node: HTMLElement,
|
||||
readonly originalModel: MonacoEditorModel,
|
||||
readonly modifiedModel: MonacoEditorModel,
|
||||
services: MonacoEditorServices,
|
||||
protected readonly diffNavigatorFactory: MonacoDiffNavigatorFactory,
|
||||
options?: MonacoDiffEditor.IOptions,
|
||||
override?: EditorServiceOverrides,
|
||||
parentEditor?: MonacoEditor
|
||||
) {
|
||||
super(uri, modifiedModel, node, services, options, override, parentEditor);
|
||||
this.diffEditorModel = { original: this.originalModel.textEditorModel, modified: this.modifiedModel.textEditorModel };
|
||||
this.documents.add(originalModel);
|
||||
this.wordWrapOverride = options?.wordWrapOverride2;
|
||||
this._diffNavigator = diffNavigatorFactory.createdDiffNavigator(this._diffEditor);
|
||||
if (parentEditor) {
|
||||
// Embedded diff editors don't participate in visibility tracking (they're not wrapped in EditorWidget),
|
||||
// so we need to set the model immediately since handleVisibilityChanged will never be called.
|
||||
this.diffEditor.setModel(this.diffEditorModel);
|
||||
}
|
||||
}
|
||||
|
||||
get diffEditor(): monaco.editor.IStandaloneDiffEditor {
|
||||
return this._diffEditor as unknown as monaco.editor.IStandaloneDiffEditor;
|
||||
}
|
||||
|
||||
get diffNavigator(): DiffNavigator {
|
||||
return this._diffNavigator;
|
||||
}
|
||||
|
||||
get diffInformation(): ILineChange[] {
|
||||
return this._diffEditor.getLineChanges() || [];
|
||||
}
|
||||
|
||||
protected override create(options?: IDiffEditorConstructionOptions, override?: EditorServiceOverrides): Disposable {
|
||||
options = { ...options, fixedOverflowWidgets: true };
|
||||
const instantiator = this.getInstantiatorWithOverrides(override);
|
||||
/**
|
||||
* @monaco-uplift. Should be guaranteed to work.
|
||||
* Incomparable enums prevent TypeScript from believing that public IStandaloneDiffEditor is satisfied by private StandaloneDiffEditor
|
||||
*/
|
||||
this._diffEditor = this.parentEditor ?
|
||||
instantiator.createInstance(EmbeddedDiffEditor, this.node, options, {}, this.parentEditor.getControl() as unknown as ICodeEditor) :
|
||||
instantiator.createInstance(StandaloneDiffEditor2, this.node, options);
|
||||
this.editor = this._diffEditor.getModifiedEditor() as unknown as monaco.editor.IStandaloneCodeEditor;
|
||||
return this._diffEditor;
|
||||
}
|
||||
|
||||
protected wordWrapOverride: IEditorOptions['wordWrapOverride2'];
|
||||
protected lastReachedSideBySideBreakpoint = true;
|
||||
protected override resize(dimension: Dimension | null): void {
|
||||
if (this.node) {
|
||||
const layoutSize = this.computeLayoutSize(this.node, dimension);
|
||||
this._diffEditor.layout(layoutSize);
|
||||
// Workaround for https://github.com/microsoft/vscode/issues/217386#issuecomment-2711750462
|
||||
const leftEditor = this._diffEditor.getOriginalEditor();
|
||||
const hasReachedSideBySideBreakpoint = leftEditor.contextKeyService
|
||||
.getContextKeyValue(EditorContextKeys.diffEditorRenderSideBySideInlineBreakpointReached.key);
|
||||
if (hasReachedSideBySideBreakpoint !== this.lastReachedSideBySideBreakpoint) {
|
||||
leftEditor.updateOptions({ wordWrapOverride2: this.wordWrapOverride ?? hasReachedSideBySideBreakpoint ? 'off' : 'inherit' });
|
||||
}
|
||||
this.lastReachedSideBySideBreakpoint = !!hasReachedSideBySideBreakpoint;
|
||||
}
|
||||
}
|
||||
|
||||
override isActionSupported(id: string): boolean {
|
||||
const action = this._diffEditor.getSupportedActions().find(a => a.id === id);
|
||||
return !!action && action.isSupported() && super.isActionSupported(id);
|
||||
}
|
||||
|
||||
override deltaDecorations(params: DeltaDecorationParams): string[] {
|
||||
console.warn('`deltaDecorations` should be called on either the original, or the modified editor.');
|
||||
return [];
|
||||
}
|
||||
|
||||
override getResourceUri(): URI {
|
||||
return new URI(this.originalModel.uri);
|
||||
}
|
||||
override createMoveToUri(resourceUri: URI): URI {
|
||||
const [left, right] = DiffUris.decode(this.uri);
|
||||
return DiffUris.encode(left.withPath(resourceUri.path), right.withPath(resourceUri.path));
|
||||
}
|
||||
|
||||
override readonly onShouldDisplayDirtyDiffChanged = undefined;
|
||||
override shouldDisplayDirtyDiff(): boolean {
|
||||
return false;
|
||||
}
|
||||
override setShouldDisplayDirtyDiff(value: boolean): void {
|
||||
// no op
|
||||
}
|
||||
|
||||
protected override get baseEditor(): monaco.editor.IEditor {
|
||||
return this.diffEditor;
|
||||
}
|
||||
|
||||
protected override get baseModel(): monaco.editor.IEditorModel {
|
||||
return this.diffEditorModel;
|
||||
}
|
||||
}
|
||||
|
||||
class EmbeddedDiffEditor extends EmbeddedDiffEditorWidget implements IStandaloneDiffEditor {
|
||||
|
||||
protected override _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement,
|
||||
options: Readonly<IEditorConstructionOptions>): StandaloneCodeEditor {
|
||||
return instantiationService.createInstance(StandaloneCodeEditor, container, options);
|
||||
}
|
||||
|
||||
override getOriginalEditor(): IStandaloneCodeEditor {
|
||||
return super.getOriginalEditor() as IStandaloneCodeEditor;
|
||||
}
|
||||
|
||||
override getModifiedEditor(): IStandaloneCodeEditor {
|
||||
return super.getModifiedEditor() as IStandaloneCodeEditor;
|
||||
}
|
||||
|
||||
addCommand(keybinding: number, handler: ICommandHandler, context?: string): string | null {
|
||||
return this.getModifiedEditor().addCommand(keybinding, handler, context);
|
||||
}
|
||||
|
||||
createContextKey<T extends ContextKeyValue = ContextKeyValue>(key: string, defaultValue: T): IContextKey<T> {
|
||||
return this.getModifiedEditor().createContextKey(key, defaultValue);
|
||||
}
|
||||
|
||||
addAction(descriptor: IActionDescriptor): IDisposable {
|
||||
return this.getModifiedEditor().addAction(descriptor);
|
||||
}
|
||||
}
|
||||
39
packages/monaco/src/browser/monaco-diff-navigator-factory.ts
Normal file
39
packages/monaco/src/browser/monaco-diff-navigator-factory.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { DiffNavigator } from '@theia/editor/lib/browser';
|
||||
import { IDiffEditor } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
|
||||
|
||||
@injectable()
|
||||
export class MonacoDiffNavigatorFactory {
|
||||
|
||||
static nullNavigator = <DiffNavigator>{
|
||||
hasNext: () => false,
|
||||
hasPrevious: () => false,
|
||||
next: () => { },
|
||||
previous: () => { },
|
||||
};
|
||||
|
||||
createdDiffNavigator(editor: IDiffEditor): DiffNavigator {
|
||||
return {
|
||||
hasNext: () => true,
|
||||
hasPrevious: () => true,
|
||||
next: () => editor.goToDiff('next'),
|
||||
previous: () => editor.goToDiff('previous')
|
||||
};
|
||||
}
|
||||
}
|
||||
127
packages/monaco/src/browser/monaco-editor-content-menu.ts
Normal file
127
packages/monaco/src/browser/monaco-editor-content-menu.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 1C-Soft LLC and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { ArrayUtils, CommandMenu, CommandRegistry, CompoundMenuNode, Disposable, Event, MenuModelRegistry, MenuNode } from '@theia/core';
|
||||
import { ObservableFromEvent, ObservableUtils } from '@theia/core/lib/common/observable';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
||||
import { Context } from '@theia/core/lib/browser/context-key-service';
|
||||
import { EditorManager, EDITOR_CONTENT_MENU, EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { ICodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
|
||||
import { IContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';
|
||||
import { MonacoContextKeyService } from './monaco-context-key-service';
|
||||
import { MonacoEditor } from './monaco-editor';
|
||||
import { MonacoEditorOverlayButton } from './monaco-editor-overlay-button';
|
||||
|
||||
/**
|
||||
* Implements {@link EDITOR_CONTENT_MENU} for {@link MonacoEditor}s.
|
||||
*/
|
||||
@injectable()
|
||||
export class MonacoEditorContentMenuContribution implements FrontendApplicationContribution {
|
||||
|
||||
@inject(EditorManager)
|
||||
protected readonly editorManager: EditorManager;
|
||||
|
||||
@inject(MenuModelRegistry)
|
||||
protected readonly menus: MenuModelRegistry;
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commands: CommandRegistry;
|
||||
|
||||
@inject(MonacoContextKeyService)
|
||||
protected readonly contextKeyService: MonacoContextKeyService;
|
||||
|
||||
onStart(): void {
|
||||
this.editorManager.onCreated(editorWidget => {
|
||||
const editor = MonacoEditor.get(editorWidget);
|
||||
if (editor) {
|
||||
const disposable = this.createEditorContentMenu(editor, editorWidget);
|
||||
editor.onDispose(() => disposable.dispose());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected createEditorContentMenu(editor: MonacoEditor, editorWidget: EditorWidget): Disposable {
|
||||
const contextKeyService = (editor.getControl() as unknown as ICodeEditor).invokeWithinContext( // get the editor-scoped context key service
|
||||
accessor => accessor.get(IContextKeyService)
|
||||
);
|
||||
const context: Context = {
|
||||
getValue: key => contextKeyService.getContextKeyValue(key),
|
||||
onDidChange: Event.map(contextKeyService.onDidChangeContext, event => ({
|
||||
affects: keys => event.affectsSome(keys)
|
||||
}))
|
||||
};
|
||||
const menuNodesObservable = ObservableFromEvent.create(this.menus.onDidChange,
|
||||
() => this.getEditorContentMenuNodes(),
|
||||
{ isEqual: (a, b) => ArrayUtils.equals(a, b) }
|
||||
);
|
||||
return ObservableUtils.autorunWithDisposables(({ toDispose }) => {
|
||||
const menuNodes = menuNodesObservable.get();
|
||||
const firstMatchObservable = ObservableFromEvent.create(contextKeyService.onDidChangeContext, () => this.withContext(context,
|
||||
() => menuNodes.find(menuNode => menuNode.isVisible(EDITOR_CONTENT_MENU, this.contextKeyService, undefined, editorWidget))
|
||||
));
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
toDispose.push(ObservableUtils.autorunWithDisposables(({ toDispose }) => {
|
||||
const firstMatch = firstMatchObservable.get();
|
||||
if (firstMatch) {
|
||||
const button = new MonacoEditorOverlayButton(editor, firstMatch.label);
|
||||
toDispose.push(button);
|
||||
toDispose.push(button.onClick(() =>
|
||||
this.withContext(context, () => firstMatch.run(EDITOR_CONTENT_MENU, editorWidget))
|
||||
));
|
||||
|
||||
const handlersObservable = ObservableFromEvent.create(this.commands.onCommandsChanged,
|
||||
() => this.commands.getAllHandlers(firstMatch.id),
|
||||
{ isEqual: (a, b) => ArrayUtils.equals(a, b) }
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
toDispose.push(ObservableUtils.autorunWithDisposables(({ toDispose }) => {
|
||||
this.withContext(context, () => {
|
||||
button.enabled = firstMatch.isEnabled(EDITOR_CONTENT_MENU, editorWidget);
|
||||
const handlers = handlersObservable.get();
|
||||
for (const handler of handlers) {
|
||||
const { onDidChangeEnabled } = handler;
|
||||
if (onDidChangeEnabled) {
|
||||
// for handlers with declarative enablement such as those originating from `PluginContributionHandler.registerCommand`,
|
||||
// the onDidChangeEnabled event is context-dependent, so we need to ensure the subscription is made within `withContext`
|
||||
toDispose.push(onDidChangeEnabled(() => this.withContext(context, () =>
|
||||
button.enabled = firstMatch.isEnabled(EDITOR_CONTENT_MENU, editorWidget)
|
||||
)));
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
protected getEditorContentMenuNodes(): CommandMenu[] {
|
||||
const result: CommandMenu[] = [];
|
||||
const children = this.menus.getMenu(EDITOR_CONTENT_MENU)?.children ?? [];
|
||||
const getCommandMenuNodes = (nodes: MenuNode[]) => nodes.filter(CommandMenu.is);
|
||||
// inline the special navigation group, if any; the navigation group would always be the first element
|
||||
if (children.length && CompoundMenuNode.isNavigationGroup(children[0])) {
|
||||
result.push(...getCommandMenuNodes(children[0].children));
|
||||
}
|
||||
result.push(...getCommandMenuNodes(children));
|
||||
return result;
|
||||
}
|
||||
|
||||
protected withContext<T>(context: Context, callback: () => T): T {
|
||||
return this.contextKeyService.withContext(context, callback);
|
||||
}
|
||||
}
|
||||
655
packages/monaco/src/browser/monaco-editor-model.ts
Normal file
655
packages/monaco/src/browser/monaco-editor-model.ts
Normal file
@@ -0,0 +1,655 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { Position, Range, TextDocumentSaveReason } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import { TextEditorDocument, EncodingMode, FindMatchesOptions, FindMatch } from '@theia/editor/lib/browser';
|
||||
import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposable';
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
import { CancellationTokenSource, CancellationToken } from '@theia/core/lib/common/cancellation';
|
||||
import { Resource, ResourceError, ResourceVersion } from '@theia/core/lib/common/resource';
|
||||
import { Saveable, SaveOptions, SaveReason } from '@theia/core/lib/browser/saveable';
|
||||
import { MonacoToProtocolConverter } from './monaco-to-protocol-converter';
|
||||
import { ProtocolToMonacoConverter } from './protocol-to-monaco-converter';
|
||||
import { ILogger, Loggable, Log } from '@theia/core/lib/common/logger';
|
||||
import { ITextBufferFactory, ITextModel, ITextSnapshot } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
|
||||
import { IResolvedTextEditorModel } from '@theia/monaco-editor-core/esm/vs/editor/common/services/resolverService';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { ILanguageService } from '@theia/monaco-editor-core/esm/vs/editor/common/languages/language';
|
||||
import { IModelService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/model';
|
||||
import { createTextBufferFactoryFromStream } from '@theia/monaco-editor-core/esm/vs/editor/common/model/textModel';
|
||||
import { editorGeneratedPreferenceProperties } from '@theia/editor/lib/common/editor-generated-preference-schema';
|
||||
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
|
||||
import { BinaryBuffer } from '@theia/core/lib/common/buffer';
|
||||
import { Listener, ListenerList } from '@theia/core';
|
||||
import { EditorPreferences } from '@theia/editor/lib/common/editor-preferences';
|
||||
|
||||
export {
|
||||
TextDocumentSaveReason
|
||||
};
|
||||
|
||||
export interface WillSaveMonacoModelEvent {
|
||||
model: MonacoEditorModel,
|
||||
token: CancellationToken,
|
||||
options?: SaveOptions
|
||||
}
|
||||
|
||||
export interface MonacoModelContentChangedEvent {
|
||||
readonly model: MonacoEditorModel;
|
||||
readonly contentChanges: MonacoTextDocumentContentChange[];
|
||||
}
|
||||
|
||||
export interface MonacoTextDocumentContentChange {
|
||||
readonly range: Range;
|
||||
readonly rangeOffset: number;
|
||||
readonly rangeLength: number;
|
||||
readonly text: string;
|
||||
}
|
||||
|
||||
export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDocument {
|
||||
|
||||
suppressOpenEditorWhenDirty = false;
|
||||
lineNumbersMinChars = 3;
|
||||
|
||||
/* @deprecated there is no general save timeout, each participant should introduce a sensible timeout */
|
||||
readonly onWillSaveLoopTimeOut = 1500;
|
||||
protected bufferSavedVersionId: number;
|
||||
|
||||
protected model: ITextModel;
|
||||
protected readonly resolveModel: Promise<void>;
|
||||
|
||||
protected readonly toDispose = new DisposableCollection();
|
||||
protected readonly toDisposeOnAutoSave = new DisposableCollection();
|
||||
|
||||
protected readonly onDidChangeContentEmitter = new Emitter<MonacoModelContentChangedEvent>();
|
||||
readonly onDidChangeContent = this.onDidChangeContentEmitter.event;
|
||||
|
||||
get onContentChanged(): Event<void> {
|
||||
return (listener, thisArgs, disposables) => this.onDidChangeContent(() => listener(), thisArgs, disposables);
|
||||
}
|
||||
|
||||
protected readonly onDidSaveModelEmitter = new Emitter<ITextModel>();
|
||||
readonly onDidSaveModel = this.onDidSaveModelEmitter.event;
|
||||
|
||||
protected readonly onDidChangeValidEmitter = new Emitter<void>();
|
||||
readonly onDidChangeValid = this.onDidChangeValidEmitter.event;
|
||||
|
||||
protected readonly onDidChangeEncodingEmitter = new Emitter<string>();
|
||||
readonly onDidChangeEncoding = this.onDidChangeEncodingEmitter.event;
|
||||
|
||||
readonly onDidChangeReadOnly: Event<boolean | MarkdownString> = this.resource.onDidChangeReadOnly ?? Event.None;
|
||||
|
||||
private preferredEncoding: string | undefined;
|
||||
private contentEncoding: string | undefined;
|
||||
|
||||
protected resourceVersion: ResourceVersion | undefined;
|
||||
|
||||
protected readonly onWillSaveModelListeners: ListenerList<WillSaveMonacoModelEvent, Promise<void>> = new ListenerList;
|
||||
readonly onModelWillSaveModel = this.onWillSaveModelListeners.registration;
|
||||
|
||||
constructor(
|
||||
protected readonly resource: Resource,
|
||||
protected readonly m2p: MonacoToProtocolConverter,
|
||||
protected readonly p2m: ProtocolToMonacoConverter,
|
||||
protected readonly logger?: ILogger,
|
||||
protected readonly editorPreferences?: EditorPreferences
|
||||
) {
|
||||
this.toDispose.push(resource);
|
||||
this.toDispose.push(this.toDisposeOnAutoSave);
|
||||
this.toDispose.push(this.onDidChangeContentEmitter);
|
||||
this.toDispose.push(this.onDidSaveModelEmitter);
|
||||
this.toDispose.push(this.onDirtyChangedEmitter);
|
||||
this.toDispose.push(this.onDidChangeEncodingEmitter);
|
||||
this.toDispose.push(this.onDidChangeValidEmitter);
|
||||
this.toDispose.push(Disposable.create(() => this.cancelSave()));
|
||||
this.toDispose.push(Disposable.create(() => this.cancelSync()));
|
||||
this.resolveModel = this.readContents().then(
|
||||
content => this.initialize(content || '')
|
||||
);
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
this.model.undo();
|
||||
}
|
||||
|
||||
redo(): void {
|
||||
this.model.redo();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
isDisposed(): boolean {
|
||||
return this.toDispose.disposed;
|
||||
}
|
||||
|
||||
resolve(): Promise<void> {
|
||||
return this.resolveModel;
|
||||
}
|
||||
|
||||
isResolved(): boolean {
|
||||
return Boolean(this.model);
|
||||
}
|
||||
|
||||
setEncoding(encoding: string, mode: EncodingMode): Promise<void> {
|
||||
if (mode === EncodingMode.Decode && this.dirty) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (!this.setPreferredEncoding(encoding)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (mode === EncodingMode.Decode) {
|
||||
return this.sync();
|
||||
}
|
||||
return this.scheduleSave(this.cancelSave(), true, { saveReason: SaveReason.Manual });
|
||||
}
|
||||
|
||||
getEncoding(): string | undefined {
|
||||
return this.preferredEncoding || this.contentEncoding;
|
||||
}
|
||||
|
||||
protected setPreferredEncoding(encoding: string): boolean {
|
||||
if (encoding === this.preferredEncoding || (!this.preferredEncoding && encoding === this.contentEncoding)) {
|
||||
return false;
|
||||
}
|
||||
this.preferredEncoding = encoding;
|
||||
this.onDidChangeEncodingEmitter.fire(encoding);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected updateContentEncoding(): void {
|
||||
const contentEncoding = this.resource.encoding;
|
||||
if (!contentEncoding || this.contentEncoding === contentEncoding) {
|
||||
return;
|
||||
}
|
||||
this.contentEncoding = contentEncoding;
|
||||
if (!this.preferredEncoding) {
|
||||
this.onDidChangeEncodingEmitter.fire(contentEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #### Important
|
||||
* Only this method can create an instance of `monaco.editor.IModel`,
|
||||
* there should not be other calls to `monaco.editor.createModel`.
|
||||
*/
|
||||
protected initialize(value: string | ITextBufferFactory): void {
|
||||
if (!this.toDispose.disposed) {
|
||||
const uri = monaco.Uri.parse(this.resource.uri.toString());
|
||||
let firstLine;
|
||||
if (typeof value === 'string') {
|
||||
firstLine = value;
|
||||
const firstLF = value.indexOf('\n');
|
||||
if (firstLF !== -1) {
|
||||
firstLine = value.substring(0, firstLF);
|
||||
}
|
||||
} else {
|
||||
firstLine = value.getFirstLineText(1000);
|
||||
}
|
||||
const languageSelection = StandaloneServices.get(ILanguageService).createByFilepathOrFirstLine(uri, firstLine);
|
||||
this.model = StandaloneServices.get(IModelService).createModel(value, languageSelection, uri);
|
||||
this.resourceVersion = this.resource.version;
|
||||
this.setDirty(this._dirty || (!!this.resource.initiallyDirty));
|
||||
this.updateSavedVersionId();
|
||||
this.toDispose.push(this.model);
|
||||
this.toDispose.push(this.model.onDidChangeContent(event => this.fireDidChangeContent(event)));
|
||||
if (this.resource.onDidChangeContents) {
|
||||
this.toDispose.push(this.resource.onDidChangeContents(() => this.sync()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use `valid` to access it.
|
||||
* Use `setValid` to mutate it.
|
||||
*/
|
||||
protected _valid = false;
|
||||
/**
|
||||
* Whether it is possible to load content from the underlying resource.
|
||||
*/
|
||||
get valid(): boolean {
|
||||
return this._valid;
|
||||
}
|
||||
protected setValid(valid: boolean): void {
|
||||
if (valid === this._valid) {
|
||||
return;
|
||||
}
|
||||
this._valid = valid;
|
||||
this.onDidChangeValidEmitter.fire(undefined);
|
||||
}
|
||||
|
||||
protected _dirty = false;
|
||||
get dirty(): boolean {
|
||||
return this._dirty;
|
||||
}
|
||||
protected setDirty(dirty: boolean): void {
|
||||
if (dirty === this._dirty) {
|
||||
return;
|
||||
}
|
||||
this._dirty = dirty;
|
||||
if (dirty === false) {
|
||||
this.updateSavedVersionId();
|
||||
}
|
||||
this.onDirtyChangedEmitter.fire(undefined);
|
||||
}
|
||||
|
||||
private updateSavedVersionId(): void {
|
||||
this.bufferSavedVersionId = this.model.getAlternativeVersionId();
|
||||
}
|
||||
|
||||
protected readonly onDirtyChangedEmitter = new Emitter<void>();
|
||||
get onDirtyChanged(): Event<void> {
|
||||
return this.onDirtyChangedEmitter.event;
|
||||
}
|
||||
|
||||
get uri(): string {
|
||||
return this.resource.uri.toString();
|
||||
}
|
||||
|
||||
get autosaveable(): boolean | undefined {
|
||||
return this.resource.autosaveable;
|
||||
}
|
||||
|
||||
protected _languageId: string | undefined;
|
||||
get languageId(): string {
|
||||
return this._languageId !== undefined ? this._languageId : this.model.getLanguageId();
|
||||
}
|
||||
|
||||
getLanguageId(): string | undefined {
|
||||
return this.languageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a hack to dispatch close notification with an old language id; don't use it.
|
||||
*/
|
||||
setLanguageId(languageId: string | undefined): void {
|
||||
this._languageId = languageId;
|
||||
}
|
||||
|
||||
get version(): number {
|
||||
return this.model.getVersionId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return selected text by Range or all text by default
|
||||
*/
|
||||
getText(range?: Range): string {
|
||||
if (!range) {
|
||||
return this.model.getValue();
|
||||
} else {
|
||||
return this.model.getValueInRange(this.p2m.asRange(range));
|
||||
}
|
||||
}
|
||||
|
||||
positionAt(offset: number): Position {
|
||||
const { lineNumber, column } = this.model.getPositionAt(offset);
|
||||
return this.m2p.asPosition(lineNumber, column);
|
||||
}
|
||||
|
||||
offsetAt(position: Position): number {
|
||||
return this.model.getOffsetAt(this.p2m.asPosition(position));
|
||||
}
|
||||
|
||||
get lineCount(): number {
|
||||
return this.model.getLineCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a line in a text document expressed as a one-based position.
|
||||
*/
|
||||
getLineContent(lineNumber: number): string {
|
||||
return this.model.getLineContent(lineNumber);
|
||||
}
|
||||
|
||||
getLineMaxColumn(lineNumber: number): number {
|
||||
return this.model.getLineMaxColumn(lineNumber);
|
||||
}
|
||||
|
||||
toValidPosition(position: Position): Position {
|
||||
const { lineNumber, column } = this.model.validatePosition(this.p2m.asPosition(position));
|
||||
return this.m2p.asPosition(lineNumber, column);
|
||||
}
|
||||
|
||||
toValidRange(range: Range): Range {
|
||||
return this.m2p.asRange(this.model.validateRange(this.p2m.asRange(range)));
|
||||
}
|
||||
|
||||
get readOnly(): boolean | MarkdownString {
|
||||
return this.resource.readOnly ?? false;
|
||||
}
|
||||
|
||||
isReadonly(): boolean | MarkdownString {
|
||||
return this.readOnly;
|
||||
}
|
||||
|
||||
get onDispose(): monaco.IEvent<void> {
|
||||
return this.toDispose.onDispose;
|
||||
}
|
||||
|
||||
get onWillDispose(): Event<void> {
|
||||
return this.toDispose.onDispose;
|
||||
}
|
||||
|
||||
// We have a TypeScript problem here. There is a const enum `DefaultEndOfLine` used for ITextModel and a non-const redeclaration of that enum in the public API in
|
||||
// Monaco.editor. The values will be the same, but TS won't accept that the two enums are equivalent, so it says these types are irreconcilable.
|
||||
get textEditorModel(): monaco.editor.ITextModel & ITextModel {
|
||||
// @ts-expect-error ts(2322)
|
||||
return this.model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all matches in an editor for the given options.
|
||||
* @param options the options for finding matches.
|
||||
*
|
||||
* @returns the list of matches.
|
||||
*/
|
||||
findMatches(options: FindMatchesOptions): FindMatch[] {
|
||||
const wordSeparators = this.editorPreferences?.['editor.wordSeparators'] ?? editorGeneratedPreferenceProperties['editor.wordSeparators'].default as string;
|
||||
const results: monaco.editor.FindMatch[] = this.model.findMatches(
|
||||
options.searchString,
|
||||
false,
|
||||
options.isRegex,
|
||||
options.matchCase,
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
options.matchWholeWord ? wordSeparators : null,
|
||||
true,
|
||||
options.limitResultCount
|
||||
);
|
||||
const extractedMatches: FindMatch[] = [];
|
||||
results.forEach(r => {
|
||||
if (r.matches) {
|
||||
extractedMatches.push({
|
||||
matches: r.matches,
|
||||
range: Range.create(r.range.startLineNumber, r.range.startColumn, r.range.endLineNumber, r.range.endColumn)
|
||||
});
|
||||
}
|
||||
});
|
||||
return extractedMatches;
|
||||
}
|
||||
|
||||
async load(): Promise<MonacoEditorModel> {
|
||||
await this.resolveModel;
|
||||
return this;
|
||||
}
|
||||
|
||||
save(options?: SaveOptions): Promise<void> {
|
||||
return this.scheduleSave(undefined, undefined, {
|
||||
saveReason: TextDocumentSaveReason.Manual,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
protected pendingOperation = Promise.resolve();
|
||||
protected async run(operation: () => Promise<void>): Promise<void> {
|
||||
if (this.toDispose.disposed) {
|
||||
return;
|
||||
}
|
||||
return this.pendingOperation = this.pendingOperation.then(async () => {
|
||||
try {
|
||||
await operation();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected syncCancellationTokenSource = new CancellationTokenSource();
|
||||
protected cancelSync(): CancellationToken {
|
||||
this.trace(log => log('MonacoEditorModel.cancelSync'));
|
||||
this.syncCancellationTokenSource.cancel();
|
||||
this.syncCancellationTokenSource = new CancellationTokenSource();
|
||||
return this.syncCancellationTokenSource.token;
|
||||
}
|
||||
|
||||
async sync(): Promise<void> {
|
||||
const token = this.cancelSync();
|
||||
return this.run(() => this.doSync(token));
|
||||
}
|
||||
protected async doSync(token: CancellationToken): Promise<void> {
|
||||
this.trace(log => log('MonacoEditorModel.doSync - enter'));
|
||||
if (token.isCancellationRequested) {
|
||||
this.trace(log => log('MonacoEditorModel.doSync - exit - cancelled'));
|
||||
return;
|
||||
}
|
||||
|
||||
const value = await this.readContents();
|
||||
if (value === undefined) {
|
||||
this.trace(log => log('MonacoEditorModel.doSync - exit - resource not found'));
|
||||
return;
|
||||
}
|
||||
if (token.isCancellationRequested) {
|
||||
this.trace(log => log('MonacoEditorModel.doSync - exit - cancelled while looking for a resource'));
|
||||
return;
|
||||
}
|
||||
if (this._dirty) {
|
||||
this.trace(log => log('MonacoEditorModel.doSync - exit - pending dirty changes'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.resourceVersion = this.resource.version;
|
||||
this.updateModel(() => StandaloneServices.get(IModelService).updateModel(this.model, value), {
|
||||
ignoreDirty: true,
|
||||
ignoreContentChanges: true
|
||||
});
|
||||
this.trace(log => log('MonacoEditorModel.doSync - exit'));
|
||||
}
|
||||
protected async readContents(): Promise<string | ITextBufferFactory | undefined> {
|
||||
try {
|
||||
const options = { encoding: this.getEncoding() };
|
||||
const content = await (this.resource.readStream ? this.resource.readStream(options) : this.resource.readContents(options));
|
||||
let value;
|
||||
if (typeof content === 'string') {
|
||||
value = content;
|
||||
} else {
|
||||
value = createTextBufferFactoryFromStream(content);
|
||||
}
|
||||
this.updateContentEncoding();
|
||||
this.setValid(true);
|
||||
return value;
|
||||
} catch (e) {
|
||||
this.setValid(false);
|
||||
if (ResourceError.NotFound.is(e)) {
|
||||
return undefined;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
protected ignoreDirtyEdits = false;
|
||||
protected markAsDirty(): void {
|
||||
this.trace(log => log('MonacoEditorModel.markAsDirty - enter'));
|
||||
if (this.ignoreDirtyEdits) {
|
||||
this.trace(log => log('MonacoEditorModel.markAsDirty - exit - ignoring dirty changes enabled'));
|
||||
return;
|
||||
}
|
||||
this.cancelSync();
|
||||
this.setDirty(true);
|
||||
this.trace(log => log('MonacoEditorModel.markAsDirty - exit'));
|
||||
}
|
||||
|
||||
protected saveCancellationTokenSource = new CancellationTokenSource();
|
||||
protected cancelSave(): CancellationToken {
|
||||
this.trace(log => log('MonacoEditorModel.cancelSave'));
|
||||
this.saveCancellationTokenSource.cancel();
|
||||
this.saveCancellationTokenSource = new CancellationTokenSource();
|
||||
return this.saveCancellationTokenSource.token;
|
||||
}
|
||||
|
||||
protected scheduleSave(token: CancellationToken = this.cancelSave(), overwriteEncoding?: boolean, options?: SaveOptions): Promise<void> {
|
||||
return this.run(() => this.doSave(token, overwriteEncoding, options));
|
||||
}
|
||||
|
||||
protected ignoreContentChanges = false;
|
||||
protected readonly contentChanges: MonacoTextDocumentContentChange[] = [];
|
||||
protected pushContentChanges(contentChanges: MonacoTextDocumentContentChange[]): void {
|
||||
if (!this.ignoreContentChanges) {
|
||||
this.contentChanges.push(...contentChanges);
|
||||
}
|
||||
}
|
||||
|
||||
protected fireDidChangeContent(event: monaco.editor.IModelContentChangedEvent): void {
|
||||
this.trace(log => log(`MonacoEditorModel.fireDidChangeContent - enter - ${JSON.stringify(event, undefined, 2)}`));
|
||||
if (this.model.getAlternativeVersionId() === this.bufferSavedVersionId) {
|
||||
this.setDirty(false);
|
||||
} else {
|
||||
this.markAsDirty();
|
||||
}
|
||||
|
||||
const changeContentEvent = this.asContentChangedEvent(event);
|
||||
this.onDidChangeContentEmitter.fire(changeContentEvent);
|
||||
this.pushContentChanges(changeContentEvent.contentChanges);
|
||||
this.trace(log => log('MonacoEditorModel.fireDidChangeContent - exit'));
|
||||
}
|
||||
protected asContentChangedEvent(event: monaco.editor.IModelContentChangedEvent): MonacoModelContentChangedEvent {
|
||||
const contentChanges = event.changes.map(change => this.asTextDocumentContentChangeEvent(change));
|
||||
return { model: this, contentChanges };
|
||||
}
|
||||
protected asTextDocumentContentChangeEvent(change: monaco.editor.IModelContentChange): MonacoTextDocumentContentChange {
|
||||
const range = this.m2p.asRange(change.range);
|
||||
const rangeOffset = change.rangeOffset;
|
||||
const rangeLength = change.rangeLength;
|
||||
const text = change.text;
|
||||
return { range, rangeOffset, rangeLength, text };
|
||||
}
|
||||
|
||||
protected applyEdits(
|
||||
operations: monaco.editor.IIdentifiedSingleEditOperation[],
|
||||
options?: Partial<MonacoEditorModel.ApplyEditsOptions>
|
||||
): void {
|
||||
return this.updateModel(() => this.model.applyEdits(operations), options);
|
||||
}
|
||||
|
||||
protected updateModel<T>(doUpdate: () => T, options?: Partial<MonacoEditorModel.ApplyEditsOptions>): T {
|
||||
const resolvedOptions: MonacoEditorModel.ApplyEditsOptions = {
|
||||
ignoreDirty: false,
|
||||
ignoreContentChanges: false,
|
||||
...options
|
||||
};
|
||||
const { ignoreDirtyEdits, ignoreContentChanges } = this;
|
||||
this.ignoreDirtyEdits = resolvedOptions.ignoreDirty;
|
||||
this.ignoreContentChanges = resolvedOptions.ignoreContentChanges;
|
||||
try {
|
||||
return doUpdate();
|
||||
} finally {
|
||||
this.ignoreDirtyEdits = ignoreDirtyEdits;
|
||||
this.ignoreContentChanges = ignoreContentChanges;
|
||||
}
|
||||
}
|
||||
|
||||
protected async doSave(token: CancellationToken, overwriteEncoding?: boolean, options?: SaveOptions): Promise<void> {
|
||||
if (token.isCancellationRequested || !this.resource.saveContents) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.fireWillSaveModel(token, options);
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
const changes = [...this.contentChanges];
|
||||
if ((changes.length === 0 && !this.resource.initiallyDirty) && !overwriteEncoding && options?.saveReason !== TextDocumentSaveReason.Manual) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentToSaveVersion = this.model.getAlternativeVersionId();
|
||||
const contentLength = this.model.getValueLength();
|
||||
const content = this.model.getValue();
|
||||
try {
|
||||
const encoding = this.getEncoding();
|
||||
const version = this.resourceVersion;
|
||||
await Resource.save(this.resource, { changes, content, contentLength, options: { encoding, overwriteEncoding, version } }, token);
|
||||
this.contentChanges.splice(0, changes.length);
|
||||
this.resourceVersion = this.resource.version;
|
||||
this.updateContentEncoding();
|
||||
this.setValid(true);
|
||||
|
||||
if (token.isCancellationRequested && this.model.getAlternativeVersionId() !== currentToSaveVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setDirty(false);
|
||||
this.fireDidSaveModel();
|
||||
} catch (e) {
|
||||
if (!ResourceError.OutOfSync.is(e)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async fireWillSaveModel(token: CancellationToken, options?: SaveOptions): Promise<void> {
|
||||
await Listener.awaitAll({ model: this, token, options }, this.onWillSaveModelListeners);
|
||||
}
|
||||
|
||||
protected fireDidSaveModel(): void {
|
||||
this.onDidSaveModelEmitter.fire(this.model);
|
||||
}
|
||||
|
||||
async revert(options?: Saveable.RevertOptions): Promise<void> {
|
||||
this.trace(log => log('MonacoEditorModel.revert - enter'));
|
||||
this.cancelSave();
|
||||
const soft = options && options.soft;
|
||||
if (soft !== true) {
|
||||
const dirty = this._dirty;
|
||||
this._dirty = false;
|
||||
try {
|
||||
await this.sync();
|
||||
} finally {
|
||||
this._dirty = dirty;
|
||||
}
|
||||
}
|
||||
this.setDirty(false);
|
||||
this.trace(log => log('MonacoEditorModel.revert - exit'));
|
||||
}
|
||||
|
||||
createSnapshot(preserveBOM?: boolean): ITextSnapshot {
|
||||
return { read: () => this.model.getValue(undefined, preserveBOM) };
|
||||
}
|
||||
|
||||
applySnapshot(snapshot: Saveable.Snapshot): void {
|
||||
const value = Saveable.Snapshot.read(snapshot) ?? '';
|
||||
this.model.setValue(value);
|
||||
}
|
||||
|
||||
async serialize(): Promise<BinaryBuffer> {
|
||||
return BinaryBuffer.fromString(this.model.getValue());
|
||||
}
|
||||
|
||||
filters(): { [name: string]: string[] } {
|
||||
const language = monaco.languages.getLanguages().find(lang => lang.id === this.languageId);
|
||||
if (!language || !language.extensions) {
|
||||
return {};
|
||||
}
|
||||
const name = language.aliases?.[0] || this.languageId;
|
||||
const extensions = language.extensions.map(ext => ext.startsWith('.') ? ext.substring(1) : ext);
|
||||
return { [name]: extensions };
|
||||
}
|
||||
|
||||
protected trace(loggable: Loggable): void {
|
||||
if (this.logger) {
|
||||
this.logger.debug((log: Log) =>
|
||||
loggable((message, ...params) => log(message, ...params, this.resource.uri.toString(true)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
export namespace MonacoEditorModel {
|
||||
export interface ApplyEditsOptions {
|
||||
ignoreDirty: boolean
|
||||
ignoreContentChanges: boolean
|
||||
}
|
||||
}
|
||||
69
packages/monaco/src/browser/monaco-editor-overlay-button.ts
Normal file
69
packages/monaco/src/browser/monaco-editor-overlay-button.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 1C-Soft LLC and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { Disposable, DisposableCollection, Emitter } from '@theia/core';
|
||||
import { DISABLED_CLASS, onDomEvent } from '@theia/core/lib/browser';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { MonacoEditor } from './monaco-editor';
|
||||
|
||||
export class MonacoEditorOverlayButton implements Disposable {
|
||||
|
||||
private static nextId = 1;
|
||||
|
||||
readonly domNode: HTMLElement;
|
||||
|
||||
protected readonly onClickEmitter = new Emitter<void>();
|
||||
readonly onClick = this.onClickEmitter.event;
|
||||
|
||||
protected readonly toDispose = new DisposableCollection(this.onClickEmitter);
|
||||
|
||||
constructor(
|
||||
editor: MonacoEditor,
|
||||
label: string,
|
||||
id = 'theia-editor.overlayButtonWidget' + MonacoEditorOverlayButton.nextId++
|
||||
) {
|
||||
this.domNode = document.createElement('div');
|
||||
this.domNode.classList.add('overlay-button');
|
||||
this.domNode.textContent = label;
|
||||
this.toDispose.push(onDomEvent(this.domNode, 'click', () => this.onClickEmitter.fire()));
|
||||
|
||||
const overlayWidget: monaco.editor.IOverlayWidget = {
|
||||
getId: () => id,
|
||||
getDomNode: () => this.domNode,
|
||||
getPosition: () => ({
|
||||
preference: monaco.editor.OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER
|
||||
})
|
||||
};
|
||||
editor.getControl().addOverlayWidget(overlayWidget);
|
||||
this.toDispose.push(Disposable.create(() => editor.getControl().removeOverlayWidget(overlayWidget)));
|
||||
}
|
||||
|
||||
get enabled(): boolean {
|
||||
return !this.domNode.classList.contains(DISABLED_CLASS);
|
||||
}
|
||||
|
||||
set enabled(value: boolean) {
|
||||
if (value) {
|
||||
this.domNode.classList.remove(DISABLED_CLASS);
|
||||
} else {
|
||||
this.domNode.classList.add(DISABLED_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
}
|
||||
285
packages/monaco/src/browser/monaco-editor-peek-view-widget.ts
Normal file
285
packages/monaco/src/browser/monaco-editor-peek-view-widget.ts
Normal file
@@ -0,0 +1,285 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 1C-Soft LLC and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { Position, Range } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import { DisposableCollection } from '@theia/core';
|
||||
import { MonacoEditor } from './monaco-editor';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { PeekViewWidget, IPeekViewOptions, IPeekViewStyles } from '@theia/monaco-editor-core/esm/vs/editor/contrib/peekView/browser/peekView';
|
||||
import { ICodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
|
||||
import { ActionBar } from '@theia/monaco-editor-core/esm/vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Action } from '@theia/monaco-editor-core/esm/vs/base/common/actions';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { IInstantiationService } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/instantiation';
|
||||
import { IThemeService } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService';
|
||||
import { Color } from '@theia/monaco-editor-core/esm/vs/base/common/color';
|
||||
|
||||
export { peekViewBorder, peekViewTitleBackground, peekViewTitleForeground, peekViewTitleInfoForeground }
|
||||
from '@theia/monaco-editor-core/esm/vs/editor/contrib/peekView/browser/peekView';
|
||||
|
||||
export namespace MonacoEditorPeekViewWidget {
|
||||
export interface Styles {
|
||||
frameColor?: string;
|
||||
arrowColor?: string;
|
||||
headerBackgroundColor?: string;
|
||||
primaryHeadingColor?: string;
|
||||
secondaryHeadingColor?: string;
|
||||
}
|
||||
export interface Options {
|
||||
showFrame?: boolean;
|
||||
showArrow?: boolean;
|
||||
frameWidth?: number;
|
||||
className?: string;
|
||||
isAccessible?: boolean;
|
||||
isResizeable?: boolean;
|
||||
keepEditorSelection?: boolean;
|
||||
allowUnlimitedHeight?: boolean;
|
||||
ordinal?: number;
|
||||
showInHiddenAreas?: boolean;
|
||||
supportOnTitleClick?: boolean;
|
||||
}
|
||||
export interface Action {
|
||||
readonly id: string;
|
||||
label: string;
|
||||
tooltip: string;
|
||||
class: string | undefined;
|
||||
enabled: boolean;
|
||||
checked?: boolean;
|
||||
run(...args: unknown[]): unknown;
|
||||
}
|
||||
export interface ActionOptions {
|
||||
icon?: boolean;
|
||||
label?: boolean;
|
||||
keybinding?: string;
|
||||
index?: number;
|
||||
}
|
||||
}
|
||||
|
||||
export class MonacoEditorPeekViewWidget {
|
||||
|
||||
protected readonly toDispose = new DisposableCollection();
|
||||
|
||||
readonly onDidClose = this.toDispose.onDispose;
|
||||
|
||||
private readonly themeService = StandaloneServices.get(IThemeService);
|
||||
|
||||
private readonly delegate;
|
||||
|
||||
constructor(
|
||||
readonly editor: MonacoEditor,
|
||||
options: MonacoEditorPeekViewWidget.Options = {},
|
||||
protected styles: MonacoEditorPeekViewWidget.Styles = {}
|
||||
) {
|
||||
const that = this;
|
||||
this.toDispose.push(this.delegate = new class extends PeekViewWidget {
|
||||
|
||||
get actionBar(): ActionBar | undefined {
|
||||
return this._actionbarWidget;
|
||||
}
|
||||
|
||||
fillContainer(container: HTMLElement): void {
|
||||
super._fillContainer(container);
|
||||
}
|
||||
|
||||
protected override _fillContainer(container: HTMLElement): void {
|
||||
that.fillContainer(container);
|
||||
}
|
||||
|
||||
fillHead(container: HTMLElement, noCloseAction?: boolean): void {
|
||||
super._fillHead(container, noCloseAction);
|
||||
}
|
||||
|
||||
protected override _fillHead(container: HTMLElement, noCloseAction?: boolean): void {
|
||||
that.fillHead(container, noCloseAction);
|
||||
}
|
||||
|
||||
fillBody(container: HTMLElement): void {
|
||||
// super._fillBody is an abstract method
|
||||
}
|
||||
|
||||
protected override _fillBody(container: HTMLElement): void {
|
||||
that.fillBody(container);
|
||||
};
|
||||
|
||||
doLayoutHead(heightInPixel: number, widthInPixel: number): void {
|
||||
super._doLayoutHead(heightInPixel, widthInPixel);
|
||||
}
|
||||
|
||||
protected override _doLayoutHead(heightInPixel: number, widthInPixel: number): void {
|
||||
that.doLayoutHead(heightInPixel, widthInPixel);
|
||||
}
|
||||
|
||||
doLayoutBody(heightInPixel: number, widthInPixel: number): void {
|
||||
super._doLayoutBody(heightInPixel, widthInPixel);
|
||||
}
|
||||
|
||||
protected override _doLayoutBody(heightInPixel: number, widthInPixel: number): void {
|
||||
that.doLayoutBody(heightInPixel, widthInPixel);
|
||||
}
|
||||
|
||||
onWidth(widthInPixel: number): void {
|
||||
super._onWidth(widthInPixel);
|
||||
}
|
||||
|
||||
protected override _onWidth(widthInPixel: number): void {
|
||||
that.onWidth(widthInPixel);
|
||||
}
|
||||
|
||||
doRevealRange(range: monaco.Range, isLastLine: boolean): void {
|
||||
super.revealRange(range, isLastLine);
|
||||
}
|
||||
|
||||
protected override revealRange(range: monaco.Range, isLastLine: boolean): void {
|
||||
that.doRevealRange(that.editor['m2p'].asRange(range), isLastLine);
|
||||
}
|
||||
|
||||
getBodyElement(): HTMLDivElement | undefined {
|
||||
return this._bodyElement;
|
||||
}
|
||||
|
||||
setBodyElement(element: HTMLDivElement | undefined): void {
|
||||
this._bodyElement = element;
|
||||
}
|
||||
|
||||
getHeadElement(): HTMLDivElement | undefined {
|
||||
return this._headElement;
|
||||
}
|
||||
|
||||
setHeadElement(element: HTMLDivElement | undefined): void {
|
||||
this._headElement = element;
|
||||
}
|
||||
|
||||
override setCssClass(className: string, classToReplace?: string | undefined): void {
|
||||
super.setCssClass(className, classToReplace);
|
||||
}
|
||||
}(
|
||||
editor.getControl() as unknown as ICodeEditor,
|
||||
Object.assign(<IPeekViewOptions>{}, options, this.convertStyles(styles)),
|
||||
StandaloneServices.get(IInstantiationService)
|
||||
));
|
||||
this.toDispose.push(this.themeService.onDidColorThemeChange(() => this.style(this.styles)));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
create(): void {
|
||||
this.delegate.create();
|
||||
}
|
||||
|
||||
setTitle(primaryHeading: string, secondaryHeading?: string): void {
|
||||
this.delegate.setTitle(primaryHeading, secondaryHeading);
|
||||
}
|
||||
|
||||
style(styles: MonacoEditorPeekViewWidget.Styles): void {
|
||||
this.delegate.style(this.convertStyles(this.styles = styles));
|
||||
}
|
||||
|
||||
show(rangeOrPos: Range | Position, heightInLines: number): void {
|
||||
this.delegate.show(this.convertRangeOrPosition(rangeOrPos), heightInLines);
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.delegate.hide();
|
||||
}
|
||||
|
||||
clearActions(): void {
|
||||
this.delegate.actionBar?.clear();
|
||||
}
|
||||
|
||||
addAction(id: string, label: string, cssClass: string | undefined, enabled: boolean, actionCallback: (arg: unknown) => unknown,
|
||||
options?: MonacoEditorPeekViewWidget.ActionOptions): MonacoEditorPeekViewWidget.Action {
|
||||
options = cssClass ? { icon: true, label: false, ...options } : { icon: false, label: true, ...options };
|
||||
const { actionBar } = this.delegate;
|
||||
if (!actionBar) {
|
||||
throw new Error('Action bar has not been created.');
|
||||
}
|
||||
const action = new Action(id, label, cssClass, enabled, actionCallback);
|
||||
actionBar.push(action, options);
|
||||
return action;
|
||||
}
|
||||
|
||||
protected fillContainer(container: HTMLElement): void {
|
||||
this.delegate.fillContainer(container);
|
||||
}
|
||||
|
||||
protected fillHead(container: HTMLElement, noCloseAction?: boolean): void {
|
||||
this.delegate.fillHead(container, noCloseAction);
|
||||
}
|
||||
|
||||
protected fillBody(container: HTMLElement): void {
|
||||
this.delegate.fillBody(container);
|
||||
}
|
||||
|
||||
protected doLayoutHead(heightInPixel: number, widthInPixel: number): void {
|
||||
this.delegate.doLayoutHead(heightInPixel, widthInPixel);
|
||||
}
|
||||
|
||||
protected doLayoutBody(heightInPixel: number, widthInPixel: number): void {
|
||||
this.delegate.doLayoutBody(heightInPixel, widthInPixel);
|
||||
}
|
||||
|
||||
protected onWidth(widthInPixel: number): void {
|
||||
this.delegate.onWidth(widthInPixel);
|
||||
}
|
||||
|
||||
protected doRevealRange(range: Range, isLastLine: boolean): void {
|
||||
this.delegate.doRevealRange(this.editor['p2m'].asRange(range), isLastLine);
|
||||
}
|
||||
|
||||
protected get bodyElement(): HTMLDivElement | undefined {
|
||||
return this.delegate.getBodyElement();
|
||||
}
|
||||
|
||||
protected set bodyElement(element: HTMLDivElement | undefined) {
|
||||
this.delegate.setBodyElement(element);
|
||||
}
|
||||
|
||||
protected get headElement(): HTMLDivElement | undefined {
|
||||
return this.delegate.getHeadElement();
|
||||
}
|
||||
|
||||
protected set headElement(element: HTMLDivElement | undefined) {
|
||||
this.delegate.setHeadElement(element);
|
||||
}
|
||||
|
||||
protected setCssClass(className: string, classToReplace?: string | undefined): void {
|
||||
this.delegate.setCssClass(className, classToReplace);
|
||||
}
|
||||
|
||||
private convertStyles(styles: MonacoEditorPeekViewWidget.Styles): IPeekViewStyles {
|
||||
return {
|
||||
frameColor: this.convertColor(styles.frameColor),
|
||||
arrowColor: this.convertColor(styles.arrowColor),
|
||||
headerBackgroundColor: this.convertColor(styles.headerBackgroundColor),
|
||||
primaryHeadingColor: this.convertColor(styles.primaryHeadingColor),
|
||||
secondaryHeadingColor: this.convertColor(styles.secondaryHeadingColor),
|
||||
};
|
||||
}
|
||||
|
||||
private convertColor(color?: string): Color | undefined {
|
||||
if (color === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return this.themeService.getColorTheme().getColor(color) || Color.fromHex(color);
|
||||
}
|
||||
|
||||
private convertRangeOrPosition(arg: Range | Position): monaco.Range | monaco.Position {
|
||||
const p2m = this.editor['p2m'];
|
||||
return Range.is(arg) ? p2m.asRange(arg) : p2m.asPosition(arg);
|
||||
}
|
||||
}
|
||||
565
packages/monaco/src/browser/monaco-editor-provider.ts
Normal file
565
packages/monaco/src/browser/monaco-editor-provider.ts
Normal file
@@ -0,0 +1,565 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { TextEditor, DiffNavigator } from '@theia/editor/lib/browser';
|
||||
import { DiffUris } from '@theia/core/lib/browser/diff-uris';
|
||||
import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify';
|
||||
import { DisposableCollection, deepClone, Disposable, CancellationToken } from '@theia/core/lib/common';
|
||||
import { MonacoDiffEditor } from './monaco-diff-editor';
|
||||
import { MonacoDiffNavigatorFactory } from './monaco-diff-navigator-factory';
|
||||
import { EditorServiceOverrides, MonacoEditor, MonacoEditorServices } from './monaco-editor';
|
||||
import { MonacoEditorModel, TextDocumentSaveReason } from './monaco-editor-model';
|
||||
import { MonacoWorkspace } from './monaco-workspace';
|
||||
import { ContributionProvider } from '@theia/core';
|
||||
import { KeybindingRegistry, OpenerService, open, WidgetOpenerOptions, SaveOptions, FormatType } from '@theia/core/lib/browser';
|
||||
import { MonacoResolvedKeybinding } from './monaco-resolved-keybinding';
|
||||
import { HttpOpenHandlerOptions } from '@theia/core/lib/browser/http-open-handler';
|
||||
import { MonacoToProtocolConverter } from './monaco-to-protocol-converter';
|
||||
import { ProtocolToMonacoConverter } from './protocol-to-monaco-converter';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { IOpenerService, OpenExternalOptions, OpenInternalOptions } from '@theia/monaco-editor-core/esm/vs/platform/opener/common/opener';
|
||||
import { IKeybindingService } from '@theia/monaco-editor-core/esm/vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from '@theia/monaco-editor-core/esm/vs/platform/contextview/browser/contextView';
|
||||
import { KeyCodeChord } from '@theia/monaco-editor-core/esm/vs/base/common/keybindings';
|
||||
import { IContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';
|
||||
import { ITextModelService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/resolverService';
|
||||
import { IReference } from '@theia/monaco-editor-core/esm/vs/base/common/lifecycle';
|
||||
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
|
||||
import { SimpleMonacoEditor } from './simple-monaco-editor';
|
||||
import { ICodeEditorWidgetOptions } from '@theia/monaco-editor-core/esm/vs/editor/browser/widget/codeEditor/codeEditorWidget';
|
||||
import { timeoutReject } from '@theia/core/lib/common/promise-util';
|
||||
import { FileSystemPreferences } from '@theia/filesystem/lib/common';
|
||||
import { insertFinalNewline } from './monaco-utilities';
|
||||
import { EditorPreferenceChange, EditorPreferences } from '@theia/editor/lib/common/editor-preferences';
|
||||
|
||||
export const MonacoEditorFactory = Symbol('MonacoEditorFactory');
|
||||
export interface MonacoEditorFactory {
|
||||
readonly scheme: string;
|
||||
create(model: MonacoEditorModel, defaultOptions: MonacoEditor.IOptions, defaultOverrides: EditorServiceOverrides): Promise<MonacoEditor>;
|
||||
}
|
||||
|
||||
export const SaveParticipant = Symbol('SaveParticipant');
|
||||
|
||||
export interface SaveParticipant {
|
||||
readonly order: number;
|
||||
applyChangesOnSave(
|
||||
editor: MonacoEditor,
|
||||
cancellationToken: CancellationToken,
|
||||
options?: SaveOptions): Promise<void>;
|
||||
}
|
||||
export const SAVE_PARTICIPANT_DEFAULT_ORDER = 0;
|
||||
|
||||
@injectable()
|
||||
export class MonacoEditorProvider {
|
||||
|
||||
@inject(ContributionProvider)
|
||||
@named(MonacoEditorFactory)
|
||||
protected readonly factories: ContributionProvider<MonacoEditorFactory>;
|
||||
|
||||
@inject(MonacoEditorServices)
|
||||
protected readonly services: MonacoEditorServices;
|
||||
|
||||
@inject(KeybindingRegistry)
|
||||
protected readonly keybindingRegistry: KeybindingRegistry;
|
||||
|
||||
@inject(OpenerService)
|
||||
protected readonly openerService: OpenerService;
|
||||
@inject(ContributionProvider)
|
||||
@named(SaveParticipant)
|
||||
protected readonly saveProviderContributions: ContributionProvider<SaveParticipant>;
|
||||
|
||||
@inject(FileSystemPreferences)
|
||||
protected readonly filePreferences: FileSystemPreferences;
|
||||
protected saveParticipants: SaveParticipant[];
|
||||
|
||||
protected _current: MonacoEditor | undefined;
|
||||
/**
|
||||
* Returns the last focused MonacoEditor.
|
||||
* It takes into account inline editors as well.
|
||||
* If you are interested only in standalone editors then use `MonacoEditor.getCurrent(EditorManager)`
|
||||
*/
|
||||
get current(): MonacoEditor | undefined {
|
||||
return this._current;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@inject(MonacoToProtocolConverter) protected readonly m2p: MonacoToProtocolConverter,
|
||||
@inject(ProtocolToMonacoConverter) protected readonly p2m: ProtocolToMonacoConverter,
|
||||
@inject(MonacoWorkspace) protected readonly workspace: MonacoWorkspace,
|
||||
@inject(EditorPreferences) protected readonly editorPreferences: EditorPreferences,
|
||||
@inject(MonacoDiffNavigatorFactory) protected readonly diffNavigatorFactory: MonacoDiffNavigatorFactory,
|
||||
) {
|
||||
}
|
||||
|
||||
protected async getModel(uri: URI, toDispose: DisposableCollection): Promise<MonacoEditorModel> {
|
||||
const reference = await StandaloneServices.get(ITextModelService).createModelReference(monaco.Uri.from(uri.toComponents())) as IReference<MonacoEditorModel>;
|
||||
// if document is invalid makes sure that all events from underlying resource are processed before throwing invalid model
|
||||
if (!reference.object.valid) {
|
||||
await reference.object.sync();
|
||||
}
|
||||
if (!reference.object.valid) {
|
||||
reference.dispose();
|
||||
throw Object.assign(new Error(`'${uri.toString()}' is invalid`), { code: 'MODEL_IS_INVALID' });
|
||||
}
|
||||
toDispose.push(reference);
|
||||
return reference.object;
|
||||
}
|
||||
|
||||
async get(uri: URI): Promise<MonacoEditor> {
|
||||
await this.editorPreferences.ready;
|
||||
return this.doCreateEditor(uri, (override, toDispose) => this.createEditor(uri, override, toDispose));
|
||||
}
|
||||
|
||||
protected async doCreateEditor<T extends MonacoEditor | SimpleMonacoEditor>(uri: URI, factory: (
|
||||
override: EditorServiceOverrides, toDispose: DisposableCollection) => Promise<T>
|
||||
): Promise<T> {
|
||||
const domNode = document.createElement('div');
|
||||
const contextKeyService = StandaloneServices.get(IContextKeyService).createScoped(domNode);
|
||||
StandaloneServices.get(IOpenerService).registerOpener({
|
||||
open: (u, options) => this.interceptOpen(u, options)
|
||||
});
|
||||
const overrides: EditorServiceOverrides = [
|
||||
[IContextKeyService, contextKeyService],
|
||||
];
|
||||
const toDispose = new DisposableCollection();
|
||||
const editor = await factory(overrides, toDispose);
|
||||
editor.onDispose(() => toDispose.dispose());
|
||||
if (editor instanceof MonacoEditor) {
|
||||
|
||||
this.injectKeybindingResolver(editor);
|
||||
|
||||
toDispose.push(editor.onFocusChanged(focused => {
|
||||
if (focused) {
|
||||
this._current = editor;
|
||||
}
|
||||
}));
|
||||
toDispose.push(Disposable.create(() => {
|
||||
if (this._current === editor) {
|
||||
this._current = undefined;
|
||||
}
|
||||
}));
|
||||
}
|
||||
return editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept internal Monaco open calls and delegate to OpenerService.
|
||||
*/
|
||||
protected async interceptOpen(monacoUri: monaco.Uri | string, monacoOptions?: OpenInternalOptions | OpenExternalOptions): Promise<boolean> {
|
||||
let options = undefined;
|
||||
if (monacoOptions) {
|
||||
if ('openToSide' in monacoOptions && monacoOptions.openToSide) {
|
||||
options = Object.assign(options || {}, <WidgetOpenerOptions>{
|
||||
widgetOptions: {
|
||||
mode: 'split-right'
|
||||
}
|
||||
});
|
||||
}
|
||||
if ('openExternal' in monacoOptions && monacoOptions.openExternal) {
|
||||
options = Object.assign(options || {}, <HttpOpenHandlerOptions>{
|
||||
openExternal: true
|
||||
});
|
||||
}
|
||||
}
|
||||
const uri = new URI(monacoUri.toString());
|
||||
try {
|
||||
await open(this.openerService, uri, options);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(`Fail to open '${uri.toString()}':`, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected injectKeybindingResolver(editor: MonacoEditor): void {
|
||||
const keybindingService = StandaloneServices.get(IKeybindingService);
|
||||
keybindingService.resolveKeybinding = keybinding => [new MonacoResolvedKeybinding(MonacoResolvedKeybinding.keySequence(keybinding.chords), this.keybindingRegistry)];
|
||||
keybindingService.resolveKeyboardEvent = keyboardEvent => {
|
||||
const keybinding = new KeyCodeChord(
|
||||
keyboardEvent.ctrlKey,
|
||||
keyboardEvent.shiftKey,
|
||||
keyboardEvent.altKey,
|
||||
keyboardEvent.metaKey,
|
||||
keyboardEvent.keyCode
|
||||
);
|
||||
return new MonacoResolvedKeybinding(MonacoResolvedKeybinding.keySequence([keybinding]), this.keybindingRegistry);
|
||||
};
|
||||
}
|
||||
|
||||
protected createEditor(uri: URI, override: EditorServiceOverrides, toDispose: DisposableCollection): Promise<MonacoEditor> {
|
||||
if (DiffUris.isDiffUri(uri)) {
|
||||
return this.createMonacoDiffEditor(uri, override, toDispose);
|
||||
}
|
||||
return this.createMonacoEditor(uri, override, toDispose);
|
||||
}
|
||||
|
||||
protected get preferencePrefixes(): string[] {
|
||||
return ['editor.'];
|
||||
}
|
||||
async createMonacoEditor(uri: URI, override: EditorServiceOverrides, toDispose: DisposableCollection): Promise<MonacoEditor> {
|
||||
const model = await this.getModel(uri, toDispose);
|
||||
const options = this.createMonacoEditorOptions(model);
|
||||
const factory = this.factories.getContributions().find(({ scheme }) => uri.scheme === scheme);
|
||||
const editor = factory
|
||||
? await factory.create(model, options, override)
|
||||
: await MonacoEditor.create(uri, model, document.createElement('div'), this.services, options, override);
|
||||
toDispose.push(this.editorPreferences.onPreferenceChanged(event => {
|
||||
if (event.affects(uri.toString(), model.languageId)) {
|
||||
this.updateMonacoEditorOptions(editor, event);
|
||||
}
|
||||
}));
|
||||
toDispose.push(editor.onLanguageChanged(() => this.updateMonacoEditorOptions(editor)));
|
||||
toDispose.push(editor.onDidChangeReadOnly(() => this.updateReadOnlyMessage(options, model.readOnly)));
|
||||
toDispose.push(editor.document.onModelWillSaveModel(e => this.runSaveParticipants(editor, e.token, e.options)));
|
||||
return editor;
|
||||
}
|
||||
|
||||
protected updateReadOnlyMessage(options: MonacoEditor.IOptions, readOnly: boolean | MarkdownString): void {
|
||||
options.readOnlyMessage = MarkdownString.is(readOnly) ? readOnly : undefined;
|
||||
}
|
||||
|
||||
protected createMonacoEditorOptions(model: MonacoEditorModel): MonacoEditor.IOptions {
|
||||
const options = this.createOptions(this.preferencePrefixes, model.uri, model.languageId);
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
options.model = null; // explicitly set to null to avoid creating an initial model automatically
|
||||
options.readOnly = model.readOnly;
|
||||
this.updateReadOnlyMessage(options, model.readOnly);
|
||||
options.lineNumbersMinChars = model.lineNumbersMinChars;
|
||||
return options;
|
||||
}
|
||||
protected updateMonacoEditorOptions(editor: MonacoEditor, event?: EditorPreferenceChange): void {
|
||||
if (event) {
|
||||
const preferenceName = event.preferenceName;
|
||||
const overrideIdentifier = editor.document.languageId;
|
||||
const newValue = this.editorPreferences.get({ preferenceName, overrideIdentifier }, undefined, editor.uri.toString());
|
||||
editor.getControl().updateOptions(this.setOption(preferenceName, newValue, this.preferencePrefixes));
|
||||
} else {
|
||||
const options = this.createMonacoEditorOptions(editor.document);
|
||||
delete options.model;
|
||||
editor.getControl().updateOptions(options);
|
||||
}
|
||||
}
|
||||
|
||||
protected get diffPreferencePrefixes(): string[] {
|
||||
return [...this.preferencePrefixes, 'diffEditor.'];
|
||||
}
|
||||
protected async createMonacoDiffEditor(uri: URI, override: EditorServiceOverrides, toDispose: DisposableCollection): Promise<MonacoDiffEditor> {
|
||||
const [original, modified] = DiffUris.decode(uri);
|
||||
|
||||
const [originalModel, modifiedModel] = await Promise.all([this.getModel(original, toDispose), this.getModel(modified, toDispose)]);
|
||||
|
||||
const options = this.createMonacoDiffEditorOptions(originalModel, modifiedModel);
|
||||
const editor = new MonacoDiffEditor(
|
||||
uri,
|
||||
document.createElement('div'),
|
||||
originalModel, modifiedModel,
|
||||
this.services,
|
||||
this.diffNavigatorFactory,
|
||||
options,
|
||||
override);
|
||||
toDispose.push(this.editorPreferences.onPreferenceChanged(event => {
|
||||
const originalFileUri = original.withoutQuery().withScheme('file').toString();
|
||||
if (event.affects(originalFileUri, editor.document.languageId)) {
|
||||
this.updateMonacoDiffEditorOptions(editor, event, originalFileUri);
|
||||
}
|
||||
}));
|
||||
toDispose.push(editor.onLanguageChanged(() => this.updateMonacoDiffEditorOptions(editor)));
|
||||
return editor;
|
||||
}
|
||||
protected createMonacoDiffEditorOptions(original: MonacoEditorModel, modified: MonacoEditorModel): MonacoDiffEditor.IOptions {
|
||||
const options = this.createOptions(this.diffPreferencePrefixes, modified.uri, modified.languageId);
|
||||
options.originalEditable = !original.readOnly;
|
||||
options.readOnly = modified.readOnly;
|
||||
options.readOnlyMessage = MarkdownString.is(modified.readOnly) ? modified.readOnly : undefined;
|
||||
return options;
|
||||
}
|
||||
protected updateMonacoDiffEditorOptions(editor: MonacoDiffEditor, event?: EditorPreferenceChange, resourceUri?: string): void {
|
||||
if (event) {
|
||||
const preferenceName = event.preferenceName;
|
||||
const overrideIdentifier = editor.document.languageId;
|
||||
const newValue = this.editorPreferences.get({ preferenceName, overrideIdentifier }, undefined, resourceUri);
|
||||
editor.diffEditor.updateOptions(this.setOption(preferenceName, newValue, this.diffPreferencePrefixes));
|
||||
} else {
|
||||
const options = this.createMonacoDiffEditorOptions(editor.originalModel, editor.modifiedModel);
|
||||
editor.diffEditor.updateOptions(options);
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated always pass a language as an overrideIdentifier */
|
||||
protected createOptions(prefixes: string[], uri: string): Record<string, any>;
|
||||
protected createOptions(prefixes: string[], uri: string, overrideIdentifier: string): Record<string, any>;
|
||||
protected createOptions(prefixes: string[], uri: string, overrideIdentifier?: string): Record<string, any> {
|
||||
const flat: Record<string, any> = {};
|
||||
for (const preferenceName of Object.keys(this.editorPreferences)) {
|
||||
flat[preferenceName] = (<any>this.editorPreferences).get({ preferenceName, overrideIdentifier }, undefined, uri);
|
||||
}
|
||||
return Object.entries(flat).reduce((tree, [preferenceName, value]) => this.setOption(preferenceName, deepClone(value), prefixes, tree), {});
|
||||
}
|
||||
|
||||
protected setOption(preferenceName: string, value: any, prefixes: string[], options: Record<string, any> = {}): {
|
||||
[name: string]: any;
|
||||
} {
|
||||
const optionName = this.toOptionName(preferenceName, prefixes);
|
||||
this.doSetOption(options, value, optionName.split('.'));
|
||||
return options;
|
||||
}
|
||||
protected toOptionName(preferenceName: string, prefixes: string[]): string {
|
||||
for (const prefix of prefixes) {
|
||||
if (preferenceName.startsWith(prefix)) {
|
||||
return preferenceName.substring(prefix.length);
|
||||
}
|
||||
}
|
||||
return preferenceName;
|
||||
}
|
||||
protected doSetOption(obj: Record<string, any>, value: any, names: string[]): void {
|
||||
for (let i = 0; i < names.length - 1; i++) {
|
||||
const name = names[i];
|
||||
if (obj[name] === undefined) {
|
||||
obj = obj[name] = {};
|
||||
} else if (typeof obj[name] !== 'object' || obj[name] === null) { // eslint-disable-line no-null/no-null
|
||||
console.warn(`Preference (diff)editor.${names.join('.')} conflicts with another preference name.`);
|
||||
obj = obj[name] = {};
|
||||
} else {
|
||||
obj = obj[name];
|
||||
}
|
||||
}
|
||||
obj[names[names.length - 1]] = value;
|
||||
}
|
||||
|
||||
getDiffNavigator(editor: TextEditor): DiffNavigator {
|
||||
if (editor instanceof MonacoDiffEditor) {
|
||||
return editor.diffNavigator;
|
||||
}
|
||||
return MonacoDiffNavigatorFactory.nullNavigator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of the standard MonacoEditor with a StandaloneCodeEditor as its Monaco delegate.
|
||||
* Among other differences, these editors execute basic actions like typing or deletion via commands that may be overridden by extensions.
|
||||
* @deprecated Most use cases for inline editors should be served by `createSimpleInline` instead.
|
||||
*/
|
||||
async createInline(uri: URI, node: HTMLElement, options?: MonacoEditor.IOptions): Promise<MonacoEditor> {
|
||||
return this.doCreateEditor(uri, async (override, toDispose) => {
|
||||
const overrides = override ? Array.from(override) : [];
|
||||
overrides.push([IContextMenuService, { showContextMenu: () => {/** no op! */ } }]);
|
||||
const document = await this.getModel(uri, toDispose);
|
||||
document.suppressOpenEditorWhenDirty = true;
|
||||
const model = (await document.load()).textEditorModel;
|
||||
return await MonacoEditor.create(
|
||||
uri,
|
||||
document,
|
||||
node,
|
||||
this.services,
|
||||
Object.assign({
|
||||
model,
|
||||
autoSizing: false,
|
||||
minHeight: 1,
|
||||
maxHeight: 1
|
||||
}, MonacoEditorProvider.inlineOptions, options),
|
||||
overrides
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of the standard MonacoEditor with a CodeEditorWidget as its Monaco delegate.
|
||||
* In addition to the service customizability of the StandaloneCodeEditor,This editor allows greater customization the editor contributions active in the widget.
|
||||
* See {@link ICodeEditorWidgetOptions.contributions}.
|
||||
*/
|
||||
async createSimpleInline(uri: URI, node: HTMLElement, options?: MonacoEditor.IOptions, widgetOptions?: ICodeEditorWidgetOptions): Promise<SimpleMonacoEditor> {
|
||||
return this.doCreateEditor(uri, async (override, toDispose) => {
|
||||
const overrides = override ? Array.from(override) : [];
|
||||
overrides.push([IContextMenuService, { showContextMenu: () => { /** no op! */ } }]);
|
||||
const document = await this.getModel(uri, toDispose);
|
||||
document.suppressOpenEditorWhenDirty = true;
|
||||
const model = (await document.load()).textEditorModel;
|
||||
const baseOptions: Partial<MonacoEditor.IOptions> = {
|
||||
model,
|
||||
autoSizing: false,
|
||||
minHeight: 1,
|
||||
maxHeight: 1
|
||||
};
|
||||
const editorOptions = {
|
||||
...baseOptions,
|
||||
...MonacoEditorProvider.inlineOptions,
|
||||
...options
|
||||
};
|
||||
return new SimpleMonacoEditor(
|
||||
uri,
|
||||
document,
|
||||
node,
|
||||
this.services,
|
||||
editorOptions,
|
||||
overrides,
|
||||
{ isSimpleWidget: true, ...widgetOptions }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
static inlineOptions: monaco.editor.IEditorConstructionOptions = {
|
||||
wordWrap: 'on',
|
||||
overviewRulerLanes: 0,
|
||||
glyphMargin: false,
|
||||
lineNumbers: 'off',
|
||||
folding: false,
|
||||
selectOnLineNumbers: false,
|
||||
hideCursorInOverviewRuler: true,
|
||||
selectionHighlight: false,
|
||||
scrollbar: {
|
||||
horizontal: 'hidden'
|
||||
},
|
||||
lineDecorationsWidth: 0,
|
||||
overviewRulerBorder: false,
|
||||
scrollBeyondLastLine: false,
|
||||
renderLineHighlight: 'none',
|
||||
fixedOverflowWidgets: true,
|
||||
acceptSuggestionOnEnter: 'smart',
|
||||
minimap: {
|
||||
enabled: false
|
||||
}
|
||||
};
|
||||
|
||||
async createEmbeddedDiffEditor(parentEditor: MonacoEditor, node: HTMLElement, originalUri: URI, modifiedUri: URI = parentEditor.uri,
|
||||
options?: MonacoDiffEditor.IOptions): Promise<MonacoDiffEditor> {
|
||||
options = {
|
||||
scrollBeyondLastLine: true,
|
||||
overviewRulerLanes: 2,
|
||||
fixedOverflowWidgets: true,
|
||||
minimap: { enabled: false },
|
||||
renderSideBySide: false,
|
||||
readOnly: false,
|
||||
renderIndicators: false,
|
||||
diffAlgorithm: 'advanced',
|
||||
stickyScroll: { enabled: false },
|
||||
...options,
|
||||
scrollbar: {
|
||||
verticalScrollbarSize: 14,
|
||||
horizontal: 'auto',
|
||||
useShadows: true,
|
||||
verticalHasArrows: false,
|
||||
horizontalHasArrows: false,
|
||||
...options?.scrollbar
|
||||
}
|
||||
};
|
||||
const uri = DiffUris.encode(originalUri, modifiedUri);
|
||||
return await this.doCreateEditor(uri, async (override, toDispose) =>
|
||||
new MonacoDiffEditor(
|
||||
uri,
|
||||
node,
|
||||
await this.getModel(originalUri, toDispose),
|
||||
await this.getModel(modifiedUri, toDispose),
|
||||
this.services,
|
||||
this.diffNavigatorFactory,
|
||||
options,
|
||||
override,
|
||||
parentEditor
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
init(): void {
|
||||
this.saveParticipants = this.saveProviderContributions.getContributions().slice().sort((left, right) => left.order - right.order);
|
||||
this.registerSaveParticipant({
|
||||
order: 1000,
|
||||
applyChangesOnSave: (
|
||||
editor: MonacoEditor,
|
||||
cancellationToken: monaco.CancellationToken,
|
||||
options: SaveOptions): Promise<void> => this.formatOnSave(editor, editor.document, cancellationToken, options)
|
||||
});
|
||||
}
|
||||
|
||||
registerSaveParticipant(saveParticipant: SaveParticipant): Disposable {
|
||||
if (this.saveParticipants.find(value => value === saveParticipant)) {
|
||||
throw new Error('Save participant already registered');
|
||||
}
|
||||
this.saveParticipants.push(saveParticipant);
|
||||
this.saveParticipants.sort((left, right) => left.order - right.order);
|
||||
return Disposable.create(() => {
|
||||
const index = this.saveParticipants.indexOf(saveParticipant);
|
||||
if (index >= 0) {
|
||||
this.saveParticipants.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected shouldFormat(model: MonacoEditorModel, options: SaveOptions): boolean {
|
||||
if (options.saveReason !== TextDocumentSaveReason.Manual) {
|
||||
return false;
|
||||
}
|
||||
switch (options.formatType) {
|
||||
case FormatType.ON: return true;
|
||||
case FormatType.OFF: return false;
|
||||
case FormatType.DIRTY: return model.dirty;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async runSaveParticipants(editor: MonacoEditor, cancellationToken: CancellationToken, options?: SaveOptions): Promise<void> {
|
||||
const initialState = editor.document.createSnapshot();
|
||||
for (const participant of this.saveParticipants) {
|
||||
if (cancellationToken.isCancellationRequested) {
|
||||
break;
|
||||
}
|
||||
const snapshot = editor.document.createSnapshot();
|
||||
try {
|
||||
await participant.applyChangesOnSave(editor, cancellationToken, options);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
editor.document.applySnapshot(snapshot);
|
||||
}
|
||||
}
|
||||
if (cancellationToken.isCancellationRequested) {
|
||||
editor.document.applySnapshot(initialState);
|
||||
}
|
||||
}
|
||||
|
||||
protected async formatOnSave(
|
||||
editor: MonacoEditor,
|
||||
model: MonacoEditorModel,
|
||||
cancellationToken: CancellationToken,
|
||||
options: SaveOptions): Promise<void> {
|
||||
if (!this.shouldFormat(model, options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const overrideIdentifier = model.languageId;
|
||||
const uri = model.uri.toString();
|
||||
const formatOnSave = this.editorPreferences.get({ preferenceName: 'editor.formatOnSave', overrideIdentifier }, undefined, uri);
|
||||
if (formatOnSave) {
|
||||
const formatOnSaveTimeout = this.editorPreferences.get({ preferenceName: 'editor.formatOnSaveTimeout', overrideIdentifier }, undefined, uri)!;
|
||||
await Promise.race([
|
||||
timeoutReject(formatOnSaveTimeout, `Aborted format on save after ${formatOnSaveTimeout}ms`),
|
||||
await editor.runAction('editor.action.formatDocument')
|
||||
]);
|
||||
}
|
||||
const shouldRemoveWhiteSpace = this.filePreferences.get({ preferenceName: 'files.trimTrailingWhitespace', overrideIdentifier }, undefined, uri);
|
||||
if (shouldRemoveWhiteSpace) {
|
||||
await editor.runAction('editor.action.trimTrailingWhitespace');
|
||||
}
|
||||
const shouldInsertFinalNewline = this.filePreferences.get({ preferenceName: 'files.insertFinalNewline', overrideIdentifier }, undefined, uri);
|
||||
if (shouldInsertFinalNewline) {
|
||||
this.insertFinalNewline(model);
|
||||
}
|
||||
}
|
||||
|
||||
protected insertFinalNewline(editorModel: MonacoEditorModel): void {
|
||||
insertFinalNewline(editorModel);
|
||||
}
|
||||
}
|
||||
169
packages/monaco/src/browser/monaco-editor-service.ts
Normal file
169
packages/monaco/src/browser/monaco-editor-service.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject, decorate, named } from '@theia/core/shared/inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { OpenerService, open, WidgetOpenMode, ApplicationShell } from '@theia/core/lib/browser';
|
||||
import { EditorWidget, EditorOpenerOptions, EditorManager, CustomEditorWidget } from '@theia/editor/lib/browser';
|
||||
import { MonacoEditor } from './monaco-editor';
|
||||
import { MonacoToProtocolConverter } from './monaco-to-protocol-converter';
|
||||
import { MonacoEditorModel } from './monaco-editor-model';
|
||||
import { IResourceEditorInput, ITextResourceEditorInput } from '@theia/monaco-editor-core/esm/vs/platform/editor/common/editor';
|
||||
import { StandaloneCodeEditorService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneCodeEditorService';
|
||||
import { StandaloneCodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneCodeEditor';
|
||||
import { ICodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
|
||||
import { IContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';
|
||||
import { IThemeService } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService';
|
||||
import { ContributionProvider, PreferenceService } from '@theia/core';
|
||||
|
||||
decorate(injectable(), StandaloneCodeEditorService);
|
||||
|
||||
export const VSCodeContextKeyService = Symbol('VSCodeContextKeyService');
|
||||
export const VSCodeThemeService = Symbol('VSCodeThemeService');
|
||||
|
||||
export const MonacoEditorServiceFactory = Symbol('MonacoEditorServiceFactory');
|
||||
export type MonacoEditorServiceFactoryType = (contextKeyService: IContextKeyService, themeService: IThemeService) => MonacoEditorService;
|
||||
|
||||
/**
|
||||
* contribution provider to extend the active editor handling to other editor types than just standalone editor widgets.
|
||||
*/
|
||||
export const ActiveMonacoEditorContribution = Symbol('ActiveMonacoEditorContribution');
|
||||
export interface ActiveMonacoEditorContribution {
|
||||
getActiveEditor(): ICodeEditor | undefined;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MonacoEditorService extends StandaloneCodeEditorService {
|
||||
|
||||
public static readonly ENABLE_PREVIEW_PREFERENCE: string = 'editor.enablePreview';
|
||||
|
||||
@inject(OpenerService)
|
||||
protected readonly openerService: OpenerService;
|
||||
|
||||
@inject(MonacoToProtocolConverter)
|
||||
protected readonly m2p: MonacoToProtocolConverter;
|
||||
|
||||
@inject(ApplicationShell)
|
||||
protected readonly shell: ApplicationShell;
|
||||
|
||||
@inject(EditorManager)
|
||||
protected readonly editors: EditorManager;
|
||||
|
||||
@inject(PreferenceService)
|
||||
protected readonly preferencesService: PreferenceService;
|
||||
|
||||
@inject(ContributionProvider) @named(ActiveMonacoEditorContribution)
|
||||
protected readonly activeMonacoEditorContribution: ContributionProvider<ActiveMonacoEditorContribution>;
|
||||
|
||||
constructor(@inject(VSCodeContextKeyService) contextKeyService: IContextKeyService, @inject(VSCodeThemeService) themeService: IThemeService) {
|
||||
super(contextKeyService, themeService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Monaco active editor is either focused or last focused editor.
|
||||
*/
|
||||
override getActiveCodeEditor(): ICodeEditor | null {
|
||||
let editor = MonacoEditor.getCurrent(this.editors);
|
||||
if (!editor && CustomEditorWidget.is(this.shell.activeWidget)) {
|
||||
const model = this.shell.activeWidget.modelRef.object;
|
||||
if (model?.editorTextModel instanceof MonacoEditorModel) {
|
||||
editor = MonacoEditor.findByDocument(this.editors, model.editorTextModel)[0];
|
||||
}
|
||||
}
|
||||
const candidate = editor?.getControl();
|
||||
// Since we extend a private super class, we have to check that the thing that matches the public interface also matches the private expectations the superclass.
|
||||
if (candidate instanceof StandaloneCodeEditor) {
|
||||
return candidate;
|
||||
}
|
||||
for (const activeEditorProvider of this.activeMonacoEditorContribution.getContributions()) {
|
||||
const activeEditor = activeEditorProvider.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
return activeEditor;
|
||||
}
|
||||
}
|
||||
/* eslint-disable-next-line no-null/no-null */
|
||||
return null;
|
||||
}
|
||||
|
||||
override async openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
|
||||
const uri = new URI(input.resource.toString());
|
||||
const openerOptions = this.createEditorOpenerOptions(input, source, sideBySide);
|
||||
const widget = await open(this.openerService, uri, openerOptions);
|
||||
const editorWidget = await this.findEditorWidgetByUri(widget, uri.toString());
|
||||
const candidate = MonacoEditor.get(editorWidget)?.getControl();
|
||||
// Since we extend a private super class, we have to check that the thing that matches the public interface also matches the private expectations the superclass.
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
return candidate instanceof StandaloneCodeEditor ? candidate : null;
|
||||
}
|
||||
|
||||
protected async findEditorWidgetByUri(widget: object | undefined, uriAsString: string): Promise<EditorWidget | undefined> {
|
||||
if (widget instanceof EditorWidget) {
|
||||
if (widget.editor.uri.toString() === uriAsString) {
|
||||
return widget;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (ApplicationShell.TrackableWidgetProvider.is(widget)) {
|
||||
for (const childWidget of widget.getTrackableWidgets()) {
|
||||
const editorWidget = await this.findEditorWidgetByUri(childWidget, uriAsString);
|
||||
if (editorWidget) {
|
||||
return editorWidget;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected createEditorOpenerOptions(input: IResourceEditorInput | ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): EditorOpenerOptions {
|
||||
const mode = this.getEditorOpenMode(input);
|
||||
const widgetOptions = this.getWidgetOptions(source, sideBySide);
|
||||
const selection = this.getSelection(input);
|
||||
const preview = !!this.preferencesService.get<boolean>(MonacoEditorService.ENABLE_PREVIEW_PREFERENCE, false);
|
||||
return { mode, widgetOptions, preview, selection };
|
||||
}
|
||||
|
||||
protected getSelection(input: IResourceEditorInput | ITextResourceEditorInput): EditorOpenerOptions['selection'] {
|
||||
if ('options' in input && input.options && 'selection' in input.options) {
|
||||
return this.m2p.asRange(input.options.selection);
|
||||
}
|
||||
}
|
||||
|
||||
protected getEditorOpenMode(input: IResourceEditorInput): WidgetOpenMode {
|
||||
const options = {
|
||||
preserveFocus: false,
|
||||
revealIfVisible: true,
|
||||
...input.options
|
||||
};
|
||||
if (options.preserveFocus) {
|
||||
return 'reveal';
|
||||
}
|
||||
return options.revealIfVisible ? 'activate' : 'open';
|
||||
}
|
||||
|
||||
protected getWidgetOptions(source: ICodeEditor | null, sideBySide?: boolean): ApplicationShell.WidgetOptions | undefined {
|
||||
const ref = MonacoEditor.getWidgetFor(this.editors, source);
|
||||
if (!ref) {
|
||||
return undefined;
|
||||
}
|
||||
const area = (ref && this.shell.getAreaFor(ref)) || 'main';
|
||||
const mode = ref && sideBySide ? 'split-right' : undefined;
|
||||
if (area === 'secondaryWindow') {
|
||||
return { area: 'main', mode };
|
||||
}
|
||||
return { area, mode, ref };
|
||||
}
|
||||
|
||||
}
|
||||
250
packages/monaco/src/browser/monaco-editor-zone-widget.ts
Normal file
250
packages/monaco/src/browser/monaco-editor-zone-widget.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation and others. All rights reserved.
|
||||
* Licensed under the MIT License. See https://github.com/Microsoft/vscode/blob/master/LICENSE.txt for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, DisposableCollection, Event, Emitter } from '@theia/core';
|
||||
import { TrackedRangeStickiness } from '@theia/editor/lib/browser';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
|
||||
export interface MonacoEditorViewZone extends monaco.editor.IViewZone {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export class MonacoEditorZoneWidget implements Disposable {
|
||||
|
||||
private arrow: Arrow | undefined;
|
||||
|
||||
readonly zoneNode = document.createElement('div');
|
||||
readonly containerNode = document.createElement('div');
|
||||
|
||||
protected readonly onDidLayoutChangeEmitter = new Emitter<monaco.editor.IDimension>();
|
||||
readonly onDidLayoutChange: Event<monaco.editor.IDimension> = this.onDidLayoutChangeEmitter.event;
|
||||
|
||||
protected viewZone: MonacoEditorViewZone | undefined;
|
||||
|
||||
protected readonly toHide = new DisposableCollection();
|
||||
|
||||
protected readonly toDispose = new DisposableCollection(
|
||||
this.onDidLayoutChangeEmitter,
|
||||
this.toHide
|
||||
);
|
||||
|
||||
editor: monaco.editor.ICodeEditor;
|
||||
|
||||
constructor(
|
||||
editorInstance: monaco.editor.ICodeEditor, readonly showArrow: boolean = true
|
||||
) {
|
||||
this.editor = editorInstance;
|
||||
this.zoneNode.classList.add('zone-widget');
|
||||
this.containerNode.classList.add('zone-widget-container');
|
||||
this.zoneNode.appendChild(this.containerNode);
|
||||
this.updateWidth();
|
||||
this.toDispose.push(this.editor.onDidLayoutChange(info => this.updateWidth(info)));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.toDispose.dispose();
|
||||
this.hide();
|
||||
}
|
||||
|
||||
protected _options: MonacoEditorZoneWidget.Options | undefined;
|
||||
get options(): MonacoEditorZoneWidget.Options | undefined {
|
||||
return this.viewZone ? this._options : undefined;
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.toHide.dispose();
|
||||
}
|
||||
|
||||
show(options: MonacoEditorZoneWidget.Options): void {
|
||||
let { afterLineNumber, afterColumn, heightInLines } = this._options = { showFrame: true, ...options };
|
||||
const lineHeight = this.editor.getOption(monaco.editor.EditorOption.lineHeight);
|
||||
// adjust heightInLines to viewport
|
||||
const maxHeightInLines = Math.max(12, (this.editor.getLayoutInfo().height / lineHeight) * 0.8);
|
||||
heightInLines = Math.min(heightInLines, maxHeightInLines);
|
||||
let arrowHeight = 0;
|
||||
this.toHide.dispose();
|
||||
this.editor.changeViewZones(accessor => {
|
||||
this.zoneNode.style.top = '-1000px';
|
||||
const domNode = document.createElement('div');
|
||||
domNode.style.overflow = 'hidden';
|
||||
const zone: monaco.editor.IViewZone = {
|
||||
domNode,
|
||||
afterLineNumber,
|
||||
afterColumn,
|
||||
heightInLines,
|
||||
onDomNodeTop: zoneTop => this.updateTop(zoneTop),
|
||||
onComputedHeight: zoneHeight => this.updateHeight(zoneHeight)
|
||||
};
|
||||
this.viewZone = Object.assign(zone, {
|
||||
id: accessor.addZone(zone)
|
||||
});
|
||||
const id = this.viewZone.id;
|
||||
this.toHide.push(Disposable.create(() => {
|
||||
this.editor.changeViewZones(a => a.removeZone(id));
|
||||
this.viewZone = undefined;
|
||||
}));
|
||||
if (this.showArrow) {
|
||||
this.arrow = new Arrow(this.editor);
|
||||
arrowHeight = Math.round(lineHeight / 3);
|
||||
this.arrow.height = arrowHeight;
|
||||
this.arrow.show({ lineNumber: options.afterLineNumber, column: 0 });
|
||||
|
||||
this.toHide.push(this.arrow);
|
||||
}
|
||||
const widget: monaco.editor.IOverlayWidget = {
|
||||
getId: () => 'editor-zone-widget-' + id,
|
||||
getDomNode: () => this.zoneNode,
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
getPosition: () => null!
|
||||
};
|
||||
this.editor.addOverlayWidget(widget);
|
||||
this.toHide.push(Disposable.create(() => this.editor.removeOverlayWidget(widget)));
|
||||
});
|
||||
|
||||
this.containerNode.style.overflow = 'hidden';
|
||||
this.updateContainerHeight(heightInLines * lineHeight);
|
||||
|
||||
const model = this.editor.getModel();
|
||||
if (model) {
|
||||
const revealLineNumber = Math.min(model.getLineCount(), Math.max(1, afterLineNumber + 1));
|
||||
this.editor.revealLine(revealLineNumber, monaco.editor.ScrollType.Smooth);
|
||||
}
|
||||
}
|
||||
|
||||
layout(heightInLines: number): void {
|
||||
if (this.viewZone && this.viewZone.heightInLines !== heightInLines) {
|
||||
this.viewZone.heightInLines = heightInLines;
|
||||
const id = this.viewZone.id;
|
||||
this.editor.changeViewZones(accessor => accessor.layoutZone(id));
|
||||
}
|
||||
}
|
||||
|
||||
protected updateTop(top: number): void {
|
||||
this.zoneNode.style.top = top + (this.showArrow ? 6 : 0) + 'px';
|
||||
}
|
||||
protected updateHeight(zoneHeight: number): void {
|
||||
this.zoneNode.style.height = zoneHeight + 'px';
|
||||
this.updateContainerHeight(zoneHeight);
|
||||
}
|
||||
protected updateContainerHeight(zoneHeight: number): void {
|
||||
const { frameWidth, height } = this.computeContainerHeight(zoneHeight);
|
||||
this.containerNode.style.height = height + 'px';
|
||||
this.containerNode.style.borderTopWidth = frameWidth + 'px';
|
||||
this.containerNode.style.borderBottomWidth = frameWidth + 'px';
|
||||
const width = this.computeWidth();
|
||||
this.onDidLayoutChangeEmitter.fire({ height, width });
|
||||
}
|
||||
protected computeContainerHeight(zoneHeight: number): {
|
||||
height: number,
|
||||
frameWidth: number
|
||||
} {
|
||||
const lineHeight = this.editor.getOption(monaco.editor.EditorOption.lineHeight);
|
||||
const frameWidth = this._options && this._options.frameWidth;
|
||||
const frameThickness = this._options && this._options.showFrame ? Math.round(lineHeight / 9) : 0;
|
||||
return {
|
||||
frameWidth: frameWidth !== undefined ? frameWidth : frameThickness,
|
||||
height: zoneHeight - 2 * frameThickness
|
||||
};
|
||||
}
|
||||
|
||||
protected updateWidth(info: monaco.editor.EditorLayoutInfo = this.editor.getLayoutInfo()): void {
|
||||
const width = this.computeWidth(info);
|
||||
this.zoneNode.style.width = width + 'px';
|
||||
this.zoneNode.style.left = this.computeLeft(info) + 'px';
|
||||
}
|
||||
protected computeWidth(info: monaco.editor.EditorLayoutInfo = this.editor.getLayoutInfo()): number {
|
||||
return info.width - info.minimap.minimapWidth - info.verticalScrollbarWidth;
|
||||
}
|
||||
protected computeLeft(info: monaco.editor.EditorLayoutInfo = this.editor.getLayoutInfo()): number {
|
||||
// If minimap is to the left, we move beyond it
|
||||
if (info.minimap.minimapWidth > 0 && info.minimap.minimapLeft === 0) {
|
||||
return info.minimap.minimapWidth;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class IdGenerator {
|
||||
private lastId: number;
|
||||
constructor(private prefix: string) {
|
||||
this.lastId = 0;
|
||||
}
|
||||
|
||||
nextId(): string {
|
||||
return this.prefix + (++this.lastId);
|
||||
}
|
||||
}
|
||||
|
||||
class Arrow implements Disposable {
|
||||
|
||||
private readonly idGenerator = new IdGenerator('.arrow-decoration-');
|
||||
|
||||
private readonly ruleName = this.idGenerator.nextId();
|
||||
private decorations: string[] = [];
|
||||
private _height: number = -1;
|
||||
|
||||
constructor(
|
||||
private readonly _editor: monaco.editor.ICodeEditor
|
||||
) { }
|
||||
|
||||
dispose(): void {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
set height(value: number) {
|
||||
if (this._height !== value) {
|
||||
this._height = value;
|
||||
this._updateStyle();
|
||||
}
|
||||
}
|
||||
|
||||
private _updateStyle(): void {
|
||||
const style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
style.media = 'screen';
|
||||
document.getElementsByTagName('head')[0].appendChild(style);
|
||||
const selector = `.monaco-editor ${this.ruleName}`;
|
||||
const cssText = `border-style: solid; border-color: transparent transparent var(--theia-peekView-border); border-width:
|
||||
${this._height}px; bottom: -${this._height}px; margin-left: -${this._height}px; `;
|
||||
(<CSSStyleSheet>style.sheet).insertRule(selector + '{' + cssText + '}', 0);
|
||||
}
|
||||
|
||||
show(where: monaco.IPosition): void {
|
||||
this.decorations = this._editor.deltaDecorations(
|
||||
this.decorations,
|
||||
[{ range: monaco.Range.fromPositions(where), options: { className: this.ruleName, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }]
|
||||
);
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this._editor.deltaDecorations(this.decorations, []);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace MonacoEditorZoneWidget {
|
||||
export interface Options {
|
||||
afterLineNumber: number,
|
||||
afterColumn?: number,
|
||||
heightInLines: number,
|
||||
showFrame?: boolean,
|
||||
frameWidth?: number
|
||||
}
|
||||
}
|
||||
845
packages/monaco/src/browser/monaco-editor.ts
Normal file
845
packages/monaco/src/browser/monaco-editor.ts
Normal file
@@ -0,0 +1,845 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject, unmanaged } from '@theia/core/shared/inversify';
|
||||
import { ElementExt } from '@theia/core/shared/@lumino/domutils';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
|
||||
import { DisposableCollection, Disposable, Emitter, Event, nullToUndefined, MaybeNull } from '@theia/core/lib/common';
|
||||
import {
|
||||
Dimension,
|
||||
EditorManager,
|
||||
EditorWidget,
|
||||
Position,
|
||||
Range,
|
||||
TextDocumentContentChangeDelta,
|
||||
TextDocumentChangeEvent,
|
||||
TextEditor,
|
||||
RevealRangeOptions,
|
||||
RevealPositionOptions,
|
||||
DeltaDecorationParams,
|
||||
ReplaceTextParams,
|
||||
EditorDecoration,
|
||||
EditorMouseEvent,
|
||||
EncodingMode,
|
||||
EditorDecorationOptions,
|
||||
MouseTargetType
|
||||
} from '@theia/editor/lib/browser';
|
||||
import { MonacoEditorModel } from './monaco-editor-model';
|
||||
import { MonacoToProtocolConverter } from './monaco-to-protocol-converter';
|
||||
import { ProtocolToMonacoConverter } from './protocol-to-monaco-converter';
|
||||
import { TextEdit } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import { UTF8 } from '@theia/core/lib/common/encodings';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { ILanguageService } from '@theia/monaco-editor-core/esm/vs/editor/common/languages/language';
|
||||
import { IInstantiationService, ServiceIdentifier } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/instantiation';
|
||||
import { ICodeEditor, IMouseTargetMargin } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
|
||||
import { IStandaloneEditorConstructionOptions, StandaloneCodeEditor, StandaloneEditor } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneCodeEditor';
|
||||
import { ServiceCollection } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/serviceCollection';
|
||||
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
|
||||
import { ConfigurationChangedEvent, IEditorOptions, ShowLightbulbIconMode } from '@theia/monaco-editor-core/esm/vs/editor/common/config/editorOptions';
|
||||
import { ICodeEditorService } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/codeEditorService';
|
||||
import { ICommandService } from '@theia/monaco-editor-core/esm/vs/platform/commands/common/commands';
|
||||
import { IContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';
|
||||
import { IKeybindingService } from '@theia/monaco-editor-core/esm/vs/platform/keybinding/common/keybinding';
|
||||
import { IThemeService } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService';
|
||||
import { INotificationService } from '@theia/monaco-editor-core/esm/vs/platform/notification/common/notification';
|
||||
import { IAccessibilityService } from '@theia/monaco-editor-core/esm/vs/platform/accessibility/common/accessibility';
|
||||
import { ILanguageConfigurationService } from '@theia/monaco-editor-core/esm/vs/editor/common/languages/languageConfigurationRegistry';
|
||||
import { ILanguageFeaturesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageFeatures';
|
||||
import * as objects from '@theia/monaco-editor-core/esm/vs/base/common/objects';
|
||||
import { Selection } from '@theia/editor/lib/browser/editor';
|
||||
import { IHoverService, WorkbenchHoverDelegate } from '@theia/monaco-editor-core/esm/vs/platform/hover/browser/hover';
|
||||
import { setHoverDelegateFactory } from '@theia/monaco-editor-core/esm/vs/base/browser/ui/hover/hoverDelegateFactory';
|
||||
import { MonacoTextModelService } from './monaco-text-model-service';
|
||||
|
||||
export type ServicePair<T> = [ServiceIdentifier<T>, T];
|
||||
|
||||
export interface EditorServiceOverrides extends Iterable<ServicePair<unknown>> { }
|
||||
|
||||
@injectable()
|
||||
export class MonacoEditorServices {
|
||||
|
||||
@inject(MonacoToProtocolConverter)
|
||||
protected readonly m2p: MonacoToProtocolConverter;
|
||||
|
||||
@inject(ProtocolToMonacoConverter)
|
||||
protected readonly p2m: ProtocolToMonacoConverter;
|
||||
|
||||
@inject(ContextKeyService)
|
||||
protected readonly contextKeyService: ContextKeyService;
|
||||
|
||||
@inject(MonacoTextModelService)
|
||||
protected readonly monacoModelService: MonacoTextModelService;
|
||||
|
||||
constructor(@unmanaged() services: MonacoEditorServices) {
|
||||
Object.assign(this, services);
|
||||
}
|
||||
}
|
||||
|
||||
export class MonacoEditor extends MonacoEditorServices implements TextEditor {
|
||||
|
||||
static async create(uri: URI,
|
||||
document: MonacoEditorModel,
|
||||
node: HTMLElement,
|
||||
services: MonacoEditorServices,
|
||||
options?: MonacoEditor.IOptions,
|
||||
override?: EditorServiceOverrides,
|
||||
parentEditor?: MonacoEditor): Promise<MonacoEditor> {
|
||||
const instance = new MonacoEditor(uri, document, node, services, options, override, parentEditor);
|
||||
await instance.init();
|
||||
return instance;
|
||||
}
|
||||
|
||||
protected readonly toDispose = new DisposableCollection();
|
||||
|
||||
protected readonly autoSizing: boolean;
|
||||
protected readonly minHeight: number;
|
||||
protected readonly maxHeight: number;
|
||||
protected editor: monaco.editor.IStandaloneCodeEditor;
|
||||
|
||||
protected readonly onCursorPositionChangedEmitter = new Emitter<Position>();
|
||||
protected readonly onSelectionChangedEmitter = new Emitter<Selection>();
|
||||
protected readonly onFocusChangedEmitter = new Emitter<boolean>();
|
||||
protected readonly onDocumentContentChangedEmitter = new Emitter<TextDocumentChangeEvent>();
|
||||
protected readonly onMouseDownEmitter = new Emitter<EditorMouseEvent>();
|
||||
readonly onDidChangeReadOnly = this.document.onDidChangeReadOnly;
|
||||
protected readonly onLanguageChangedEmitter = new Emitter<string>();
|
||||
readonly onLanguageChanged = this.onLanguageChangedEmitter.event;
|
||||
protected readonly onScrollChangedEmitter = new Emitter<void>();
|
||||
readonly onEncodingChanged = this.document.onDidChangeEncoding;
|
||||
protected readonly onResizeEmitter = new Emitter<Dimension | null>();
|
||||
readonly onDidResize = this.onResizeEmitter.event;
|
||||
protected readonly onShouldDisplayDirtyDiffChangedEmitter = new Emitter<boolean>;
|
||||
readonly onShouldDisplayDirtyDiffChanged: Event<boolean> | undefined = this.onShouldDisplayDirtyDiffChangedEmitter.event;
|
||||
|
||||
readonly documents = new Set<MonacoEditorModel>();
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
protected savedViewState: monaco.editor.IEditorViewState | null = null;
|
||||
|
||||
protected constructor(
|
||||
readonly uri: URI,
|
||||
readonly document: MonacoEditorModel,
|
||||
readonly node: HTMLElement,
|
||||
services: MonacoEditorServices,
|
||||
options?: MonacoEditor.IOptions,
|
||||
override?: EditorServiceOverrides,
|
||||
readonly parentEditor?: MonacoEditor
|
||||
) {
|
||||
super(services);
|
||||
this.toDispose.pushAll([
|
||||
this.onCursorPositionChangedEmitter,
|
||||
this.onSelectionChangedEmitter,
|
||||
this.onFocusChangedEmitter,
|
||||
this.onDocumentContentChangedEmitter,
|
||||
this.onMouseDownEmitter,
|
||||
this.onLanguageChangedEmitter,
|
||||
this.onScrollChangedEmitter,
|
||||
this.onShouldDisplayDirtyDiffChangedEmitter
|
||||
]);
|
||||
this.documents.add(document);
|
||||
this.autoSizing = options && options.autoSizing !== undefined ? options.autoSizing : false;
|
||||
this.minHeight = options && options.minHeight !== undefined ? options.minHeight : -1;
|
||||
this.maxHeight = options && options.maxHeight !== undefined ? options.maxHeight : -1;
|
||||
this.toDispose.push(this.create({
|
||||
...MonacoEditor.createReadOnlyOptions(document.readOnly),
|
||||
...options
|
||||
}, override));
|
||||
// Ensure that a valid InstantiationService is responsible for creating hover delegates when the InstantiationService for this widget is disposed.
|
||||
// Cf. https://github.com/eclipse-theia/theia/issues/15102
|
||||
this.toDispose.push(Disposable.create(() => setHoverDelegateFactory((placement, enableInstantHover) =>
|
||||
StandaloneServices.get(IInstantiationService).createInstance(WorkbenchHoverDelegate, placement, enableInstantHover, {})
|
||||
)));
|
||||
this.addHandlers(this.editor);
|
||||
this.editor.createContextKey('resource', document.uri);
|
||||
}
|
||||
|
||||
protected async init(): Promise<void> {
|
||||
this.toDispose.push(await this.monacoModelService.createModelReference(this.uri));
|
||||
}
|
||||
|
||||
getEncoding(): string {
|
||||
return this.document.getEncoding() || UTF8;
|
||||
}
|
||||
|
||||
setEncoding(encoding: string, mode: EncodingMode): Promise<void> {
|
||||
return this.document.setEncoding(encoding, mode);
|
||||
}
|
||||
|
||||
protected create(options?: monaco.editor.IStandaloneEditorConstructionOptions | IStandaloneEditorConstructionOptions, override?: EditorServiceOverrides): Disposable {
|
||||
const combinedOptions = {
|
||||
...options,
|
||||
lightbulb: { enabled: ShowLightbulbIconMode.On },
|
||||
fixedOverflowWidgets: true,
|
||||
scrollbar: {
|
||||
useShadows: false,
|
||||
verticalHasArrows: false,
|
||||
horizontalHasArrows: false,
|
||||
verticalScrollbarSize: 10,
|
||||
horizontalScrollbarSize: 10,
|
||||
...options?.scrollbar,
|
||||
}
|
||||
} as IStandaloneEditorConstructionOptions;
|
||||
const instantiator = this.getInstantiatorWithOverrides(override);
|
||||
/**
|
||||
* @monaco-uplift. Should be guaranteed to work.
|
||||
* Incomparable enums prevent TypeScript from believing that public IStandaloneCodeEditor is satisfied by private StandaloneCodeEditor
|
||||
*/
|
||||
return this.editor = (this.parentEditor ?
|
||||
instantiator.createInstance(EmbeddedCodeEditor, this.node, combinedOptions, this.parentEditor.getControl() as unknown as ICodeEditor) :
|
||||
instantiator.createInstance(StandaloneEditor, this.node, combinedOptions)) as unknown as monaco.editor.IStandaloneCodeEditor;
|
||||
}
|
||||
|
||||
protected getInstantiatorWithOverrides(override?: EditorServiceOverrides): IInstantiationService {
|
||||
const instantiator = StandaloneServices.get(IInstantiationService);
|
||||
if (override) {
|
||||
const overrideServices = new ServiceCollection(...override);
|
||||
const child = instantiator.createChild(overrideServices);
|
||||
this.toDispose.push(child);
|
||||
return child;
|
||||
}
|
||||
return instantiator;
|
||||
}
|
||||
|
||||
protected addHandlers(codeEditor: monaco.editor.IStandaloneCodeEditor): void {
|
||||
this.toDispose.push(codeEditor.onDidChangeModelLanguage(e =>
|
||||
this.fireLanguageChanged(e.newLanguage)
|
||||
));
|
||||
this.toDispose.push(codeEditor.onDidChangeConfiguration(() => this.refresh()));
|
||||
this.toDispose.push(codeEditor.onDidChangeModel(() => this.refresh()));
|
||||
this.toDispose.push(codeEditor.onDidChangeModelContent(e => {
|
||||
this.refresh();
|
||||
this.onDocumentContentChangedEmitter.fire({ document: this.document, contentChanges: e.changes.map(this.mapModelContentChange.bind(this)) });
|
||||
}));
|
||||
this.toDispose.push(codeEditor.onDidChangeCursorPosition(() =>
|
||||
this.onCursorPositionChangedEmitter.fire(this.cursor)
|
||||
));
|
||||
this.toDispose.push(codeEditor.onDidChangeCursorSelection(event =>
|
||||
this.onSelectionChangedEmitter.fire({
|
||||
...this.m2p.asRange(event.selection),
|
||||
direction: event.selection.getDirection() === monaco.SelectionDirection.LTR ? 'ltr' : 'rtl'
|
||||
})
|
||||
));
|
||||
this.toDispose.push(codeEditor.onDidFocusEditorText(() =>
|
||||
this.onFocusChangedEmitter.fire(this.isFocused())
|
||||
));
|
||||
this.toDispose.push(codeEditor.onDidBlurEditorText(() =>
|
||||
this.onFocusChangedEmitter.fire(this.isFocused())
|
||||
));
|
||||
this.toDispose.push(codeEditor.onMouseDown(e => {
|
||||
const { element, position, range } = e.target;
|
||||
this.onMouseDownEmitter.fire({
|
||||
target: {
|
||||
type: e.target.type as unknown as MouseTargetType,
|
||||
element: element || undefined,
|
||||
mouseColumn: this.m2p.asPosition(undefined, e.target.mouseColumn).character,
|
||||
range: range && this.m2p.asRange(range) || undefined,
|
||||
position: position && this.m2p.asPosition(position.lineNumber, position.column) || undefined,
|
||||
detail: (e.target as unknown as IMouseTargetMargin).detail || {},
|
||||
},
|
||||
event: e.event.browserEvent
|
||||
});
|
||||
}));
|
||||
this.toDispose.push(codeEditor.onDidScrollChange(e => {
|
||||
this.onScrollChangedEmitter.fire(undefined);
|
||||
}));
|
||||
this.toDispose.push(this.onDidChangeReadOnly(readOnly => {
|
||||
codeEditor.updateOptions(MonacoEditor.createReadOnlyOptions(readOnly));
|
||||
}));
|
||||
}
|
||||
|
||||
handleVisibilityChanged(nowVisible: boolean): void {
|
||||
if (nowVisible) {
|
||||
this.baseEditor.setModel(this.baseModel);
|
||||
this.baseEditor.restoreViewState(this.savedViewState);
|
||||
this.baseEditor.focus();
|
||||
} else {
|
||||
this.savedViewState = this.baseEditor.saveViewState();
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
this.baseEditor.setModel(null); // workaround for https://github.com/eclipse-theia/theia/issues/14880
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This property allows working with the underlying editor instance
|
||||
* through the base editor interface, `monaco.editor.IEditor`.
|
||||
*
|
||||
* This property is intended to be overriden in subclasses as needed,
|
||||
* e.g. it returns the underlying diff editor in `MonacoDiffEditor`.
|
||||
*/
|
||||
protected get baseEditor(): monaco.editor.IEditor {
|
||||
return this.editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* This property allows working with the underlying editor model instance
|
||||
* through the base editor model interface, `monaco.editor.IEditorModel`.
|
||||
*
|
||||
* This property is intended to be overriden in subclasses as needed,
|
||||
* e.g. it returns the underlying diff editor model in `MonacoDiffEditor`.
|
||||
*/
|
||||
protected get baseModel(): monaco.editor.IEditorModel {
|
||||
return this.document.textEditorModel;
|
||||
}
|
||||
|
||||
getVisibleRanges(): Range[] {
|
||||
return this.editor.getVisibleRanges().map(range => this.m2p.asRange(range));
|
||||
}
|
||||
|
||||
protected mapModelContentChange(change: monaco.editor.IModelContentChange): TextDocumentContentChangeDelta {
|
||||
return {
|
||||
range: this.m2p.asRange(change.range),
|
||||
rangeLength: change.rangeLength,
|
||||
text: change.text
|
||||
};
|
||||
}
|
||||
|
||||
get onDispose(): Event<void> {
|
||||
return this.toDispose.onDispose;
|
||||
}
|
||||
|
||||
get onDocumentContentChanged(): Event<TextDocumentChangeEvent> {
|
||||
return this.onDocumentContentChangedEmitter.event;
|
||||
}
|
||||
|
||||
get isReadonly(): boolean | MarkdownString {
|
||||
return this.document.readOnly;
|
||||
}
|
||||
|
||||
get cursor(): Position {
|
||||
const { lineNumber, column } = this.editor.getPosition()!;
|
||||
return this.m2p.asPosition(lineNumber, column);
|
||||
}
|
||||
|
||||
set cursor(cursor: Position) {
|
||||
const position = this.p2m.asPosition(cursor);
|
||||
this.editor.setPosition(position);
|
||||
}
|
||||
|
||||
get onCursorPositionChanged(): Event<Position> {
|
||||
return this.onCursorPositionChangedEmitter.event;
|
||||
}
|
||||
|
||||
get selection(): Selection {
|
||||
return this.m2p.asSelection(this.editor.getSelection()!);
|
||||
}
|
||||
|
||||
set selection(selection: Selection) {
|
||||
const range = this.p2m.asRange(selection);
|
||||
this.editor.setSelection(range);
|
||||
}
|
||||
|
||||
get onSelectionChanged(): Event<Selection> {
|
||||
return this.onSelectionChangedEmitter.event;
|
||||
}
|
||||
|
||||
get onScrollChanged(): Event<void> {
|
||||
return this.onScrollChangedEmitter.event;
|
||||
}
|
||||
|
||||
revealPosition(raw: Position, options: RevealPositionOptions = { vertical: 'center' }): void {
|
||||
const position = this.p2m.asPosition(raw);
|
||||
switch (options.vertical) {
|
||||
case 'auto':
|
||||
this.editor.revealPosition(position);
|
||||
break;
|
||||
case 'center':
|
||||
this.editor.revealPositionInCenter(position);
|
||||
break;
|
||||
case 'centerIfOutsideViewport':
|
||||
this.editor.revealPositionInCenterIfOutsideViewport(position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
revealRange(raw: Range, options: RevealRangeOptions = { at: 'center' }): void {
|
||||
const range = this.p2m.asRange(raw);
|
||||
switch (options.at) {
|
||||
case 'top':
|
||||
this.editor.revealRangeAtTop(range!);
|
||||
break;
|
||||
case 'center':
|
||||
this.editor.revealRangeInCenter(range!);
|
||||
break;
|
||||
case 'centerIfOutsideViewport':
|
||||
this.editor.revealRangeInCenterIfOutsideViewport(range!);
|
||||
break;
|
||||
case 'auto':
|
||||
this.editor.revealRange(range!);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
/**
|
||||
* `this.editor.focus` forcefully changes the focus editor state,
|
||||
* regardless whether the textarea actually received the focus.
|
||||
* It could lead to issues like https://github.com/eclipse-theia/theia/issues/7902
|
||||
* Instead we focus the underlying textarea.
|
||||
*/
|
||||
const node = this.editor.getDomNode();
|
||||
if (node) {
|
||||
const textarea = node.querySelector('textarea') as HTMLElement;
|
||||
textarea.focus();
|
||||
}
|
||||
}
|
||||
|
||||
blur(): void {
|
||||
const node = this.editor.getDomNode();
|
||||
if (node) {
|
||||
const textarea = node.querySelector('textarea') as HTMLElement;
|
||||
textarea.blur();
|
||||
}
|
||||
}
|
||||
|
||||
isFocused({ strict }: { strict: boolean } = { strict: false }): boolean {
|
||||
if (!this.editor.hasTextFocus()) {
|
||||
return false;
|
||||
}
|
||||
if (strict) {
|
||||
return !this.isSuggestWidgetVisible() && !this.isFindWidgetVisible() && !this.isRenameInputVisible();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
get onFocusChanged(): Event<boolean> {
|
||||
return this.onFocusChangedEmitter.event;
|
||||
}
|
||||
|
||||
get onMouseDown(): Event<EditorMouseEvent> {
|
||||
return this.onMouseDownEmitter.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* `true` if the suggest widget is visible in the editor. Otherwise, `false`.
|
||||
*/
|
||||
isSuggestWidgetVisible(): boolean {
|
||||
return this.contextKeyService.match('suggestWidgetVisible', this.editor.getDomNode() || this.node);
|
||||
}
|
||||
|
||||
/**
|
||||
* `true` if the find (and replace) widget is visible in the editor. Otherwise, `false`.
|
||||
*/
|
||||
isFindWidgetVisible(): boolean {
|
||||
return this.contextKeyService.match('findWidgetVisible', this.editor.getDomNode() || this.node);
|
||||
}
|
||||
|
||||
/**
|
||||
* `true` if the name rename refactoring input HTML element is visible. Otherwise, `false`.
|
||||
*/
|
||||
isRenameInputVisible(): boolean {
|
||||
return this.contextKeyService.match('renameInputVisible', this.editor.getDomNode() || this.node);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
isDisposed(): boolean {
|
||||
return this.toDispose.disposed;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
trigger(source: string, handlerId: string, payload: any): void {
|
||||
this.editor.trigger(source, handlerId, payload);
|
||||
}
|
||||
|
||||
getControl(): monaco.editor.IStandaloneCodeEditor {
|
||||
return this.editor;
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
this.autoresize();
|
||||
}
|
||||
|
||||
resizeToFit(): void {
|
||||
this.autoresize();
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
this.onResizeEmitter.fire(null);
|
||||
}
|
||||
|
||||
setSize(dimension: Dimension): void {
|
||||
this.resize(dimension);
|
||||
this.onResizeEmitter.fire(dimension);
|
||||
}
|
||||
|
||||
protected autoresize(): void {
|
||||
if (this.autoSizing) {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
this.resize(null);
|
||||
}
|
||||
}
|
||||
|
||||
protected resize(dimension: Dimension | null): void {
|
||||
if (this.node) {
|
||||
const layoutSize = this.computeLayoutSize(this.node, dimension);
|
||||
this.editor.layout(layoutSize);
|
||||
}
|
||||
}
|
||||
|
||||
protected computeLayoutSize(hostNode: HTMLElement, dimension: monaco.editor.IDimension | null): monaco.editor.IDimension {
|
||||
if (dimension && dimension.width >= 0 && dimension.height >= 0) {
|
||||
return dimension;
|
||||
}
|
||||
const boxSizing = ElementExt.boxSizing(hostNode);
|
||||
|
||||
const width = (!dimension || dimension.width < 0) ?
|
||||
this.getWidth(hostNode, boxSizing) :
|
||||
dimension.width;
|
||||
|
||||
const height = (!dimension || dimension.height < 0) ?
|
||||
this.getHeight(hostNode, boxSizing) :
|
||||
dimension.height;
|
||||
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
protected getWidth(hostNode: HTMLElement, boxSizing: ElementExt.IBoxSizing): number {
|
||||
return hostNode.offsetWidth - boxSizing.horizontalSum;
|
||||
}
|
||||
|
||||
protected getHeight(hostNode: HTMLElement, boxSizing: ElementExt.IBoxSizing): number {
|
||||
if (!this.autoSizing) {
|
||||
return hostNode.offsetHeight - boxSizing.verticalSum;
|
||||
}
|
||||
|
||||
const lineHeight = this.editor.getOption(monaco.editor.EditorOption.lineHeight);
|
||||
const lineCount = this.editor.getModel()!.getLineCount();
|
||||
const contentHeight = lineHeight * lineCount;
|
||||
|
||||
const horizontalScrollbarHeight = this.editor.getLayoutInfo().horizontalScrollbarHeight;
|
||||
|
||||
const editorHeight = contentHeight + horizontalScrollbarHeight;
|
||||
if (this.minHeight >= 0) {
|
||||
const minHeight = lineHeight * this.minHeight + horizontalScrollbarHeight;
|
||||
if (editorHeight < minHeight) {
|
||||
return minHeight;
|
||||
}
|
||||
}
|
||||
if (this.maxHeight >= 0) {
|
||||
const maxHeight = lineHeight * this.maxHeight + horizontalScrollbarHeight;
|
||||
return Math.min(maxHeight, editorHeight);
|
||||
}
|
||||
return editorHeight;
|
||||
}
|
||||
|
||||
isActionSupported(id: string): boolean {
|
||||
const action = this.editor.getAction(id);
|
||||
return !!action && action.isSupported();
|
||||
}
|
||||
|
||||
async runAction(id: string): Promise<void> {
|
||||
const action = this.editor.getAction(id);
|
||||
if (action && action.isSupported()) {
|
||||
await action.run();
|
||||
}
|
||||
}
|
||||
|
||||
deltaDecorations(params: DeltaDecorationParams): string[] {
|
||||
const oldDecorations = params.oldDecorations;
|
||||
const newDecorations = this.toDeltaDecorations(params);
|
||||
return this.editor.deltaDecorations(oldDecorations, newDecorations);
|
||||
}
|
||||
|
||||
protected toDeltaDecorations(params: DeltaDecorationParams): monaco.editor.IModelDeltaDecoration[] {
|
||||
return params.newDecorations.map(({ options: theiaOptions, range }) => {
|
||||
const options: monaco.editor.IModelDecorationOptions = {
|
||||
...theiaOptions,
|
||||
hoverMessage: this.fromStringToMarkdownString(theiaOptions.hoverMessage),
|
||||
glyphMarginHoverMessage: this.fromStringToMarkdownString(theiaOptions.glyphMarginHoverMessage)
|
||||
};
|
||||
return {
|
||||
options,
|
||||
range: this.p2m.asRange(range),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
protected fromStringToMarkdownString(hoverMessage?: string | monaco.IMarkdownString | monaco.IMarkdownString[]): monaco.IMarkdownString | monaco.IMarkdownString[] | undefined {
|
||||
if (typeof hoverMessage === 'string') {
|
||||
return { value: hoverMessage };
|
||||
}
|
||||
return hoverMessage;
|
||||
}
|
||||
|
||||
protected fromMarkdownToString(maybeMarkdown?: null | string | monaco.IMarkdownString | monaco.IMarkdownString[]): string | undefined {
|
||||
if (!maybeMarkdown) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof maybeMarkdown === 'string') {
|
||||
return maybeMarkdown;
|
||||
}
|
||||
if (Array.isArray(maybeMarkdown)) {
|
||||
return maybeMarkdown.map(({ value }) => value).join('\n');
|
||||
}
|
||||
return maybeMarkdown.value;
|
||||
}
|
||||
|
||||
getLinesDecorations(startLineNumber: number, endLineNumber: number): (EditorDecoration & Readonly<{ id: string }>)[] {
|
||||
const toPosition = (line: number): monaco.Position => this.p2m.asPosition({ line, character: 0 });
|
||||
const start = toPosition(startLineNumber).lineNumber;
|
||||
const end = toPosition(endLineNumber).lineNumber;
|
||||
return this.editor.getModel()?.getLinesDecorations(start, end)
|
||||
.map(this.toEditorDecoration.bind(this)) || [];
|
||||
}
|
||||
|
||||
protected toEditorDecoration(decoration: monaco.editor.IModelDecoration): EditorDecoration & Readonly<{ id: string }> {
|
||||
const range = this.m2p.asRange(decoration.range);
|
||||
const { id, options: monacoOptions } = decoration;
|
||||
const options: MaybeNull<EditorDecorationOptions> = {
|
||||
...monacoOptions,
|
||||
hoverMessage: this.fromMarkdownToString(monacoOptions.hoverMessage),
|
||||
glyphMarginHoverMessage: this.fromMarkdownToString(monacoOptions.hoverMessage),
|
||||
};
|
||||
return {
|
||||
options: nullToUndefined(options),
|
||||
range,
|
||||
id
|
||||
};
|
||||
}
|
||||
|
||||
getVisibleColumn(position: Position): number {
|
||||
return this.editor.getVisibleColumnFromPosition(this.p2m.asPosition(position));
|
||||
}
|
||||
|
||||
async replaceText(params: ReplaceTextParams): Promise<boolean> {
|
||||
const edits: monaco.editor.IIdentifiedSingleEditOperation[] = params.replaceOperations.map(param => {
|
||||
const range = monaco.Range.fromPositions(this.p2m.asPosition(param.range.start), this.p2m.asPosition(param.range.end));
|
||||
return {
|
||||
forceMoveMarkers: true,
|
||||
identifier: {
|
||||
major: range.startLineNumber,
|
||||
minor: range.startColumn
|
||||
},
|
||||
range,
|
||||
text: param.text
|
||||
};
|
||||
});
|
||||
// If the editor doesn't have a model set (e.g., widget not visible),
|
||||
// apply edits directly to the document's underlying model.
|
||||
// This ensures text replacements work even when the editor widget is not attached to the shell.
|
||||
if (!this.editor.getModel()) {
|
||||
this.document.textEditorModel.applyEdits(edits);
|
||||
return true;
|
||||
}
|
||||
return this.editor.executeEdits(params.source, edits);
|
||||
}
|
||||
|
||||
executeEdits(edits: TextEdit[]): boolean {
|
||||
const monacoEdits = this.p2m.asTextEdits(edits) as monaco.editor.IIdentifiedSingleEditOperation[];
|
||||
// If the editor doesn't have a model set (e.g., widget not visible),
|
||||
// apply edits directly to the document's underlying model.
|
||||
if (!this.editor.getModel()) {
|
||||
this.document.textEditorModel.applyEdits(monacoEdits);
|
||||
return true;
|
||||
}
|
||||
return this.editor.executeEdits('MonacoEditor', monacoEdits);
|
||||
}
|
||||
|
||||
storeViewState(): object {
|
||||
if (this.baseEditor.getModel()) {
|
||||
this.savedViewState = this.baseEditor.saveViewState();
|
||||
}
|
||||
return this.savedViewState!;
|
||||
}
|
||||
|
||||
restoreViewState(state: monaco.editor.IEditorViewState): void {
|
||||
if (this.baseEditor.getModel()) {
|
||||
this.baseEditor.restoreViewState(state);
|
||||
}
|
||||
this.savedViewState = state;
|
||||
}
|
||||
|
||||
/* `true` because it is derived from an URI during the instantiation */
|
||||
protected _languageAutoDetected = true;
|
||||
|
||||
get languageAutoDetected(): boolean {
|
||||
return this._languageAutoDetected;
|
||||
}
|
||||
|
||||
async detectLanguage(): Promise<void> {
|
||||
const languageService = StandaloneServices.get(ILanguageService);
|
||||
const firstLine = this.document.textEditorModel.getLineContent(1);
|
||||
const model = this.getControl().getModel();
|
||||
const language = languageService.createByFilepathOrFirstLine(model && model.uri, firstLine);
|
||||
this.setLanguage(language.languageId);
|
||||
this._languageAutoDetected = true;
|
||||
}
|
||||
|
||||
setLanguage(languageId: string): void {
|
||||
for (const document of this.documents) {
|
||||
monaco.editor.setModelLanguage(document.textEditorModel, languageId);
|
||||
}
|
||||
}
|
||||
|
||||
protected fireLanguageChanged(languageId: string): void {
|
||||
this._languageAutoDetected = false;
|
||||
this.onLanguageChangedEmitter.fire(languageId);
|
||||
}
|
||||
|
||||
getResourceUri(): URI {
|
||||
return this.uri;
|
||||
}
|
||||
createMoveToUri(resourceUri: URI): URI {
|
||||
return this.uri.withPath(resourceUri.path);
|
||||
}
|
||||
|
||||
private _shouldDisplayDirtyDiff = true;
|
||||
shouldDisplayDirtyDiff(): boolean {
|
||||
return this._shouldDisplayDirtyDiff;
|
||||
}
|
||||
setShouldDisplayDirtyDiff(value: boolean): void {
|
||||
if (value !== this._shouldDisplayDirtyDiff) {
|
||||
this._shouldDisplayDirtyDiff = value;
|
||||
this.onShouldDisplayDirtyDiffChangedEmitter.fire(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export namespace MonacoEditor {
|
||||
export interface ICommonOptions {
|
||||
/**
|
||||
* Whether an editor should be auto resized on a content change.
|
||||
*
|
||||
* #### Fixme
|
||||
* remove when https://github.com/Microsoft/monaco-editor/issues/103 is resolved
|
||||
*/
|
||||
autoSizing?: boolean;
|
||||
/**
|
||||
* A minimal height of an editor in lines.
|
||||
*
|
||||
* #### Fixme
|
||||
* remove when https://github.com/Microsoft/monaco-editor/issues/103 is resolved
|
||||
*/
|
||||
minHeight?: number;
|
||||
/**
|
||||
* A maximal height of an editor in lines.
|
||||
*
|
||||
* #### Fixme
|
||||
* remove when https://github.com/Microsoft/monaco-editor/issues/103 is resolved
|
||||
*/
|
||||
maxHeight?: number;
|
||||
}
|
||||
|
||||
export interface IOptions extends ICommonOptions, monaco.editor.IStandaloneEditorConstructionOptions { }
|
||||
|
||||
export function getAll(manager: EditorManager): MonacoEditor[] {
|
||||
return manager.all.map(e => get(e)).filter(e => !!e) as MonacoEditor[];
|
||||
}
|
||||
|
||||
export function getCurrent(manager: EditorManager): MonacoEditor | undefined {
|
||||
return get(manager.currentEditor);
|
||||
}
|
||||
|
||||
export function getActive(manager: EditorManager): MonacoEditor | undefined {
|
||||
return get(manager.activeEditor);
|
||||
}
|
||||
|
||||
export function get(editorWidget: EditorWidget | undefined): MonacoEditor | undefined {
|
||||
if (editorWidget && editorWidget.editor instanceof MonacoEditor) {
|
||||
return editorWidget.editor;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function findByDocument(manager: EditorManager, document: MonacoEditorModel): MonacoEditor[] {
|
||||
return getAll(manager).filter(candidate => candidate.documents.has(document));
|
||||
}
|
||||
|
||||
export function getWidgetFor(manager: EditorManager, control: monaco.editor.ICodeEditor | ICodeEditor | undefined | null): EditorWidget | undefined {
|
||||
if (!control) {
|
||||
return undefined;
|
||||
}
|
||||
return manager.all.find(widget => {
|
||||
const candidate = get(widget);
|
||||
return candidate && candidate.getControl() === control;
|
||||
});
|
||||
}
|
||||
|
||||
export function createReadOnlyOptions(readOnly?: boolean | MarkdownString): monaco.editor.IEditorOptions {
|
||||
if (typeof readOnly === 'boolean') {
|
||||
return { readOnly, readOnlyMessage: undefined };
|
||||
}
|
||||
if (readOnly) {
|
||||
return { readOnly: true, readOnlyMessage: readOnly };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// adapted from https://github.com/microsoft/vscode/blob/0bd70d48ad8b3e2fb1922aa54f87c786ff2b4bd8/src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts
|
||||
// This class reproduces the logic in EmbeddedCodeEditorWidget but extends StandaloneCodeEditor rather than CodeEditorWidget.
|
||||
class EmbeddedCodeEditor extends StandaloneCodeEditor {
|
||||
|
||||
private readonly _parentEditor: ICodeEditor;
|
||||
private readonly _overwriteOptions: IEditorOptions;
|
||||
|
||||
constructor(
|
||||
domElement: HTMLElement,
|
||||
options: Readonly<IStandaloneEditorConstructionOptions>,
|
||||
parentEditor: ICodeEditor,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@ICodeEditorService codeEditorService: ICodeEditorService,
|
||||
@ICommandService commandService: ICommandService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IAccessibilityService accessibilityService: IAccessibilityService,
|
||||
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService,
|
||||
@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
|
||||
@IHoverService hoverService: IHoverService
|
||||
) {
|
||||
super(domElement,
|
||||
{ ...parentEditor.getRawOptions(), overflowWidgetsDomNode: parentEditor.getOverflowWidgetsDomNode() },
|
||||
instantiationService,
|
||||
codeEditorService,
|
||||
commandService,
|
||||
contextKeyService,
|
||||
hoverService,
|
||||
keybindingService,
|
||||
themeService,
|
||||
notificationService,
|
||||
accessibilityService,
|
||||
languageConfigurationService,
|
||||
languageFeaturesService);
|
||||
|
||||
this._parentEditor = parentEditor;
|
||||
this._overwriteOptions = options;
|
||||
|
||||
// Overwrite parent's options
|
||||
super.updateOptions(this._overwriteOptions);
|
||||
|
||||
this._register(parentEditor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => this._onParentConfigurationChanged(e)));
|
||||
}
|
||||
|
||||
getParentEditor(): ICodeEditor {
|
||||
return this._parentEditor;
|
||||
}
|
||||
|
||||
private _onParentConfigurationChanged(e: ConfigurationChangedEvent): void {
|
||||
super.updateOptions(this._parentEditor.getRawOptions());
|
||||
super.updateOptions(this._overwriteOptions);
|
||||
}
|
||||
|
||||
override updateOptions(newOptions: IEditorOptions): void {
|
||||
objects.mixin(this._overwriteOptions, newOptions, true);
|
||||
super.updateOptions(this._overwriteOptions);
|
||||
}
|
||||
}
|
||||
117
packages/monaco/src/browser/monaco-formatting-conflicts.ts
Normal file
117
packages/monaco/src/browser/monaco-formatting-conflicts.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 Red Hat, Inc. and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
||||
import { EditorManager } from '@theia/editor/lib/browser';
|
||||
import { MonacoQuickInputService } from './monaco-quick-input-service';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { FormattingConflicts, FormattingMode } from '@theia/monaco-editor-core/esm/vs/editor/contrib/format/browser/format';
|
||||
import { DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
|
||||
import { ITextModel } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { PreferenceService, PreferenceLanguageOverrideService } from '@theia/core';
|
||||
|
||||
type FormattingEditProvider = DocumentFormattingEditProvider | DocumentRangeFormattingEditProvider;
|
||||
|
||||
const PREFERENCE_NAME = 'editor.defaultFormatter';
|
||||
|
||||
@injectable()
|
||||
export class MonacoFormattingConflictsContribution implements FrontendApplicationContribution {
|
||||
|
||||
@inject(MonacoQuickInputService)
|
||||
protected readonly monacoQuickInputService: MonacoQuickInputService;
|
||||
|
||||
@inject(PreferenceService)
|
||||
protected readonly preferenceService: PreferenceService;
|
||||
|
||||
@inject(PreferenceLanguageOverrideService)
|
||||
protected readonly preferenceSchema: PreferenceLanguageOverrideService;
|
||||
|
||||
@inject(EditorManager)
|
||||
protected readonly editorManager: EditorManager;
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
|
||||
FormattingConflicts.setFormatterSelector(<T extends FormattingEditProvider>(
|
||||
formatters: T[], document: ITextModel, mode: FormattingMode) =>
|
||||
this.selectFormatter(formatters, document, mode));
|
||||
}
|
||||
|
||||
protected async setDefaultFormatter(language: string, formatter: string): Promise<void> {
|
||||
const name = this.preferenceSchema.overridePreferenceName({
|
||||
preferenceName: PREFERENCE_NAME,
|
||||
overrideIdentifier: language
|
||||
});
|
||||
|
||||
await this.preferenceService.set(name, formatter);
|
||||
}
|
||||
|
||||
private getDefaultFormatter(language: string, resourceURI: string): string | undefined {
|
||||
const name = this.preferenceSchema.overridePreferenceName({
|
||||
preferenceName: PREFERENCE_NAME,
|
||||
overrideIdentifier: language
|
||||
});
|
||||
|
||||
return this.preferenceService.get<string>(name, undefined, resourceURI);
|
||||
}
|
||||
|
||||
private async selectFormatter<T extends FormattingEditProvider>(
|
||||
formatters: T[], document: monaco.editor.ITextModel | ITextModel, mode: FormattingMode): Promise<T | undefined> {
|
||||
|
||||
if (formatters.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (formatters.length === 1) {
|
||||
return formatters[0];
|
||||
}
|
||||
|
||||
const currentEditor = this.editorManager.currentEditor;
|
||||
if (!currentEditor) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const languageId = currentEditor.editor.document.languageId;
|
||||
const defaultFormatterId = this.getDefaultFormatter(languageId, document.uri.toString());
|
||||
|
||||
if (defaultFormatterId) {
|
||||
const formatter = formatters.find(f => f.extensionId && f.extensionId.value === defaultFormatterId);
|
||||
if (formatter) {
|
||||
return formatter;
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise<T | undefined>(async (resolve, reject) => {
|
||||
const items = formatters
|
||||
.filter(formatter => formatter.displayName)
|
||||
.map(formatter => ({
|
||||
label: formatter.displayName!,
|
||||
detail: formatter.extensionId ? formatter.extensionId.value : undefined,
|
||||
value: formatter,
|
||||
}))
|
||||
.sort((a, b) => a.label!.localeCompare(b.label!));
|
||||
|
||||
const selectedFormatter = await this.monacoQuickInputService.showQuickPick(items, { placeholder: nls.localizeByDefault('Format Document With...') });
|
||||
if (selectedFormatter) {
|
||||
this.setDefaultFormatter(languageId, selectedFormatter.detail ? selectedFormatter.detail : '');
|
||||
resolve(selectedFormatter.value);
|
||||
} else {
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
||||
import { ColorTheme, CssStyleCollector, FrontendApplicationContribution, QuickAccessRegistry, StylingParticipant } from '@theia/core/lib/browser';
|
||||
import { MonacoSnippetSuggestProvider } from './monaco-snippet-suggest-provider';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { setSnippetSuggestSupport } from '@theia/monaco-editor-core/esm/vs/editor/contrib/suggest/browser/suggest';
|
||||
import { CompletionItemProvider } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
|
||||
import { MonacoTextModelService } from './monaco-text-model-service';
|
||||
import { MonacoThemingService } from './monaco-theming-service';
|
||||
import { isHighContrast } from '@theia/core/lib/common/theme';
|
||||
import { editorOptionsRegistry, IEditorOption } from '@theia/monaco-editor-core/esm/vs/editor/common/config/editorOptions';
|
||||
import { MAX_SAFE_INTEGER, PreferenceSchemaService } from '@theia/core';
|
||||
import { editorGeneratedPreferenceProperties } from '@theia/editor/lib/common/editor-generated-preference-schema';
|
||||
import { WorkspaceFileService } from '@theia/workspace/lib/common/workspace-file-service';
|
||||
import { SecondaryWindowHandler } from '@theia/core/lib/browser/secondary-window-handler';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { MonacoEditor } from './monaco-editor';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { StandaloneThemeService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneThemeService';
|
||||
import { IStandaloneThemeService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/common/standaloneTheme';
|
||||
import { SecondaryWindowService } from '@theia/core/lib/browser/window/secondary-window-service';
|
||||
import { registerWindow } from '@theia/monaco-editor-core/esm/vs/base/browser/dom';
|
||||
|
||||
type CodeWindow = Window & typeof globalThis & {
|
||||
vscodeWindowId: number;
|
||||
};
|
||||
|
||||
@injectable()
|
||||
export class MonacoFrontendApplicationContribution implements FrontendApplicationContribution, StylingParticipant {
|
||||
protected readonly windowsById = new Map<number, monaco.IDisposable>();
|
||||
protected nextWindowId = 2; // the main window has the id "1"
|
||||
|
||||
@inject(MonacoTextModelService)
|
||||
protected readonly textModelService: MonacoTextModelService;
|
||||
|
||||
@inject(MonacoSnippetSuggestProvider)
|
||||
protected readonly snippetSuggestProvider: MonacoSnippetSuggestProvider;
|
||||
|
||||
@inject(PreferenceSchemaService)
|
||||
protected readonly preferenceSchema: PreferenceSchemaService;
|
||||
|
||||
@inject(QuickAccessRegistry)
|
||||
protected readonly quickAccessRegistry: QuickAccessRegistry;
|
||||
|
||||
@inject(MonacoThemingService) protected readonly monacoThemingService: MonacoThemingService;
|
||||
|
||||
@inject(WorkspaceFileService) protected readonly workspaceFileService: WorkspaceFileService;
|
||||
|
||||
@inject(SecondaryWindowHandler)
|
||||
protected readonly secondaryWindowHandler: SecondaryWindowHandler;
|
||||
|
||||
@inject(SecondaryWindowService)
|
||||
protected readonly secondaryWindowService: SecondaryWindowService;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.addAdditionalPreferenceValidations();
|
||||
// Monaco registers certain quick access providers (e.g. QuickCommandAccess) at import time, but we want to use our own.
|
||||
this.quickAccessRegistry.clear();
|
||||
|
||||
/**
|
||||
* @monaco-uplift.Should be guaranteed to work.
|
||||
* Incomparable enums prevent TypeScript from believing that public ITextModel satisfied private ITextModel
|
||||
*/
|
||||
setSnippetSuggestSupport(this.snippetSuggestProvider as unknown as CompletionItemProvider);
|
||||
|
||||
for (const language of monaco.languages.getLanguages()) {
|
||||
this.preferenceSchema.registerOverrideIdentifier(language.id);
|
||||
}
|
||||
const registerLanguage = monaco.languages.register.bind(monaco.languages);
|
||||
monaco.languages.register = language => {
|
||||
// first register override identifier, because monaco will immediately update already opened documents and then initialize with bad preferences.
|
||||
this.preferenceSchema.registerOverrideIdentifier(language.id);
|
||||
registerLanguage(language);
|
||||
};
|
||||
|
||||
this.monacoThemingService.initialize();
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
const workspaceExtensions = this.workspaceFileService.getWorkspaceFileExtensions();
|
||||
monaco.languages.register({
|
||||
id: 'jsonc',
|
||||
'aliases': [
|
||||
'JSON with Comments'
|
||||
],
|
||||
'extensions': workspaceExtensions.map(ext => `.${ext}`)
|
||||
});
|
||||
}
|
||||
onStart(): void {
|
||||
this.secondaryWindowHandler.onDidAddWidget(([widget, window]) => {
|
||||
if (widget instanceof EditorWidget && widget.editor instanceof MonacoEditor) {
|
||||
const themeService = StandaloneServices.get(IStandaloneThemeService) as StandaloneThemeService;
|
||||
themeService.registerEditorContainer(widget.node);
|
||||
}
|
||||
});
|
||||
this.secondaryWindowService.onWindowOpened(window => {
|
||||
const codeWindow: CodeWindow = window as CodeWindow;
|
||||
codeWindow.vscodeWindowId = this.nextWindowId++;
|
||||
|
||||
this.windowsById.set(codeWindow.vscodeWindowId, registerWindow(codeWindow));
|
||||
});
|
||||
|
||||
this.secondaryWindowService.onWindowClosed(window => {
|
||||
const codeWindow: CodeWindow = window as CodeWindow;
|
||||
this.windowsById.get(codeWindow.vscodeWindowId)?.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
registerThemeStyle(theme: ColorTheme, collector: CssStyleCollector): void {
|
||||
if (isHighContrast(theme.type)) {
|
||||
const focusBorder = theme.getColor('focusBorder');
|
||||
const contrastBorder = theme.getColor('contrastBorder');
|
||||
if (focusBorder) {
|
||||
// Quick input
|
||||
collector.addRule(`
|
||||
.quick-input-list .monaco-list-row {
|
||||
outline-offset: -1px;
|
||||
}
|
||||
.quick-input-list .monaco-list-row.focused {
|
||||
outline: 1px dotted ${focusBorder};
|
||||
}
|
||||
.quick-input-list .monaco-list-row:hover {
|
||||
outline: 1px dashed ${focusBorder};
|
||||
}
|
||||
`);
|
||||
// Input box always displays an outline, even when unfocused
|
||||
collector.addRule(`
|
||||
.monaco-editor .find-widget .monaco-inputbox {
|
||||
outline: var(--theia-border-width) solid;
|
||||
outline-offset: calc(-1 * var(--theia-border-width));
|
||||
outline-color: var(--theia-focusBorder);
|
||||
}
|
||||
`);
|
||||
}
|
||||
if (contrastBorder) {
|
||||
collector.addRule(`
|
||||
.quick-input-widget {
|
||||
outline: 1px solid ${contrastBorder};
|
||||
outline-offset: -1px;
|
||||
}
|
||||
`);
|
||||
}
|
||||
} else {
|
||||
collector.addRule(`
|
||||
.quick-input-widget {
|
||||
box-shadow: rgb(0 0 0 / 36%) 0px 0px 8px 2px;
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For reasons that are unclear, while most preferences that apply in editors are validated, a few are not.
|
||||
* There is a utility in `examples/api-samples/src/browser/monaco-editor-preferences/monaco-editor-preference-extractor.ts` to help determine which are not.
|
||||
* Check `src/vs/editor/common/config/editorOptions.ts` for constructor arguments and to make sure that the preference names used to extract constructors are still accurate.
|
||||
*/
|
||||
protected addAdditionalPreferenceValidations(): void {
|
||||
let editorIntConstructor: undefined | (new (...args: unknown[]) => IEditorOption<number, number>);
|
||||
let editorBoolConstructor: undefined | (new (...args: unknown[]) => IEditorOption<number, boolean>);
|
||||
let editorStringEnumConstructor: undefined | (new (...args: unknown[]) => IEditorOption<number, string>);
|
||||
for (const validator of editorOptionsRegistry) {
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any,max-len */
|
||||
if (editorIntConstructor && editorBoolConstructor && editorStringEnumConstructor) { break; }
|
||||
if (validator.name === 'acceptSuggestionOnCommitCharacter') {
|
||||
editorBoolConstructor = validator.constructor as any;
|
||||
} else if (validator.name === 'acceptSuggestionOnEnter') {
|
||||
editorStringEnumConstructor = validator.constructor as any;
|
||||
} else if (validator.name === 'accessibilityPageSize') {
|
||||
editorIntConstructor = validator.constructor as any;
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
}
|
||||
if (editorIntConstructor && editorBoolConstructor && editorStringEnumConstructor) {
|
||||
let id = 200; // Needs to be bigger than the biggest index in the EditorOption enum.
|
||||
editorOptionsRegistry.push(
|
||||
new editorIntConstructor(id++, 'tabSize', 4, 1, MAX_SAFE_INTEGER, editorGeneratedPreferenceProperties['editor.tabSize']),
|
||||
new editorBoolConstructor(id++, 'insertSpaces', true, editorGeneratedPreferenceProperties['editor.insertSpaces']),
|
||||
new editorBoolConstructor(id++, 'detectIndentation', true, editorGeneratedPreferenceProperties['editor.detectIndentation']),
|
||||
new editorBoolConstructor(id++, 'trimAutoWhitespace', true, editorGeneratedPreferenceProperties['editor.trimAutoWhitespace']),
|
||||
new editorBoolConstructor(id++, 'largeFileOptimizations', true, editorGeneratedPreferenceProperties['editor.largeFileOptimizations']),
|
||||
new editorStringEnumConstructor(id++, 'wordBasedSuggestions', 'matchingDocuments', editorGeneratedPreferenceProperties['editor.wordBasedSuggestions'].enum, editorGeneratedPreferenceProperties['editor.wordBasedSuggestions']),
|
||||
new editorBoolConstructor(id++, 'stablePeek', false, editorGeneratedPreferenceProperties['editor.stablePeek']),
|
||||
new editorIntConstructor(id++, 'maxTokenizationLineLength', 20000, 1, MAX_SAFE_INTEGER, editorGeneratedPreferenceProperties['editor.maxTokenizationLineLength']),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
352
packages/monaco/src/browser/monaco-frontend-module.ts
Normal file
352
packages/monaco/src/browser/monaco-frontend-module.ts
Normal file
@@ -0,0 +1,352 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import '../../src/browser/style/index.css';
|
||||
import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
MenuContribution, CommandContribution, quickInputServicePath, createPreferenceProxy,
|
||||
OVERRIDE_PROPERTY_PATTERN, PreferenceChange, PreferenceScope, PreferenceService,
|
||||
PreferenceSchemaService
|
||||
} from '@theia/core/lib/common';
|
||||
import {
|
||||
FrontendApplicationContribution, KeybindingContribution, QuickInputService, StylingParticipant, WebSocketConnectionProvider, UndoRedoHandler, WidgetStatusBarContribution
|
||||
} from '@theia/core/lib/browser';
|
||||
import { TextEditorProvider, DiffNavigatorProvider, TextEditor } from '@theia/editor/lib/browser';
|
||||
import { MonacoEditorProvider, MonacoEditorFactory, SaveParticipant } from './monaco-editor-provider';
|
||||
import { MonacoEditorMenuContribution } from './monaco-menu';
|
||||
import { MonacoEditorCommandHandlers } from './monaco-command';
|
||||
import { MonacoKeybindingContribution } from './monaco-keybinding';
|
||||
import { MonacoLanguages } from './monaco-languages';
|
||||
import { MonacoWorkspace } from './monaco-workspace';
|
||||
import { ActiveMonacoEditorContribution, MonacoEditorService, MonacoEditorServiceFactory, VSCodeContextKeyService, VSCodeThemeService } from './monaco-editor-service';
|
||||
import { MonacoTextModelService, MonacoEditorModelFactory, MonacoEditorModelFilter } from './monaco-text-model-service';
|
||||
import { MonacoContextMenuService } from './monaco-context-menu';
|
||||
import { MonacoOutlineContribution } from './monaco-outline-contribution';
|
||||
import { MonacoStatusBarContribution } from './monaco-status-bar-contribution';
|
||||
import { MonacoCommandService } from './monaco-command-service';
|
||||
import { MonacoCommandRegistry } from './monaco-command-registry';
|
||||
import { MonacoDiffNavigatorFactory } from './monaco-diff-navigator-factory';
|
||||
import { MonacoFrontendApplicationContribution } from './monaco-frontend-application-contribution';
|
||||
import MonacoTextmateModuleBinder from './textmate/monaco-textmate-frontend-bindings';
|
||||
import { MonacoBulkEditService } from './monaco-bulk-edit-service';
|
||||
import { MonacoOutlineDecorator } from './monaco-outline-decorator';
|
||||
import { OutlineTreeDecorator } from '@theia/outline-view/lib/browser/outline-decorator-service';
|
||||
import { MonacoSnippetSuggestProvider } from './monaco-snippet-suggest-provider';
|
||||
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
|
||||
import { MonacoContextKeyService } from './monaco-context-key-service';
|
||||
import { MonacoMimeService } from './monaco-mime-service';
|
||||
import { MimeService } from '@theia/core/lib/browser/mime-service';
|
||||
import { MonacoEditorServices } from './monaco-editor';
|
||||
import { MonacoColorRegistry } from './monaco-color-registry';
|
||||
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
||||
import { MonacoIconRegistry } from './monaco-icon-registry';
|
||||
import { IconRegistry } from '@theia/core/lib/browser/icon-registry';
|
||||
import { MonacoThemingService } from './monaco-theming-service';
|
||||
import { bindContributionProvider } from '@theia/core';
|
||||
import { WorkspaceSymbolCommand } from './workspace-symbol-command';
|
||||
import { LanguageService } from '@theia/core/lib/browser/language-service';
|
||||
import { MonacoToProtocolConverter } from './monaco-to-protocol-converter';
|
||||
import { ProtocolToMonacoConverter } from './protocol-to-monaco-converter';
|
||||
import { MonacoFormattingConflictsContribution } from './monaco-formatting-conflicts';
|
||||
import { MonacoQuickInputImplementation, MonacoQuickInputService } from './monaco-quick-input-service';
|
||||
import { GotoLineQuickAccessContribution } from './monaco-gotoline-quick-access';
|
||||
import { GotoSymbolQuickAccessContribution } from './monaco-gotosymbol-quick-access';
|
||||
import { QuickAccessContribution, QuickAccessRegistry } from '@theia/core/lib/browser/quick-input/quick-access';
|
||||
import { MonacoQuickAccessRegistry } from './monaco-quick-access-registry';
|
||||
import { ConfigurationTarget, IConfigurationChangeEvent, IConfigurationService } from '@theia/monaco-editor-core/esm/vs/platform/configuration/common/configuration.js';
|
||||
import { StandaloneConfigurationService, StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { Configuration } from '@theia/monaco-editor-core/esm/vs/platform/configuration/common/configurationModels.js';
|
||||
import { MarkdownRenderer } from '@theia/core/lib/browser/markdown-rendering/markdown-renderer';
|
||||
import { MonacoMarkdownRenderer } from './markdown-renderer/monaco-markdown-renderer';
|
||||
import { ThemeService } from '@theia/core/lib/browser/theming';
|
||||
import { ThemeServiceWithDB } from './monaco-indexed-db';
|
||||
import { IContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey.js';
|
||||
import { IThemeService } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService.js';
|
||||
import { ActiveMonacoUndoRedoHandler, FocusedMonacoUndoRedoHandler } from './monaco-undo-redo-handler';
|
||||
import { ILogService } from '@theia/monaco-editor-core/esm/vs/platform/log/common/log';
|
||||
import { DefaultContentHoverWidgetPatcher } from './default-content-hover-widget-patcher';
|
||||
import { MonacoWorkspaceContextService } from './monaco-workspace-context-service';
|
||||
import { MonacoCodeActionSaveParticipant } from './monaco-code-action-save-participant';
|
||||
import { MonacoCodeActionService, MonacoCodeActionServiceImpl } from './monaco-code-action-service';
|
||||
import { MonacoDiffComputer } from './monaco-diff-computer';
|
||||
import { DiffComputer } from '@theia/core/lib/common/diff';
|
||||
import { MonacoEditorContentMenuContribution } from './monaco-editor-content-menu';
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(MonacoThemingService).toSelf().inSingletonScope();
|
||||
|
||||
bind(MonacoContextKeyService).toSelf().inSingletonScope();
|
||||
rebind(ContextKeyService).toService(MonacoContextKeyService);
|
||||
|
||||
bind(MonacoSnippetSuggestProvider).toSelf().inSingletonScope();
|
||||
bind(MonacoFrontendApplicationContribution).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(MonacoFrontendApplicationContribution);
|
||||
bind(StylingParticipant).toService(MonacoFrontendApplicationContribution);
|
||||
|
||||
bind(MonacoCodeActionServiceImpl).toSelf().inSingletonScope();
|
||||
bind(MonacoCodeActionService).toService(MonacoCodeActionServiceImpl);
|
||||
bind(MonacoCodeActionSaveParticipant).toSelf().inSingletonScope();
|
||||
bind(SaveParticipant).toService(MonacoCodeActionSaveParticipant);
|
||||
|
||||
bind(MonacoToProtocolConverter).toSelf().inSingletonScope();
|
||||
bind(ProtocolToMonacoConverter).toSelf().inSingletonScope();
|
||||
|
||||
bind(MonacoLanguages).toSelf().inSingletonScope();
|
||||
rebind(LanguageService).toService(MonacoLanguages);
|
||||
bind(WorkspaceSymbolCommand).toSelf().inSingletonScope();
|
||||
for (const identifier of [CommandContribution, KeybindingContribution, MenuContribution, QuickAccessContribution]) {
|
||||
bind(identifier).toService(WorkspaceSymbolCommand);
|
||||
}
|
||||
|
||||
bind(MonacoWorkspace).toSelf().inSingletonScope();
|
||||
bind(MonacoWorkspaceContextService).toSelf().inSingletonScope();
|
||||
|
||||
bind(MonacoConfigurationService).toDynamicValue(({ container }) => createMonacoConfigurationService(container)).inSingletonScope();
|
||||
|
||||
bind(MonacoBulkEditService).toSelf().inSingletonScope();
|
||||
bindContributionProvider(bind, ActiveMonacoEditorContribution);
|
||||
bind(MonacoEditorServiceFactory).toFactory((context: interfaces.Context) => (contextKeyService: IContextKeyService, themeService: IThemeService) => {
|
||||
const child = context.container.createChild();
|
||||
child.bind(VSCodeContextKeyService).toConstantValue(contextKeyService);
|
||||
child.bind(VSCodeThemeService).toConstantValue(themeService);
|
||||
child.bind(MonacoEditorService).toSelf().inSingletonScope();
|
||||
return child.get(MonacoEditorService);
|
||||
});
|
||||
bind(MonacoTextModelService).toSelf().inSingletonScope();
|
||||
bind(MonacoContextMenuService).toSelf().inSingletonScope();
|
||||
bind(MonacoEditorServices).toSelf().inSingletonScope();
|
||||
bind(MonacoEditorProvider).toSelf().inSingletonScope();
|
||||
bindContributionProvider(bind, MonacoEditorFactory);
|
||||
bindContributionProvider(bind, MonacoEditorModelFactory);
|
||||
bindContributionProvider(bind, MonacoEditorModelFilter);
|
||||
bindContributionProvider(bind, SaveParticipant);
|
||||
bind(MonacoCommandService).toSelf().inTransientScope();
|
||||
|
||||
bind(TextEditorProvider).toProvider(context =>
|
||||
uri => context.container.get(MonacoEditorProvider).get(uri)
|
||||
);
|
||||
|
||||
bind(MonacoDiffNavigatorFactory).toSelf().inSingletonScope();
|
||||
bind(DiffNavigatorProvider).toFactory(context =>
|
||||
(editor: TextEditor) => context.container.get(MonacoEditorProvider).getDiffNavigator(editor)
|
||||
);
|
||||
|
||||
bind(MonacoOutlineContribution).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(MonacoOutlineContribution);
|
||||
|
||||
rebind(MarkdownRenderer).to(MonacoMarkdownRenderer).inSingletonScope();
|
||||
|
||||
bind(MonacoFormattingConflictsContribution).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(MonacoFormattingConflictsContribution);
|
||||
|
||||
bind(MonacoStatusBarContribution).toSelf().inSingletonScope();
|
||||
bind(WidgetStatusBarContribution).toService(MonacoStatusBarContribution);
|
||||
|
||||
bind(MonacoCommandRegistry).toSelf().inSingletonScope();
|
||||
bind(MonacoEditorCommandHandlers).toSelf().inSingletonScope();
|
||||
bind(CommandContribution).toService(MonacoEditorCommandHandlers);
|
||||
bind(MonacoEditorMenuContribution).toSelf().inSingletonScope();
|
||||
bind(MenuContribution).toService(MonacoEditorMenuContribution);
|
||||
bind(MonacoKeybindingContribution).toSelf().inSingletonScope();
|
||||
bind(KeybindingContribution).toService(MonacoKeybindingContribution);
|
||||
|
||||
bind(MonacoQuickInputImplementation).toSelf().inSingletonScope();
|
||||
bind(MonacoQuickInputService).toSelf().inSingletonScope().onActivation(({ container }, quickInputService: MonacoQuickInputService) => {
|
||||
WebSocketConnectionProvider.createHandler(container, quickInputServicePath, quickInputService);
|
||||
return quickInputService;
|
||||
});
|
||||
bind(QuickInputService).toService(MonacoQuickInputService);
|
||||
|
||||
bind(MonacoQuickAccessRegistry).toSelf().inSingletonScope();
|
||||
bind(QuickAccessRegistry).toService(MonacoQuickAccessRegistry);
|
||||
|
||||
bind(GotoLineQuickAccessContribution).toSelf().inSingletonScope();
|
||||
bind(QuickAccessContribution).toService(GotoLineQuickAccessContribution);
|
||||
|
||||
bind(GotoSymbolQuickAccessContribution).toSelf().inSingletonScope();
|
||||
bind(QuickAccessContribution).toService(GotoSymbolQuickAccessContribution);
|
||||
|
||||
MonacoTextmateModuleBinder(bind, unbind, isBound, rebind);
|
||||
|
||||
bind(MonacoOutlineDecorator).toSelf().inSingletonScope();
|
||||
bind(OutlineTreeDecorator).toService(MonacoOutlineDecorator);
|
||||
|
||||
bind(MonacoMimeService).toSelf().inSingletonScope();
|
||||
rebind(MimeService).toService(MonacoMimeService);
|
||||
|
||||
bind(MonacoColorRegistry).toSelf().inSingletonScope();
|
||||
rebind(ColorRegistry).toService(MonacoColorRegistry);
|
||||
|
||||
bind(ThemeServiceWithDB).toSelf().inSingletonScope();
|
||||
rebind(ThemeService).toService(ThemeServiceWithDB);
|
||||
|
||||
bind(MonacoIconRegistry).toSelf().inSingletonScope();
|
||||
bind(IconRegistry).toService(MonacoIconRegistry);
|
||||
|
||||
bind(FocusedMonacoUndoRedoHandler).toSelf().inSingletonScope();
|
||||
bind(ActiveMonacoUndoRedoHandler).toSelf().inSingletonScope();
|
||||
bind(UndoRedoHandler).toService(FocusedMonacoUndoRedoHandler);
|
||||
bind(UndoRedoHandler).toService(ActiveMonacoUndoRedoHandler);
|
||||
|
||||
bind(DefaultContentHoverWidgetPatcher).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(DefaultContentHoverWidgetPatcher);
|
||||
|
||||
bind(MonacoDiffComputer).toSelf().inSingletonScope();
|
||||
bind(DiffComputer).toService(MonacoDiffComputer);
|
||||
|
||||
bind(MonacoEditorContentMenuContribution).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).to(MonacoEditorContentMenuContribution);
|
||||
});
|
||||
|
||||
export const MonacoConfigurationService = Symbol('MonacoConfigurationService');
|
||||
export function createMonacoConfigurationService(container: interfaces.Container): IConfigurationService {
|
||||
const preferences = container.get<PreferenceService>(PreferenceService);
|
||||
const preferenceSchemaService = container.get<PreferenceSchemaService>(PreferenceSchemaService);
|
||||
const service = new StandaloneConfigurationService(StandaloneServices.get(ILogService));
|
||||
const _configuration: Configuration = service['_configuration'];
|
||||
|
||||
_configuration.getValue = (section, overrides) => {
|
||||
const overrideIdentifier: string | undefined = (overrides && 'overrideIdentifier' in overrides && typeof overrides.overrideIdentifier === 'string')
|
||||
? overrides['overrideIdentifier']
|
||||
: undefined;
|
||||
const resourceUri: string | undefined = (overrides && 'resource' in overrides && !!overrides['resource']) ? overrides['resource'].toString() : undefined;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const proxy = createPreferenceProxy<{ [key: string]: any }>(preferences, preferenceSchemaService.getJSONSchema(PreferenceScope.Folder), {
|
||||
resourceUri, overrideIdentifier, style: 'both'
|
||||
});
|
||||
if (section) {
|
||||
return proxy[section];
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
|
||||
/*
|
||||
* Since we never read values from the underlying service, writing to it doesn't make sense. The standalone editor writes to the configuration when being created,
|
||||
* which makes sense in the standalone case where there is no preference infrastructure in place. Those writes degrade the performance, however, so we patch the
|
||||
* service to an empty implementation.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
service.updateValues = (values: [string, any][]) => Promise.resolve();
|
||||
|
||||
/*
|
||||
* There are a few places in Monaco where this method is called from, including actions for editor minimap in `ContextMenuController`.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
service.updateValue = (key: string, value: any) => preferences.updateValue(key, value);
|
||||
|
||||
const toTarget = (scope: PreferenceScope): ConfigurationTarget => {
|
||||
switch (scope) {
|
||||
case PreferenceScope.Default: return ConfigurationTarget.DEFAULT;
|
||||
case PreferenceScope.User: return ConfigurationTarget.USER;
|
||||
case PreferenceScope.Workspace: return ConfigurationTarget.WORKSPACE;
|
||||
case PreferenceScope.Folder: return ConfigurationTarget.WORKSPACE_FOLDER;
|
||||
}
|
||||
};
|
||||
|
||||
interface FireDidChangeConfigurationContext {
|
||||
changes: PreferenceChange[];
|
||||
affectedKeys: Set<string>;
|
||||
keys: Set<string>;
|
||||
overrides: Map<string, Set<string>>
|
||||
}
|
||||
const newFireDidChangeConfigurationContext = (): FireDidChangeConfigurationContext => ({
|
||||
changes: [],
|
||||
affectedKeys: new Set<string>(),
|
||||
keys: new Set<string>(),
|
||||
overrides: new Map<string, Set<string>>()
|
||||
});
|
||||
const fireDidChangeConfiguration = (source: ConfigurationTarget, context: FireDidChangeConfigurationContext): void => {
|
||||
if (!context.affectedKeys.size) {
|
||||
return;
|
||||
}
|
||||
const overrides: [string, string[]][] = [];
|
||||
for (const [override, values] of context.overrides) {
|
||||
overrides.push([override, [...values]]);
|
||||
}
|
||||
service['_onDidChangeConfiguration'].fire(<IConfigurationChangeEvent>{
|
||||
sourceConfig: {},
|
||||
change: {
|
||||
keys: [...context.keys],
|
||||
overrides
|
||||
},
|
||||
affectedKeys: context.affectedKeys,
|
||||
source,
|
||||
affectsConfiguration: (prefix, options) => {
|
||||
if (!context.affectedKeys.has(prefix)) {
|
||||
return false;
|
||||
}
|
||||
for (const change of context.changes) {
|
||||
const overridden = preferences.overriddenPreferenceName(change.preferenceName);
|
||||
const preferenceName = overridden ? overridden.preferenceName : change.preferenceName;
|
||||
if (preferenceName.startsWith(prefix)) {
|
||||
if (options?.overrideIdentifier !== undefined) {
|
||||
if (overridden && overridden.overrideIdentifier !== options?.overrideIdentifier) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (change.affects(options?.resource?.toString())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
preferences.onPreferencesChanged(event => {
|
||||
let source: ConfigurationTarget | undefined;
|
||||
let context = newFireDidChangeConfigurationContext();
|
||||
for (let key of Object.keys(event)) {
|
||||
const change = event[key];
|
||||
const target = toTarget(change.scope);
|
||||
if (source !== undefined && target !== source) {
|
||||
fireDidChangeConfiguration(source, context);
|
||||
context = newFireDidChangeConfigurationContext();
|
||||
}
|
||||
context.changes.push(change);
|
||||
source = target;
|
||||
|
||||
let overrideKeys: Set<string> | undefined;
|
||||
if (key.startsWith('[')) {
|
||||
const index = key.indexOf('.');
|
||||
const override = key.substring(0, index);
|
||||
const overrideIdentifier = override.match(OVERRIDE_PROPERTY_PATTERN)?.[1];
|
||||
if (overrideIdentifier) {
|
||||
context.keys.add(override);
|
||||
context.affectedKeys.add(override);
|
||||
overrideKeys = context.overrides.get(overrideIdentifier) || new Set<string>();
|
||||
context.overrides.set(overrideIdentifier, overrideKeys);
|
||||
key = key.substring(index + 1);
|
||||
}
|
||||
}
|
||||
while (key) {
|
||||
if (overrideKeys) {
|
||||
overrideKeys.add(key);
|
||||
}
|
||||
context.keys.add(key);
|
||||
context.affectedKeys.add(key);
|
||||
const index = key.lastIndexOf('.');
|
||||
key = key.substring(0, index);
|
||||
}
|
||||
}
|
||||
if (source) {
|
||||
fireDidChangeConfiguration(source, context);
|
||||
}
|
||||
});
|
||||
|
||||
return service;
|
||||
}
|
||||
47
packages/monaco/src/browser/monaco-gotoline-quick-access.ts
Normal file
47
packages/monaco/src/browser/monaco-gotoline-quick-access.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input';
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { ICodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditorService } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/codeEditorService';
|
||||
import { StandaloneGotoLineQuickAccessProvider } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess';
|
||||
import { IQuickAccessRegistry, Extensions } from '@theia/monaco-editor-core/esm/vs/platform/quickinput/common/quickAccess';
|
||||
import { Registry } from '@theia/monaco-editor-core/esm/vs/platform/registry/common/platform';
|
||||
|
||||
export class GotoLineQuickAccess extends StandaloneGotoLineQuickAccessProvider {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
constructor(...services: any[]);
|
||||
constructor(@ICodeEditorService private readonly service: ICodeEditorService) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
override get activeTextEditorControl(): ICodeEditor | undefined {
|
||||
return (this.service.getFocusedCodeEditor() || this.service.getActiveCodeEditor()) ?? undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class GotoLineQuickAccessContribution implements QuickAccessContribution {
|
||||
registerQuickAccessProvider(): void {
|
||||
Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess).registerQuickAccessProvider({
|
||||
ctor: GotoLineQuickAccess,
|
||||
prefix: ':',
|
||||
placeholder: '',
|
||||
helpEntries: [{ description: 'Go to line' }]
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input';
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { ICodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditorService } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/codeEditorService';
|
||||
import { ILanguageFeaturesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageFeatures';
|
||||
import { IOutlineModelService } from '@theia/monaco-editor-core/esm/vs/editor/contrib/documentSymbols/browser/outlineModel';
|
||||
import { StandaloneGotoSymbolQuickAccessProvider } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess';
|
||||
import { IQuickAccessRegistry, Extensions } from '@theia/monaco-editor-core/esm/vs/platform/quickinput/common/quickAccess';
|
||||
import { Registry } from '@theia/monaco-editor-core/esm/vs/platform/registry/common/platform';
|
||||
|
||||
export class GotoSymbolQuickAccess extends StandaloneGotoSymbolQuickAccessProvider {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
constructor(...services: any[]);
|
||||
constructor(
|
||||
@ICodeEditorService protected readonly codeEditorService: ICodeEditorService,
|
||||
@ILanguageFeaturesService protected readonly languageFeatures: ILanguageFeaturesService,
|
||||
@IOutlineModelService protected readonly outlineService: IOutlineModelService,
|
||||
) {
|
||||
super(codeEditorService, languageFeatures, outlineService);
|
||||
}
|
||||
|
||||
override get activeTextEditorControl(): ICodeEditor | undefined {
|
||||
return (this.codeEditorService.getFocusedCodeEditor() ?? this.codeEditorService.getActiveCodeEditor()) ?? undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class GotoSymbolQuickAccessContribution implements QuickAccessContribution {
|
||||
registerQuickAccessProvider(): void {
|
||||
Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess).registerQuickAccessProvider({
|
||||
ctor: GotoSymbolQuickAccess,
|
||||
prefix: '@',
|
||||
placeholder: '',
|
||||
helpEntries: [{ description: 'Go to symbol' }]
|
||||
});
|
||||
}
|
||||
}
|
||||
49
packages/monaco/src/browser/monaco-icon-registry.ts
Normal file
49
packages/monaco/src/browser/monaco-icon-registry.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2023 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { IconRegistry } from '@theia/core/lib/browser/icon-registry';
|
||||
import { getIconRegistry } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/iconRegistry';
|
||||
import { IconDefinition, IconFontDefinition, ThemeIcon } from '@theia/core/lib/common/theme';
|
||||
|
||||
@injectable()
|
||||
export class MonacoIconRegistry implements IconRegistry {
|
||||
|
||||
protected readonly iconRegistry = getIconRegistry();
|
||||
|
||||
registerIcon(id: string, defaults: ThemeIcon | IconDefinition, description?: string): ThemeIcon {
|
||||
return this.iconRegistry.registerIcon(id, defaults, description);
|
||||
}
|
||||
|
||||
deregisterIcon(id: string): void {
|
||||
return this.iconRegistry.deregisterIcon(id);
|
||||
}
|
||||
|
||||
registerIconFont(id: string, definition: IconFontDefinition): IconFontDefinition {
|
||||
// need to cast because of vscode issue https://github.com/microsoft/vscode/issues/190584
|
||||
return this.iconRegistry.registerIconFont(id, definition) as IconFontDefinition;
|
||||
}
|
||||
|
||||
deregisterIconFont(id: string): void {
|
||||
return this.iconRegistry.deregisterIconFont(id);
|
||||
}
|
||||
|
||||
getIconFont(id: string): IconFontDefinition | undefined {
|
||||
// need to cast because of vscode issue https://github.com/microsoft/vscode/issues/190584
|
||||
return this.iconRegistry.getIconFont(id) as IconFontDefinition;
|
||||
}
|
||||
}
|
||||
|
||||
130
packages/monaco/src/browser/monaco-indexed-db.ts
Normal file
130
packages/monaco/src/browser/monaco-indexed-db.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import * as idb from 'idb';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { ThemeService } from '@theia/core/lib/browser/theming';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import type { ThemeMix } from './textmate/monaco-theme-types';
|
||||
import { Theme } from '@theia/core/lib/common/theme';
|
||||
import { Emitter, Event, isObject } from '@theia/core';
|
||||
|
||||
let _monacoDB: Promise<idb.IDBPDatabase> | undefined;
|
||||
if ('indexedDB' in window) {
|
||||
_monacoDB = idb.openDB('theia-monaco', 1, {
|
||||
upgrade: db => {
|
||||
if (!db.objectStoreNames.contains('themes')) {
|
||||
db.createObjectStore('themes', { keyPath: 'id' });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const monacoDB = _monacoDB;
|
||||
|
||||
export interface MonacoThemeState {
|
||||
id: string,
|
||||
label: string,
|
||||
description?: string,
|
||||
uiTheme: monaco.editor.BuiltinTheme
|
||||
data: ThemeMix
|
||||
}
|
||||
export namespace MonacoThemeState {
|
||||
export function is(state: unknown): state is MonacoThemeState {
|
||||
return isObject(state) && 'id' in state && 'label' in state && 'uiTheme' in state && 'data' in state;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getThemes(): Promise<MonacoThemeState[]> {
|
||||
if (!monacoDB) {
|
||||
return [];
|
||||
}
|
||||
const db = await monacoDB;
|
||||
const result = await db.transaction('themes', 'readonly').objectStore('themes').getAll();
|
||||
return result.filter(MonacoThemeState.is);
|
||||
}
|
||||
|
||||
export function putTheme(state: MonacoThemeState): Disposable {
|
||||
const toDispose = new DisposableCollection(Disposable.create(() => { /* mark as not disposed */ }));
|
||||
doPutTheme(state, toDispose);
|
||||
return toDispose;
|
||||
}
|
||||
async function doPutTheme(state: MonacoThemeState, toDispose: DisposableCollection): Promise<void> {
|
||||
if (!monacoDB) {
|
||||
return;
|
||||
}
|
||||
const db = await monacoDB;
|
||||
if (toDispose.disposed) {
|
||||
return;
|
||||
}
|
||||
const id = state.id;
|
||||
await db.transaction('themes', 'readwrite').objectStore('themes').put(state);
|
||||
if (toDispose.disposed) {
|
||||
await deleteTheme(id);
|
||||
return;
|
||||
}
|
||||
toDispose.push(Disposable.create(() => deleteTheme(id)));
|
||||
}
|
||||
|
||||
export async function deleteTheme(id: string): Promise<void> {
|
||||
if (!monacoDB) {
|
||||
return;
|
||||
}
|
||||
const db = await monacoDB;
|
||||
await db.transaction('themes', 'readwrite').objectStore('themes').delete(id);
|
||||
}
|
||||
|
||||
export function stateToTheme(state: MonacoThemeState): Theme {
|
||||
const { id, label, description, uiTheme, data } = state;
|
||||
const type = uiTheme === 'vs' ? 'light' : uiTheme === 'vs-dark' ? 'dark' : 'hc';
|
||||
return {
|
||||
type,
|
||||
id,
|
||||
label,
|
||||
description,
|
||||
editorTheme: data.name!
|
||||
};
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ThemeServiceWithDB extends ThemeService {
|
||||
protected onDidRetrieveThemeEmitter = new Emitter<MonacoThemeState>();
|
||||
get onDidRetrieveTheme(): Event<MonacoThemeState> {
|
||||
return this.onDidRetrieveThemeEmitter.event;
|
||||
}
|
||||
|
||||
override loadUserTheme(): void {
|
||||
this.loadUserThemeWithDB();
|
||||
}
|
||||
|
||||
protected async loadUserThemeWithDB(): Promise<void> {
|
||||
const themeId = window.localStorage.getItem(ThemeService.STORAGE_KEY) ?? this.defaultTheme.id;
|
||||
const theme = this.themes[themeId] ?? await getThemes().then(themes => {
|
||||
const matchingTheme = themes.find(candidate => candidate.id === themeId);
|
||||
if (matchingTheme) {
|
||||
this.onDidRetrieveThemeEmitter.fire(matchingTheme);
|
||||
return stateToTheme(matchingTheme);
|
||||
}
|
||||
}) ?? this.getTheme(themeId);
|
||||
// In case the theme comes from the DB.
|
||||
if (!this.themes[theme.id]) {
|
||||
this.themes[theme.id] = theme;
|
||||
}
|
||||
this.setCurrentTheme(theme.id, false);
|
||||
this.deferredInitializer.resolve();
|
||||
}
|
||||
}
|
||||
171
packages/monaco/src/browser/monaco-init.ts
Normal file
171
packages/monaco/src/browser/monaco-init.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2023 STMicroelectronics and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
/*
|
||||
* The code in this file is responsible for overriding service implementations in the Monaco editor with our own Theia-based implementations.
|
||||
* Since we only get a single chance to call `StandaloneServices.initialize()` with our overrides, we need to make sure that initialize is called before the first call to
|
||||
* `StandaloneServices.get()` or `StandaloneServices.initialize()`. As we do not control the mechanics of Inversify instance constructions, the approach here is to call
|
||||
* `MonacoInit.init()` from the `index.js` file after all container modules are loaded, but before the first object is fetched from it.
|
||||
* `StandaloneServices.initialize()` is called with service descriptors, not service instances. This lets us finish all overrides before any inversify object is constructed and
|
||||
* might call `initialize()` while being constructed.
|
||||
* The service descriptors require a constructor function, so we declare dummy class for each Monaco service we override. But instead of returning an instance of the dummy class,
|
||||
* we fetch the implementation of the monaco service from the inversify container.
|
||||
* The inversify-constructed services must not call StandaloneServices.get() or StandaloneServices.initialize() from their constructors. Calling `get`()` in postConstruct methods
|
||||
* is allowed.
|
||||
*/
|
||||
|
||||
// Before importing anything from monaco we need to override its localization function
|
||||
import * as MonacoNls from '@theia/monaco-editor-core/esm/vs/nls';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { FormatType, Localization } from '@theia/core/lib/common/i18n/localization';
|
||||
|
||||
function localize(label: string, ...args: FormatType[]): MonacoNls.ILocalizedString {
|
||||
const original = Localization.format(label, args);
|
||||
if (nls.locale) {
|
||||
const defaultKey = nls.getDefaultKey(label);
|
||||
if (defaultKey) {
|
||||
return {
|
||||
original,
|
||||
value: nls.localize(defaultKey, label, ...args)
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
original,
|
||||
value: original
|
||||
};
|
||||
}
|
||||
|
||||
Object.assign(MonacoNls, {
|
||||
localize(_key: string, label: string, ...args: FormatType[]): string {
|
||||
return localize(label, ...args).value;
|
||||
},
|
||||
localize2(_key: string, label: string, ...args: FormatType[]): MonacoNls.ILocalizedString {
|
||||
return localize(label, ...args);
|
||||
}
|
||||
});
|
||||
|
||||
import { Container } from '@theia/core/shared/inversify';
|
||||
import { ICodeEditorService } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/codeEditorService';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { SyncDescriptor } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/descriptors';
|
||||
import { MonacoEditorServiceFactory, MonacoEditorServiceFactoryType } from './monaco-editor-service';
|
||||
import { IConfigurationService } from '@theia/monaco-editor-core/esm/vs/platform/configuration/common/configuration';
|
||||
import { ITextModelService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/resolverService';
|
||||
import { MonacoConfigurationService } from './monaco-frontend-module';
|
||||
import { MonacoTextModelService } from './monaco-text-model-service';
|
||||
import { MonacoContextMenuService } from './monaco-context-menu';
|
||||
import { IContextMenuService } from '@theia/monaco-editor-core/esm/vs/platform/contextview/browser/contextView';
|
||||
import { IContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';
|
||||
import { IThemeService } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService';
|
||||
import { MonacoBulkEditService } from './monaco-bulk-edit-service';
|
||||
import { MonacoCommandService } from './monaco-command-service';
|
||||
import { IBulkEditService } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/bulkEditService';
|
||||
import { ICommandService } from '@theia/monaco-editor-core/esm/vs/platform/commands/common/commands';
|
||||
import { MonacoQuickInputImplementation } from './monaco-quick-input-service';
|
||||
import { IQuickInputService } from '@theia/monaco-editor-core/esm/vs/platform/quickinput/common/quickInput';
|
||||
import { IStandaloneThemeService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/common/standaloneTheme';
|
||||
import { MonacoStandaloneThemeService } from './monaco-standalone-theme-service';
|
||||
import { createContentHoverWidgetPatcher } from './content-hover-widget-patcher';
|
||||
import { IHoverService } from '@theia/monaco-editor-core/esm/vs/platform/hover/browser/hover';
|
||||
import { setBaseLayerHoverDelegate } from '@theia/monaco-editor-core/esm/vs/base/browser/ui/hover/hoverDelegate2';
|
||||
import { IWorkspaceContextService } from '@theia/monaco-editor-core/esm/vs/platform/workspace/common/workspace';
|
||||
import { MonacoWorkspaceContextService } from './monaco-workspace-context-service';
|
||||
|
||||
export const contentHoverWidgetPatcher = createContentHoverWidgetPatcher();
|
||||
|
||||
class MonacoEditorServiceConstructor {
|
||||
/**
|
||||
* MonacoEditorService needs other Monaco services as constructor parameters, so we need to do use a factory for constructing the service. If we want the singleton instance,
|
||||
* we need to fetch it from the `StandaloneServices` class instead of injecting it.
|
||||
* @param container
|
||||
* @param contextKeyService
|
||||
* @param themeService
|
||||
*/
|
||||
constructor(container: Container,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IThemeService themeService: IThemeService) {
|
||||
|
||||
return container.get<MonacoEditorServiceFactoryType>(MonacoEditorServiceFactory)(contextKeyService, themeService);
|
||||
};
|
||||
}
|
||||
|
||||
class MonacoConfigurationServiceConstructor {
|
||||
constructor(container: Container) {
|
||||
return container.get(MonacoConfigurationService);
|
||||
}
|
||||
}
|
||||
|
||||
class MonacoTextModelServiceConstructor {
|
||||
constructor(container: Container) {
|
||||
return container.get(MonacoTextModelService);
|
||||
}
|
||||
}
|
||||
|
||||
class MonacoContextMenuServiceConstructor {
|
||||
constructor(container: Container) {
|
||||
return container.get(MonacoContextMenuService);
|
||||
}
|
||||
}
|
||||
|
||||
class MonacoBulkEditServiceConstructor {
|
||||
constructor(container: Container) {
|
||||
return container.get(MonacoBulkEditService);
|
||||
}
|
||||
}
|
||||
|
||||
class MonacoCommandServiceConstructor {
|
||||
constructor(container: Container) {
|
||||
return container.get(MonacoCommandService);
|
||||
}
|
||||
}
|
||||
|
||||
class MonacoQuickInputImplementationConstructor {
|
||||
constructor(container: Container) {
|
||||
return container.get(MonacoQuickInputImplementation);
|
||||
}
|
||||
}
|
||||
|
||||
class MonacoStandaloneThemeServiceConstructor {
|
||||
constructor(container: Container) {
|
||||
return new MonacoStandaloneThemeService();
|
||||
}
|
||||
}
|
||||
|
||||
class MonacoWorkspaceContextServiceConstructor {
|
||||
constructor(container: Container) {
|
||||
return container.get(MonacoWorkspaceContextService);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace MonacoInit {
|
||||
export function init(container: Container): void {
|
||||
StandaloneServices.initialize({
|
||||
[ICodeEditorService.toString()]: new SyncDescriptor(MonacoEditorServiceConstructor, [container]),
|
||||
[IConfigurationService.toString()]: new SyncDescriptor(MonacoConfigurationServiceConstructor, [container]),
|
||||
[ITextModelService.toString()]: new SyncDescriptor(MonacoTextModelServiceConstructor, [container]),
|
||||
[IContextMenuService.toString()]: new SyncDescriptor(MonacoContextMenuServiceConstructor, [container]),
|
||||
[IBulkEditService.toString()]: new SyncDescriptor(MonacoBulkEditServiceConstructor, [container]),
|
||||
[ICommandService.toString()]: new SyncDescriptor(MonacoCommandServiceConstructor, [container]),
|
||||
[IQuickInputService.toString()]: new SyncDescriptor(MonacoQuickInputImplementationConstructor, [container]),
|
||||
[IStandaloneThemeService.toString()]: new SyncDescriptor(MonacoStandaloneThemeServiceConstructor, []),
|
||||
[IWorkspaceContextService.toString()]: new SyncDescriptor(MonacoWorkspaceContextServiceConstructor, [container])
|
||||
});
|
||||
// Make sure the global base hover delegate is initialized as otherwise the quick input will throw an error and not update correctly
|
||||
// in case no Monaco editor was constructed before and items with keybindings are shown. See #15042.
|
||||
setBaseLayerHoverDelegate(StandaloneServices.get(IHoverService));
|
||||
}
|
||||
}
|
||||
111
packages/monaco/src/browser/monaco-keybinding.ts
Normal file
111
packages/monaco/src/browser/monaco-keybinding.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
||||
import { KeybindingContribution, KeybindingRegistry, KeybindingScope, KeyCode } from '@theia/core/lib/browser';
|
||||
import { MonacoCommands } from './monaco-command';
|
||||
import { MonacoCommandRegistry } from './monaco-command-registry';
|
||||
import { CommandRegistry, DisposableCollection, environment, isOSX } from '@theia/core';
|
||||
import { MonacoResolvedKeybinding } from './monaco-resolved-keybinding';
|
||||
import { KeybindingsRegistry } from '@theia/monaco-editor-core/esm/vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { StandaloneKeybindingService, StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { IKeybindingService } from '@theia/monaco-editor-core/esm/vs/platform/keybinding/common/keybinding';
|
||||
import { MonacoContextKeyService } from './monaco-context-key-service';
|
||||
import { KEY_CODE_MAP } from './monaco-keycode-map';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
|
||||
@injectable()
|
||||
export class MonacoKeybindingContribution implements KeybindingContribution {
|
||||
|
||||
protected toDisposeOnKeybindingChange = new DisposableCollection();
|
||||
|
||||
@inject(MonacoCommandRegistry) protected readonly commands: MonacoCommandRegistry;
|
||||
@inject(KeybindingRegistry) protected readonly keybindings: KeybindingRegistry;
|
||||
@inject(CommandRegistry) protected readonly theiaCommandRegistry: CommandRegistry;
|
||||
@inject(MonacoContextKeyService) protected readonly contextKeyService: MonacoContextKeyService;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.keybindings.onKeybindingsChanged(() => this.updateMonacoKeybindings());
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
const defaultKeybindings = KeybindingsRegistry.getDefaultKeybindings();
|
||||
for (const item of defaultKeybindings) {
|
||||
const command = this.commands.validate(item.command || undefined);
|
||||
if (command && item.keybinding) {
|
||||
const when = (item.when && item.when.serialize()) ?? undefined;
|
||||
let keybinding;
|
||||
if (item.command === MonacoCommands.GO_TO_DEFINITION && !environment.electron.is()) {
|
||||
keybinding = 'ctrlcmd+f11';
|
||||
} else {
|
||||
keybinding = MonacoResolvedKeybinding.toKeybinding(item.keybinding.chords);
|
||||
}
|
||||
registry.registerKeybinding({ command, keybinding, when });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected updateMonacoKeybindings(): void {
|
||||
const monacoKeybindingRegistry = StandaloneServices.get(IKeybindingService);
|
||||
if (monacoKeybindingRegistry instanceof StandaloneKeybindingService) {
|
||||
this.toDisposeOnKeybindingChange.dispose();
|
||||
for (const binding of this.keybindings.getKeybindingsByScope(KeybindingScope.USER).concat(this.keybindings.getKeybindingsByScope(KeybindingScope.WORKSPACE))) {
|
||||
const resolved = this.keybindings.resolveKeybinding(binding);
|
||||
const command = binding.command;
|
||||
const when = binding.when
|
||||
? this.contextKeyService.parse(binding.when)
|
||||
: binding.context
|
||||
? this.contextKeyService.parse(binding.context)
|
||||
: undefined;
|
||||
this.toDisposeOnKeybindingChange.push(monacoKeybindingRegistry.addDynamicKeybinding(
|
||||
binding.command,
|
||||
this.toMonacoKeybindingNumber(resolved),
|
||||
(_, ...args) => this.theiaCommandRegistry.executeCommand(command, ...args),
|
||||
when,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected toMonacoKeybindingNumber(codes: KeyCode[]): number {
|
||||
const [firstPart, secondPart] = codes;
|
||||
if (codes.length > 2) {
|
||||
console.warn('Key chords should not consist of more than two parts; got ', codes);
|
||||
}
|
||||
const encodedFirstPart = this.toSingleMonacoKeybindingNumber(firstPart);
|
||||
const encodedSecondPart = secondPart ? this.toSingleMonacoKeybindingNumber(secondPart) << 16 : 0;
|
||||
return monaco.KeyMod.chord(encodedFirstPart, encodedSecondPart);
|
||||
}
|
||||
|
||||
protected toSingleMonacoKeybindingNumber(code: KeyCode): number {
|
||||
const keyCode = code.key?.keyCode !== undefined ? KEY_CODE_MAP[code.key.keyCode] : 0;
|
||||
let encoded = (keyCode >>> 0) & 0x000000FF;
|
||||
if (code.alt) {
|
||||
encoded |= monaco.KeyMod.Alt;
|
||||
}
|
||||
if (code.shift) {
|
||||
encoded |= monaco.KeyMod.Shift;
|
||||
}
|
||||
if (code.ctrl) {
|
||||
encoded |= monaco.KeyMod.WinCtrl;
|
||||
}
|
||||
if (code.meta && isOSX) {
|
||||
encoded |= monaco.KeyMod.CtrlCmd;
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
}
|
||||
171
packages/monaco/src/browser/monaco-keycode-map.ts
Normal file
171
packages/monaco/src/browser/monaco-keycode-map.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as browser from '@theia/core/lib/browser';
|
||||
// This is exported as part of the public API, but we use it with private API's so we need to refer to the private version.
|
||||
import { KeyCode } from '@theia/monaco-editor-core/esm/vs/base/common/keyCodes';
|
||||
import * as MonacoPlatform from '@theia/monaco-editor-core/esm/vs/base/common/platform';
|
||||
|
||||
export const KEY_CODE_MAP: KeyCode[] = [];
|
||||
(function (): void {
|
||||
KEY_CODE_MAP[3] = KeyCode.PauseBreak; // VK_CANCEL 0x03 Control-break processing
|
||||
KEY_CODE_MAP[8] = KeyCode.Backspace;
|
||||
KEY_CODE_MAP[9] = KeyCode.Tab;
|
||||
KEY_CODE_MAP[13] = KeyCode.Enter;
|
||||
KEY_CODE_MAP[16] = KeyCode.Shift;
|
||||
KEY_CODE_MAP[17] = KeyCode.Ctrl;
|
||||
KEY_CODE_MAP[18] = KeyCode.Alt;
|
||||
KEY_CODE_MAP[19] = KeyCode.PauseBreak;
|
||||
KEY_CODE_MAP[20] = KeyCode.CapsLock;
|
||||
KEY_CODE_MAP[27] = KeyCode.Escape;
|
||||
KEY_CODE_MAP[32] = KeyCode.Space;
|
||||
KEY_CODE_MAP[33] = KeyCode.PageUp;
|
||||
KEY_CODE_MAP[34] = KeyCode.PageDown;
|
||||
KEY_CODE_MAP[35] = KeyCode.End;
|
||||
KEY_CODE_MAP[36] = KeyCode.Home;
|
||||
KEY_CODE_MAP[37] = KeyCode.LeftArrow;
|
||||
KEY_CODE_MAP[38] = KeyCode.UpArrow;
|
||||
KEY_CODE_MAP[39] = KeyCode.RightArrow;
|
||||
KEY_CODE_MAP[40] = KeyCode.DownArrow;
|
||||
KEY_CODE_MAP[45] = KeyCode.Insert;
|
||||
KEY_CODE_MAP[46] = KeyCode.Delete;
|
||||
|
||||
KEY_CODE_MAP[48] = KeyCode.Digit0;
|
||||
KEY_CODE_MAP[49] = KeyCode.Digit1;
|
||||
KEY_CODE_MAP[50] = KeyCode.Digit2;
|
||||
KEY_CODE_MAP[51] = KeyCode.Digit3;
|
||||
KEY_CODE_MAP[52] = KeyCode.Digit4;
|
||||
KEY_CODE_MAP[53] = KeyCode.Digit5;
|
||||
KEY_CODE_MAP[54] = KeyCode.Digit6;
|
||||
KEY_CODE_MAP[55] = KeyCode.Digit7;
|
||||
KEY_CODE_MAP[56] = KeyCode.Digit8;
|
||||
KEY_CODE_MAP[57] = KeyCode.Digit9;
|
||||
|
||||
KEY_CODE_MAP[65] = KeyCode.KeyA;
|
||||
KEY_CODE_MAP[66] = KeyCode.KeyB;
|
||||
KEY_CODE_MAP[67] = KeyCode.KeyC;
|
||||
KEY_CODE_MAP[68] = KeyCode.KeyD;
|
||||
KEY_CODE_MAP[69] = KeyCode.KeyE;
|
||||
KEY_CODE_MAP[70] = KeyCode.KeyF;
|
||||
KEY_CODE_MAP[71] = KeyCode.KeyG;
|
||||
KEY_CODE_MAP[72] = KeyCode.KeyH;
|
||||
KEY_CODE_MAP[73] = KeyCode.KeyI;
|
||||
KEY_CODE_MAP[74] = KeyCode.KeyJ;
|
||||
KEY_CODE_MAP[75] = KeyCode.KeyK;
|
||||
KEY_CODE_MAP[76] = KeyCode.KeyL;
|
||||
KEY_CODE_MAP[77] = KeyCode.KeyM;
|
||||
KEY_CODE_MAP[78] = KeyCode.KeyN;
|
||||
KEY_CODE_MAP[79] = KeyCode.KeyO;
|
||||
KEY_CODE_MAP[80] = KeyCode.KeyP;
|
||||
KEY_CODE_MAP[81] = KeyCode.KeyQ;
|
||||
KEY_CODE_MAP[82] = KeyCode.KeyR;
|
||||
KEY_CODE_MAP[83] = KeyCode.KeyS;
|
||||
KEY_CODE_MAP[84] = KeyCode.KeyT;
|
||||
KEY_CODE_MAP[85] = KeyCode.KeyU;
|
||||
KEY_CODE_MAP[86] = KeyCode.KeyV;
|
||||
KEY_CODE_MAP[87] = KeyCode.KeyW;
|
||||
KEY_CODE_MAP[88] = KeyCode.KeyX;
|
||||
KEY_CODE_MAP[89] = KeyCode.KeyY;
|
||||
KEY_CODE_MAP[90] = KeyCode.KeyZ;
|
||||
|
||||
KEY_CODE_MAP[93] = KeyCode.ContextMenu;
|
||||
|
||||
KEY_CODE_MAP[96] = KeyCode.Numpad0;
|
||||
KEY_CODE_MAP[97] = KeyCode.Numpad1;
|
||||
KEY_CODE_MAP[98] = KeyCode.Numpad2;
|
||||
KEY_CODE_MAP[99] = KeyCode.Numpad3;
|
||||
KEY_CODE_MAP[100] = KeyCode.Numpad4;
|
||||
KEY_CODE_MAP[101] = KeyCode.Numpad5;
|
||||
KEY_CODE_MAP[102] = KeyCode.Numpad6;
|
||||
KEY_CODE_MAP[103] = KeyCode.Numpad7;
|
||||
KEY_CODE_MAP[104] = KeyCode.Numpad8;
|
||||
KEY_CODE_MAP[105] = KeyCode.Numpad9;
|
||||
KEY_CODE_MAP[106] = KeyCode.NumpadMultiply;
|
||||
KEY_CODE_MAP[107] = KeyCode.NumpadAdd;
|
||||
KEY_CODE_MAP[108] = KeyCode.NUMPAD_SEPARATOR;
|
||||
KEY_CODE_MAP[109] = KeyCode.NumpadSubtract;
|
||||
KEY_CODE_MAP[110] = KeyCode.NumpadDecimal;
|
||||
KEY_CODE_MAP[111] = KeyCode.NumpadDivide;
|
||||
|
||||
KEY_CODE_MAP[112] = KeyCode.F1;
|
||||
KEY_CODE_MAP[113] = KeyCode.F2;
|
||||
KEY_CODE_MAP[114] = KeyCode.F3;
|
||||
KEY_CODE_MAP[115] = KeyCode.F4;
|
||||
KEY_CODE_MAP[116] = KeyCode.F5;
|
||||
KEY_CODE_MAP[117] = KeyCode.F6;
|
||||
KEY_CODE_MAP[118] = KeyCode.F7;
|
||||
KEY_CODE_MAP[119] = KeyCode.F8;
|
||||
KEY_CODE_MAP[120] = KeyCode.F9;
|
||||
KEY_CODE_MAP[121] = KeyCode.F10;
|
||||
KEY_CODE_MAP[122] = KeyCode.F11;
|
||||
KEY_CODE_MAP[123] = KeyCode.F12;
|
||||
KEY_CODE_MAP[124] = KeyCode.F13;
|
||||
KEY_CODE_MAP[125] = KeyCode.F14;
|
||||
KEY_CODE_MAP[126] = KeyCode.F15;
|
||||
KEY_CODE_MAP[127] = KeyCode.F16;
|
||||
KEY_CODE_MAP[128] = KeyCode.F17;
|
||||
KEY_CODE_MAP[129] = KeyCode.F18;
|
||||
KEY_CODE_MAP[130] = KeyCode.F19;
|
||||
|
||||
KEY_CODE_MAP[144] = KeyCode.NumLock;
|
||||
KEY_CODE_MAP[145] = KeyCode.ScrollLock;
|
||||
|
||||
KEY_CODE_MAP[186] = KeyCode.Semicolon;
|
||||
KEY_CODE_MAP[187] = KeyCode.Equal;
|
||||
KEY_CODE_MAP[188] = KeyCode.Comma;
|
||||
KEY_CODE_MAP[189] = KeyCode.Minus;
|
||||
KEY_CODE_MAP[190] = KeyCode.Period;
|
||||
KEY_CODE_MAP[191] = KeyCode.Slash;
|
||||
KEY_CODE_MAP[192] = KeyCode.Backquote;
|
||||
KEY_CODE_MAP[193] = KeyCode.ABNT_C1;
|
||||
KEY_CODE_MAP[194] = KeyCode.ABNT_C2;
|
||||
KEY_CODE_MAP[219] = KeyCode.BracketLeft;
|
||||
KEY_CODE_MAP[220] = KeyCode.Backslash;
|
||||
KEY_CODE_MAP[221] = KeyCode.BracketRight;
|
||||
KEY_CODE_MAP[222] = KeyCode.Quote;
|
||||
KEY_CODE_MAP[223] = KeyCode.OEM_8;
|
||||
|
||||
KEY_CODE_MAP[226] = KeyCode.IntlBackslash;
|
||||
|
||||
/**
|
||||
* https://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html
|
||||
* If an Input Method Editor is processing key input and the event is keydown, return 229.
|
||||
*/
|
||||
KEY_CODE_MAP[229] = KeyCode.KEY_IN_COMPOSITION;
|
||||
|
||||
if (browser.isIE) {
|
||||
KEY_CODE_MAP[91] = KeyCode.Meta;
|
||||
} else if (browser.isFirefox) {
|
||||
KEY_CODE_MAP[59] = KeyCode.Semicolon;
|
||||
KEY_CODE_MAP[107] = KeyCode.Equal;
|
||||
KEY_CODE_MAP[109] = KeyCode.Minus;
|
||||
if (MonacoPlatform.OS === MonacoPlatform.OperatingSystem.Macintosh) {
|
||||
KEY_CODE_MAP[224] = KeyCode.Meta;
|
||||
}
|
||||
} else if (browser.isWebKit) {
|
||||
KEY_CODE_MAP[91] = KeyCode.Meta;
|
||||
if (MonacoPlatform.OS === MonacoPlatform.OperatingSystem.Macintosh) {
|
||||
// the two meta keys in the Mac have different key codes (91 and 93)
|
||||
KEY_CODE_MAP[93] = KeyCode.Meta;
|
||||
} else {
|
||||
KEY_CODE_MAP[92] = KeyCode.Meta;
|
||||
}
|
||||
}
|
||||
})();
|
||||
177
packages/monaco/src/browser/monaco-languages.ts
Normal file
177
packages/monaco/src/browser/monaco-languages.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { SymbolInformation, WorkspaceSymbolParams } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
||||
import { ProblemManager } from '@theia/markers/lib/browser/problem/problem-manager';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { MaybePromise, Mutable } from '@theia/core/lib/common/types';
|
||||
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||
import { CancellationToken } from '@theia/core/lib/common/cancellation';
|
||||
import { Language, LanguageService } from '@theia/core/lib/browser/language-service';
|
||||
import { MonacoMarkerCollection } from './monaco-marker-collection';
|
||||
import { ProtocolToMonacoConverter } from './protocol-to-monaco-converter';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { FileStat } from '@theia/filesystem/lib/common/files';
|
||||
import { FileStatNode } from '@theia/filesystem/lib/browser';
|
||||
import { ILanguageService } from '@theia/monaco-editor-core/esm/vs/editor/common/languages/language';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
|
||||
export interface WorkspaceSymbolProvider {
|
||||
provideWorkspaceSymbols(params: WorkspaceSymbolParams, token: CancellationToken): MaybePromise<SymbolInformation[] | undefined>;
|
||||
resolveWorkspaceSymbol?(symbol: SymbolInformation, token: CancellationToken): Thenable<SymbolInformation | undefined>
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MonacoLanguages extends LanguageService {
|
||||
|
||||
readonly workspaceSymbolProviders: WorkspaceSymbolProvider[] = [];
|
||||
|
||||
protected readonly markers = new Map<string, MonacoMarkerCollection>();
|
||||
protected readonly icons = new Map<string, string>();
|
||||
|
||||
@inject(ProblemManager) protected readonly problemManager: ProblemManager;
|
||||
@inject(ProtocolToMonacoConverter) protected readonly p2m: ProtocolToMonacoConverter;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.problemManager.onDidChangeMarkers(uri => this.updateMarkers(uri));
|
||||
monaco.editor.onDidCreateModel(model => this.updateModelMarkers(model));
|
||||
}
|
||||
|
||||
updateMarkers(uri: URI): void {
|
||||
const markers = this.problemManager.findMarkers({ uri });
|
||||
const uriString = uri.toString();
|
||||
const collection = this.markers.get(uriString) || new MonacoMarkerCollection(uri, this.p2m);
|
||||
this.markers.set(uriString, collection);
|
||||
collection.updateMarkers(markers);
|
||||
}
|
||||
|
||||
updateModelMarkers(model: monaco.editor.ITextModel): void {
|
||||
const uriString = model.uri.toString();
|
||||
const uri = new URI(uriString);
|
||||
const collection = this.markers.get(uriString) || new MonacoMarkerCollection(uri, this.p2m);
|
||||
this.markers.set(uriString, collection);
|
||||
collection.updateModelMarkers(model);
|
||||
}
|
||||
|
||||
registerWorkspaceSymbolProvider(provider: WorkspaceSymbolProvider): Disposable {
|
||||
this.workspaceSymbolProviders.push(provider);
|
||||
return Disposable.create(() => {
|
||||
const index = this.workspaceSymbolProviders.indexOf(provider);
|
||||
if (index !== -1) {
|
||||
this.workspaceSymbolProviders.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
override get languages(): Language[] {
|
||||
return [...this.mergeLanguages(monaco.languages.getLanguages()).values()];
|
||||
}
|
||||
|
||||
override getLanguage(languageId: string): Language | undefined {
|
||||
return this.mergeLanguages(monaco.languages.getLanguages().filter(language => language.id === languageId)).get(languageId);
|
||||
}
|
||||
|
||||
override detectLanguage(obj: unknown): Language | undefined {
|
||||
if (obj === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof obj === 'string') {
|
||||
return this.detectLanguageByIdOrName(obj) ?? this.detectLanguageByURI(new URI(obj));
|
||||
}
|
||||
if (obj instanceof URI) {
|
||||
return this.detectLanguageByURI(obj);
|
||||
}
|
||||
if (FileStat.is(obj)) {
|
||||
return this.detectLanguageByURI(obj.resource);
|
||||
}
|
||||
if (FileStatNode.is(obj)) {
|
||||
return this.detectLanguageByURI(obj.uri);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected detectLanguageByIdOrName(obj: string): Language | undefined {
|
||||
const languageById = this.getLanguage(obj);
|
||||
if (languageById) {
|
||||
return languageById;
|
||||
}
|
||||
|
||||
const languageId = this.getLanguageIdByLanguageName(obj);
|
||||
return languageId ? this.getLanguage(languageId) : undefined;
|
||||
}
|
||||
|
||||
protected detectLanguageByURI(uri: URI): Language | undefined {
|
||||
const languageId = StandaloneServices.get(ILanguageService).guessLanguageIdByFilepathOrFirstLine(uri['codeUri']);
|
||||
return languageId ? this.getLanguage(languageId) : undefined;
|
||||
}
|
||||
|
||||
getExtension(languageId: string): string | undefined {
|
||||
return this.getLanguage(languageId)?.extensions.values().next().value;
|
||||
}
|
||||
|
||||
override registerIcon(languageId: string, iconClass: string): Disposable {
|
||||
this.icons.set(languageId, iconClass);
|
||||
this.onDidChangeIconEmitter.fire({ languageId });
|
||||
return Disposable.create(() => {
|
||||
this.icons.delete(languageId);
|
||||
this.onDidChangeIconEmitter.fire({ languageId });
|
||||
});
|
||||
}
|
||||
|
||||
override getIcon(obj: unknown): string | undefined {
|
||||
const language = this.detectLanguage(obj);
|
||||
return language ? this.icons.get(language.id) : undefined;
|
||||
}
|
||||
|
||||
getLanguageIdByLanguageName(languageName: string): string | undefined {
|
||||
return monaco.languages.getLanguages().find(language => language.aliases?.includes(languageName))?.id;
|
||||
}
|
||||
|
||||
protected mergeLanguages(registered: monaco.languages.ILanguageExtensionPoint[]): Map<string, Mutable<Language>> {
|
||||
const languages = new Map<string, Mutable<Language>>();
|
||||
for (const { id, aliases, extensions, filenames } of registered) {
|
||||
const merged = languages.get(id) || {
|
||||
id,
|
||||
name: '',
|
||||
extensions: new Set(),
|
||||
filenames: new Set()
|
||||
};
|
||||
if (!merged.name && aliases && aliases.length) {
|
||||
merged.name = aliases[0];
|
||||
}
|
||||
if (extensions && extensions.length) {
|
||||
for (const extension of extensions) {
|
||||
merged.extensions.add(extension);
|
||||
}
|
||||
}
|
||||
if (filenames && filenames.length) {
|
||||
for (const filename of filenames) {
|
||||
merged.filenames.add(filename);
|
||||
}
|
||||
}
|
||||
languages.set(id, merged);
|
||||
}
|
||||
for (const [id, language] of languages) {
|
||||
if (!language.name) {
|
||||
language.name = id;
|
||||
}
|
||||
}
|
||||
return languages;
|
||||
}
|
||||
|
||||
}
|
||||
83
packages/monaco/src/browser/monaco-marker-collection.ts
Normal file
83
packages/monaco/src/browser/monaco-marker-collection.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { Diagnostic } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import { ProtocolToMonacoConverter } from './protocol-to-monaco-converter';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { Marker } from '@theia/markers/lib/common/marker';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
|
||||
export class MonacoMarkerCollection {
|
||||
protected readonly uri: monaco.Uri;
|
||||
protected readonly p2m: ProtocolToMonacoConverter;
|
||||
protected markers: Marker<Diagnostic>[] = [];
|
||||
protected owners = new Map<string, Diagnostic[]>();
|
||||
protected didUpdate: boolean = false;
|
||||
|
||||
constructor(
|
||||
uri: URI,
|
||||
p2m: ProtocolToMonacoConverter
|
||||
) {
|
||||
this.uri = monaco.Uri.parse(uri.toString());
|
||||
this.p2m = p2m;
|
||||
}
|
||||
|
||||
updateMarkers(markers: Marker<Diagnostic>[]): void {
|
||||
this.markers = markers;
|
||||
const model = monaco.editor.getModel(this.uri);
|
||||
this.doUpdateMarkers(model ? model : undefined);
|
||||
}
|
||||
|
||||
updateModelMarkers(model: monaco.editor.ITextModel): void {
|
||||
if (!this.didUpdate) {
|
||||
this.doUpdateMarkers(model);
|
||||
return;
|
||||
}
|
||||
for (const [owner, diagnostics] of this.owners) {
|
||||
this.setModelMarkers(model, owner, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
doUpdateMarkers(model: monaco.editor.ITextModel | undefined): void {
|
||||
if (!model) {
|
||||
this.didUpdate = false;
|
||||
return;
|
||||
}
|
||||
this.didUpdate = true;
|
||||
const toClean = new Set<string>(this.owners.keys());
|
||||
this.owners.clear();
|
||||
for (const marker of this.markers) {
|
||||
const diagnostics = this.owners.get(marker.owner) || [];
|
||||
diagnostics.push(marker.data);
|
||||
this.owners.set(marker.owner, diagnostics);
|
||||
}
|
||||
for (const [owner, diagnostics] of this.owners) {
|
||||
toClean.delete(owner);
|
||||
this.setModelMarkers(model, owner, diagnostics);
|
||||
}
|
||||
for (const owner of toClean) {
|
||||
this.clearModelMarkers(model, owner);
|
||||
}
|
||||
}
|
||||
|
||||
setModelMarkers(model: monaco.editor.ITextModel, owner: string, diagnostics: Diagnostic[]): void {
|
||||
monaco.editor.setModelMarkers(model, owner, this.p2m.asDiagnostics(diagnostics));
|
||||
}
|
||||
|
||||
clearModelMarkers(model: monaco.editor.ITextModel, owner: string): void {
|
||||
monaco.editor.setModelMarkers(model, owner, []);
|
||||
}
|
||||
}
|
||||
148
packages/monaco/src/browser/monaco-menu.ts
Normal file
148
packages/monaco/src/browser/monaco-menu.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { MAIN_MENU_BAR, MenuAction, MenuContribution, MenuModelRegistry, MenuPath } from '@theia/core/lib/common';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { EDITOR_CONTEXT_MENU, EditorMainMenu } from '@theia/editor/lib/browser';
|
||||
import { IMenuItem, MenuId, MenuRegistry, isIMenuItem } from '@theia/monaco-editor-core/esm/vs/platform/actions/common/actions';
|
||||
import { MonacoCommands } from './monaco-command';
|
||||
import { MonacoCommandRegistry } from './monaco-command-registry';
|
||||
|
||||
export interface MonacoActionGroup {
|
||||
id: string;
|
||||
actions: string[];
|
||||
}
|
||||
export namespace MonacoMenus {
|
||||
export const SELECTION = [...MAIN_MENU_BAR, '3_selection'];
|
||||
export const PEEK_CONTEXT_SUBMENU: MenuPath = [...EDITOR_CONTEXT_MENU, 'navigation', 'peek_submenu'];
|
||||
export const MARKERS_GROUP = [...EditorMainMenu.GO, '5_markers_group'];
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MonacoEditorMenuContribution implements MenuContribution {
|
||||
|
||||
constructor(
|
||||
@inject(MonacoCommandRegistry) protected readonly commands: MonacoCommandRegistry
|
||||
) { }
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerSubmenu(EDITOR_CONTEXT_MENU, 'Editor Context Menu');
|
||||
for (const item of MenuRegistry.getMenuItems(MenuId.EditorContext)) {
|
||||
if (!isIMenuItem(item)) {
|
||||
continue;
|
||||
}
|
||||
const commandId = this.commands.validate(item.command.id);
|
||||
if (commandId) {
|
||||
const nodeId = MonacoCommands.COMMON_ACTIONS.get(commandId) || commandId;
|
||||
const menuPath = item.group ? [...EDITOR_CONTEXT_MENU, item.group] : EDITOR_CONTEXT_MENU;
|
||||
if (!registry.getMenuNode([...menuPath, nodeId])) {
|
||||
// Don't add additional actions if the item is already registered.
|
||||
registry.registerMenuAction(menuPath, this.buildMenuAction(commandId, item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.registerPeekSubmenu(registry);
|
||||
|
||||
registry.registerSubmenu(MonacoMenus.SELECTION, nls.localizeByDefault('Selection'));
|
||||
for (const item of MenuRegistry.getMenuItems(MenuId.MenubarSelectionMenu)) {
|
||||
if (!isIMenuItem(item)) {
|
||||
continue;
|
||||
}
|
||||
const commandId = this.commands.validate(item.command.id);
|
||||
if (commandId) {
|
||||
const menuPath = [...MonacoMenus.SELECTION, (item.group || '')];
|
||||
registry.registerMenuAction(menuPath, this.buildMenuAction(commandId, item));
|
||||
}
|
||||
}
|
||||
|
||||
// Builtin monaco language features commands.
|
||||
registry.registerMenuAction(EditorMainMenu.LANGUAGE_FEATURES_GROUP, {
|
||||
commandId: 'editor.action.quickOutline',
|
||||
label: nls.localizeByDefault('Go to Symbol in Editor...'),
|
||||
order: '1'
|
||||
});
|
||||
registry.registerMenuAction(EditorMainMenu.LANGUAGE_FEATURES_GROUP, {
|
||||
commandId: 'editor.action.revealDefinition',
|
||||
label: nls.localizeByDefault('Go to Definition'),
|
||||
order: '2'
|
||||
});
|
||||
registry.registerMenuAction(EditorMainMenu.LANGUAGE_FEATURES_GROUP, {
|
||||
commandId: 'editor.action.revealDeclaration',
|
||||
label: nls.localizeByDefault('Go to Declaration'),
|
||||
order: '3'
|
||||
});
|
||||
registry.registerMenuAction(EditorMainMenu.LANGUAGE_FEATURES_GROUP, {
|
||||
commandId: 'editor.action.goToTypeDefinition',
|
||||
label: nls.localizeByDefault('Go to Type Definition'),
|
||||
order: '4'
|
||||
});
|
||||
registry.registerMenuAction(EditorMainMenu.LANGUAGE_FEATURES_GROUP, {
|
||||
commandId: 'editor.action.goToImplementation',
|
||||
label: nls.localizeByDefault('Go to Implementations'),
|
||||
order: '5'
|
||||
});
|
||||
registry.registerMenuAction(EditorMainMenu.LANGUAGE_FEATURES_GROUP, {
|
||||
commandId: 'editor.action.goToReferences',
|
||||
label: nls.localizeByDefault('Go to References'),
|
||||
order: '6'
|
||||
});
|
||||
|
||||
registry.registerMenuAction(EditorMainMenu.LOCATION_GROUP, {
|
||||
commandId: 'editor.action.jumpToBracket',
|
||||
label: nls.localizeByDefault('Go to Bracket'),
|
||||
order: '2'
|
||||
});
|
||||
|
||||
// Builtin monaco problem commands.
|
||||
registry.registerMenuAction(MonacoMenus.MARKERS_GROUP, {
|
||||
commandId: 'editor.action.marker.nextInFiles',
|
||||
label: nls.localizeByDefault('Next Problem'),
|
||||
order: '1'
|
||||
});
|
||||
registry.registerMenuAction(MonacoMenus.MARKERS_GROUP, {
|
||||
commandId: 'editor.action.marker.prevInFiles',
|
||||
label: nls.localizeByDefault('Previous Problem'),
|
||||
order: '2'
|
||||
});
|
||||
}
|
||||
|
||||
protected registerPeekSubmenu(registry: MenuModelRegistry): void {
|
||||
registry.registerSubmenu(MonacoMenus.PEEK_CONTEXT_SUBMENU, nls.localizeByDefault('Peek'));
|
||||
|
||||
for (const item of MenuRegistry.getMenuItems(MenuId.EditorContextPeek)) {
|
||||
if (!isIMenuItem(item)) {
|
||||
continue;
|
||||
}
|
||||
const commandId = this.commands.validate(item.command.id);
|
||||
if (commandId) {
|
||||
registry.registerMenuAction([...MonacoMenus.PEEK_CONTEXT_SUBMENU, item.group || ''], this.buildMenuAction(commandId, item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected buildMenuAction(commandId: string, item: IMenuItem): MenuAction {
|
||||
const title = typeof item.command.title === 'string' ? item.command.title : item.command.title.value;
|
||||
const label = this.removeMnemonic(title);
|
||||
const order = item.order ? String(item.order) : '';
|
||||
return { commandId, order, label };
|
||||
}
|
||||
|
||||
protected removeMnemonic(label: string): string {
|
||||
return label.replace(/\(&&\w\)|&&/g, '');
|
||||
}
|
||||
}
|
||||
71
packages/monaco/src/browser/monaco-mime-service.ts
Normal file
71
packages/monaco/src/browser/monaco-mime-service.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 Red Hat, Inc. and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import debounce = require('@theia/core/shared/lodash.debounce');
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { MimeAssociation, MimeService } from '@theia/core/lib/browser/mime-service';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { ILanguageService } from '@theia/monaco-editor-core/esm/vs/editor/common/languages/language';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { clearConfiguredLanguageAssociations, registerConfiguredLanguageAssociation } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languagesAssociations';
|
||||
import { LanguageService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageService';
|
||||
|
||||
@injectable()
|
||||
export class MonacoMimeService extends MimeService {
|
||||
|
||||
protected associations: MimeAssociation[] = [];
|
||||
protected updatingAssociations = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
StandaloneServices.get(ILanguageService).onDidChange(() => {
|
||||
if (this.updatingAssociations) {
|
||||
return;
|
||||
}
|
||||
this.updateAssociations();
|
||||
});
|
||||
}
|
||||
|
||||
override setAssociations(associations: MimeAssociation[]): void {
|
||||
this.associations = associations;
|
||||
this.updateAssociations();
|
||||
}
|
||||
|
||||
protected updateAssociations = debounce(() => {
|
||||
this.updatingAssociations = true;
|
||||
try {
|
||||
clearConfiguredLanguageAssociations();
|
||||
|
||||
for (const association of this.associations) {
|
||||
const mimetype = this.getMimeForMode(association.id) || `text/x-${association.id}`;
|
||||
registerConfiguredLanguageAssociation({ id: association.id, mime: mimetype, filepattern: association.filepattern });
|
||||
}
|
||||
(StandaloneServices.get(ILanguageService) as LanguageService)['_onDidChange'].fire(undefined);
|
||||
} finally {
|
||||
this.updatingAssociations = false;
|
||||
}
|
||||
});
|
||||
|
||||
protected getMimeForMode(langId: string): string | undefined {
|
||||
for (const language of monaco.languages.getLanguages()) {
|
||||
if (language.id === langId && language.mimetypes) {
|
||||
return language.mimetypes[0];
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
404
packages/monaco/src/browser/monaco-outline-contribution.ts
Normal file
404
packages/monaco/src/browser/monaco-outline-contribution.ts
Normal file
@@ -0,0 +1,404 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import { FrontendApplicationContribution, FrontendApplication, TreeNode } from '@theia/core/lib/browser';
|
||||
import { Range, EditorManager, EditorOpenerOptions } from '@theia/editor/lib/browser';
|
||||
import { DisposableCollection, Disposable } from '@theia/core';
|
||||
import { OutlineViewService } from '@theia/outline-view/lib/browser/outline-view-service';
|
||||
import { OutlineSymbolInformationNode } from '@theia/outline-view/lib/browser/outline-view-widget';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { MonacoEditor } from './monaco-editor';
|
||||
import debounce = require('@theia/core/shared/lodash.debounce');
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { ILanguageFeaturesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageFeatures';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { ITextModel } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
|
||||
import { DocumentSymbol } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
|
||||
|
||||
@injectable()
|
||||
export class MonacoOutlineContribution implements FrontendApplicationContribution {
|
||||
|
||||
protected readonly toDisposeOnEditor = new DisposableCollection();
|
||||
protected roots: MonacoOutlineSymbolInformationNode[] | undefined;
|
||||
protected canUpdateOutline: boolean = true;
|
||||
|
||||
@inject(OutlineViewService) protected readonly outlineViewService: OutlineViewService;
|
||||
@inject(EditorManager) protected readonly editorManager: EditorManager;
|
||||
|
||||
onStart(app: FrontendApplication): void {
|
||||
|
||||
// updateOutline and handleCurrentEditorChanged need to be called even when the outline view widget is closed
|
||||
// in order to update breadcrumbs.
|
||||
StandaloneServices.get(ILanguageFeaturesService).documentSymbolProvider.onDidChange(
|
||||
debounce(() => this.updateOutline())
|
||||
);
|
||||
this.editorManager.onCurrentEditorChanged(
|
||||
debounce(() => this.handleCurrentEditorChanged(), 50)
|
||||
);
|
||||
this.handleCurrentEditorChanged();
|
||||
|
||||
this.outlineViewService.onDidSelect(async node => {
|
||||
if (MonacoOutlineSymbolInformationNode.is(node) && node.parent) {
|
||||
const options: EditorOpenerOptions = {
|
||||
mode: 'reveal',
|
||||
selection: node.range
|
||||
};
|
||||
await this.selectInEditor(node, options);
|
||||
}
|
||||
});
|
||||
this.outlineViewService.onDidOpen(async node => {
|
||||
if (MonacoOutlineSymbolInformationNode.is(node)) {
|
||||
const options: EditorOpenerOptions = {
|
||||
selection: {
|
||||
start: node.range.start
|
||||
}
|
||||
};
|
||||
await this.selectInEditor(node, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected async selectInEditor(node: MonacoOutlineSymbolInformationNode, options?: EditorOpenerOptions): Promise<void> {
|
||||
// Avoid cyclic updates: Outline -> Editor -> Outline.
|
||||
this.canUpdateOutline = false;
|
||||
try {
|
||||
await this.editorManager.open(node.uri, options);
|
||||
} finally {
|
||||
this.canUpdateOutline = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected handleCurrentEditorChanged(): void {
|
||||
this.toDisposeOnEditor.dispose();
|
||||
this.toDisposeOnEditor.push(Disposable.create(() => this.roots = undefined));
|
||||
const editor = this.editorManager.currentEditor;
|
||||
if (editor) {
|
||||
const model = MonacoEditor.get(editor)!.getControl().getModel();
|
||||
if (model) {
|
||||
this.toDisposeOnEditor.push(model.onDidChangeContent(() => {
|
||||
this.roots = undefined; // Invalidate the previously resolved roots.
|
||||
this.updateOutline();
|
||||
}));
|
||||
}
|
||||
this.toDisposeOnEditor.push(editor.editor.onSelectionChanged(selection => this.updateOutline(selection)));
|
||||
}
|
||||
this.updateOutline();
|
||||
}
|
||||
|
||||
protected tokenSource = new monaco.CancellationTokenSource();
|
||||
protected async updateOutline(editorSelection?: Range): Promise<void> {
|
||||
if (!this.canUpdateOutline) {
|
||||
return;
|
||||
}
|
||||
this.tokenSource.cancel();
|
||||
this.tokenSource = new monaco.CancellationTokenSource();
|
||||
const token = this.tokenSource.token;
|
||||
|
||||
const editor = MonacoEditor.get(this.editorManager.currentEditor);
|
||||
const model = editor && editor.getControl().getModel();
|
||||
const roots = model && await this.createRoots(model, token, editorSelection);
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
this.outlineViewService.publish(roots || []);
|
||||
}
|
||||
|
||||
protected async createRoots(
|
||||
model: monaco.editor.ITextModel | ITextModel, token: monaco.CancellationToken, editorSelection?: Range
|
||||
): Promise<MonacoOutlineSymbolInformationNode[]> {
|
||||
model = model as ITextModel;
|
||||
if (this.roots && this.roots.length > 0) {
|
||||
// Reset the selection on the tree nodes, so that we can apply the new ones based on the `editorSelection`.
|
||||
const resetSelection = (node: MonacoOutlineSymbolInformationNode) => {
|
||||
node.selected = false;
|
||||
node.children.forEach(resetSelection);
|
||||
};
|
||||
this.roots.forEach(resetSelection);
|
||||
} else {
|
||||
this.roots = [];
|
||||
const providers = StandaloneServices.get(ILanguageFeaturesService).documentSymbolProvider.all(model);
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
const uri = new URI(model.uri.toString());
|
||||
for (const provider of providers) {
|
||||
try {
|
||||
const symbols = await provider.provideDocumentSymbols(model, token) ?? [];
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
const nodes = this.createNodes(uri, symbols);
|
||||
if (providers.length > 1 && provider.displayName) {
|
||||
const providerRoot = this.createProviderRootNode(uri, provider.displayName, nodes);
|
||||
this.roots.push(providerRoot);
|
||||
} else {
|
||||
this.roots.push(...nodes);
|
||||
}
|
||||
} catch {
|
||||
/* collect symbols from other providers */
|
||||
}
|
||||
}
|
||||
}
|
||||
this.applySelection(this.roots, editorSelection);
|
||||
return this.roots;
|
||||
}
|
||||
|
||||
protected createProviderRootNode(uri: URI, displayName: string, children: MonacoOutlineSymbolInformationNode[]): MonacoOutlineSymbolInformationNode {
|
||||
const node: MonacoOutlineSymbolInformationNode = {
|
||||
uri,
|
||||
id: displayName,
|
||||
name: displayName,
|
||||
iconClass: '',
|
||||
range: this.asRange(new monaco.Range(1, 1, 1, 1)),
|
||||
fullRange: this.asRange(new monaco.Range(1, 1, 1, 1)),
|
||||
children,
|
||||
parent: undefined,
|
||||
selected: false,
|
||||
expanded: true
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
protected createNodes(uri: URI, symbols: monaco.languages.DocumentSymbol[] | DocumentSymbol[]): MonacoOutlineSymbolInformationNode[] {
|
||||
symbols = symbols as monaco.languages.DocumentSymbol[];
|
||||
let rangeBased = false;
|
||||
const ids = new Map();
|
||||
const roots: MonacoOutlineSymbolInformationNode[] = [];
|
||||
const nodesByName = symbols.sort(this.orderByPosition).reduce((result, symbol) => {
|
||||
const node = this.createNode(uri, symbol, ids);
|
||||
if (symbol.children) {
|
||||
MonacoOutlineSymbolInformationNode.insert(roots, node);
|
||||
} else {
|
||||
rangeBased = rangeBased || symbol.range.startLineNumber !== symbol.range.endLineNumber;
|
||||
const values = result.get(symbol.name) || [];
|
||||
values.push({ symbol, node });
|
||||
result.set(symbol.name, values);
|
||||
}
|
||||
return result;
|
||||
}, new Map<string, MonacoOutlineContribution.NodeAndSymbol[]>());
|
||||
|
||||
for (const nodes of nodesByName.values()) {
|
||||
for (const { node, symbol } of nodes) {
|
||||
if (!symbol.containerName) {
|
||||
MonacoOutlineSymbolInformationNode.insert(roots, node);
|
||||
} else {
|
||||
const possibleParents = nodesByName.get(symbol.containerName);
|
||||
if (possibleParents) {
|
||||
const parent = possibleParents.find(possibleParent => this.parentContains(symbol, possibleParent.symbol, rangeBased));
|
||||
if (parent) {
|
||||
node.parent = parent.node;
|
||||
MonacoOutlineSymbolInformationNode.insert(parent.node.children, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!roots.length) {
|
||||
const nodes = nodesByName.values().next().value;
|
||||
if (nodes && !nodes[0].node.parent) {
|
||||
return [nodes[0].node];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selection on the sub-trees based on the optional editor selection.
|
||||
* Select the narrowest node that is strictly contains the editor selection.
|
||||
*/
|
||||
protected applySelection(roots: MonacoOutlineSymbolInformationNode[], editorSelection?: Range): boolean {
|
||||
if (editorSelection) {
|
||||
for (const root of roots) {
|
||||
if (this.parentContains(editorSelection, root.fullRange, true)) {
|
||||
const { children } = root;
|
||||
root.selected = !root.expanded || !this.applySelection(children, editorSelection);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if `candidate` is strictly contained inside `parent`
|
||||
*
|
||||
* If the argument is a `DocumentSymbol`, then `getFullRange` will be used to retrieve the range of the underlying symbol.
|
||||
*/
|
||||
protected parentContains(candidate: monaco.languages.DocumentSymbol | Range, parent: monaco.languages.DocumentSymbol | Range, rangeBased: boolean): boolean {
|
||||
// TODO: move this code to the `monaco-languageclient`: https://github.com/eclipse-theia/theia/pull/2885#discussion_r217800446
|
||||
const candidateRange = Range.is(candidate) ? candidate : this.getFullRange(candidate);
|
||||
const parentRange = Range.is(parent) ? parent : this.getFullRange(parent);
|
||||
const sameStartLine = candidateRange.start.line === parentRange.start.line;
|
||||
const startColGreaterOrEqual = candidateRange.start.character >= parentRange.start.character;
|
||||
const startLineGreater = candidateRange.start.line > parentRange.start.line;
|
||||
const sameEndLine = candidateRange.end.line === parentRange.end.line;
|
||||
const endColSmallerOrEqual = candidateRange.end.character <= parentRange.end.character;
|
||||
const endLineSmaller = candidateRange.end.line < parentRange.end.line;
|
||||
return (((sameStartLine && startColGreaterOrEqual || startLineGreater) &&
|
||||
(sameEndLine && endColSmallerOrEqual || endLineSmaller)) || !rangeBased);
|
||||
}
|
||||
|
||||
/**
|
||||
* `monaco` to LSP `Range` converter. Converts the `1-based` location indices into `0-based` ones.
|
||||
*/
|
||||
protected asRange(range: monaco.IRange): Range {
|
||||
const { startLineNumber, startColumn, endLineNumber, endColumn } = range;
|
||||
return {
|
||||
start: {
|
||||
line: startLineNumber - 1,
|
||||
character: startColumn - 1
|
||||
},
|
||||
end: {
|
||||
line: endLineNumber - 1,
|
||||
character: endColumn - 1
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns with a range enclosing this symbol not including leading/trailing whitespace but everything else like comments.
|
||||
* This information is typically used to determine if the clients cursor is inside the symbol to reveal in the symbol in the UI.
|
||||
* This allows to obtain the range including the associated comments.
|
||||
*
|
||||
* See: [`DocumentSymbol#range`](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) for more details.
|
||||
*/
|
||||
protected getFullRange(documentSymbol: monaco.languages.DocumentSymbol): Range {
|
||||
return this.asRange(documentSymbol.range);
|
||||
}
|
||||
|
||||
/**
|
||||
* The range that should be selected and revealed when this symbol is being picked, e.g the name of a function. Must be contained by the `getSelectionRange`.
|
||||
*
|
||||
* See: [`DocumentSymbol#selectionRange`](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) for more details.
|
||||
*/
|
||||
protected getNameRange(documentSymbol: monaco.languages.DocumentSymbol): Range {
|
||||
return this.asRange(documentSymbol.selectionRange);
|
||||
}
|
||||
|
||||
protected createNode(
|
||||
uri: URI, symbol: monaco.languages.DocumentSymbol, ids: Map<string, number>, parent?: MonacoOutlineSymbolInformationNode
|
||||
): MonacoOutlineSymbolInformationNode {
|
||||
const id = this.createId(symbol.name, ids);
|
||||
const children: MonacoOutlineSymbolInformationNode[] = [];
|
||||
const node: MonacoOutlineSymbolInformationNode = {
|
||||
children,
|
||||
id,
|
||||
iconClass: monaco.languages.SymbolKind[symbol.kind].toString().toLowerCase(),
|
||||
name: this.getName(symbol),
|
||||
detail: this.getDetail(symbol),
|
||||
parent,
|
||||
uri,
|
||||
range: this.getNameRange(symbol),
|
||||
fullRange: this.getFullRange(symbol),
|
||||
selected: false,
|
||||
expanded: this.shouldExpand(symbol)
|
||||
};
|
||||
if (symbol.children) {
|
||||
for (const child of symbol.children) {
|
||||
MonacoOutlineSymbolInformationNode.insert(children, this.createNode(uri, child, ids, node));
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
protected getName(symbol: monaco.languages.DocumentSymbol): string {
|
||||
return symbol.name;
|
||||
}
|
||||
|
||||
protected getDetail(symbol: monaco.languages.DocumentSymbol): string {
|
||||
return symbol.detail;
|
||||
}
|
||||
|
||||
protected createId(name: string, ids: Map<string, number>): string {
|
||||
const counter = ids.get(name);
|
||||
const index = typeof counter === 'number' ? counter + 1 : 0;
|
||||
ids.set(name, index);
|
||||
return name + '_' + index;
|
||||
}
|
||||
|
||||
protected shouldExpand(symbol: monaco.languages.DocumentSymbol): boolean {
|
||||
return [
|
||||
monaco.languages.SymbolKind.Class,
|
||||
monaco.languages.SymbolKind.Enum, monaco.languages.SymbolKind.File,
|
||||
monaco.languages.SymbolKind.Interface, monaco.languages.SymbolKind.Module,
|
||||
monaco.languages.SymbolKind.Namespace, monaco.languages.SymbolKind.Object,
|
||||
monaco.languages.SymbolKind.Package, monaco.languages.SymbolKind.Struct
|
||||
].indexOf(symbol.kind) !== -1;
|
||||
}
|
||||
|
||||
protected orderByPosition(symbol: monaco.languages.DocumentSymbol, symbol2: monaco.languages.DocumentSymbol): number {
|
||||
const startLineComparison = symbol.range.startLineNumber - symbol2.range.startLineNumber;
|
||||
if (startLineComparison !== 0) {
|
||||
return startLineComparison;
|
||||
}
|
||||
const startOffsetComparison = symbol.range.startColumn - symbol2.range.startColumn;
|
||||
if (startOffsetComparison !== 0) {
|
||||
return startOffsetComparison;
|
||||
}
|
||||
const endLineComparison = symbol.range.endLineNumber - symbol2.range.endLineNumber;
|
||||
if (endLineComparison !== 0) {
|
||||
return endLineComparison;
|
||||
}
|
||||
return symbol.range.endColumn - symbol2.range.endColumn;
|
||||
}
|
||||
|
||||
}
|
||||
export namespace MonacoOutlineContribution {
|
||||
export interface NodeAndSymbol {
|
||||
node: MonacoOutlineSymbolInformationNode;
|
||||
symbol: monaco.languages.DocumentSymbol
|
||||
}
|
||||
}
|
||||
|
||||
export interface MonacoOutlineSymbolInformationNode extends OutlineSymbolInformationNode {
|
||||
uri: URI;
|
||||
range: Range;
|
||||
fullRange: Range;
|
||||
detail?: string;
|
||||
parent: MonacoOutlineSymbolInformationNode | undefined;
|
||||
children: MonacoOutlineSymbolInformationNode[];
|
||||
}
|
||||
|
||||
export namespace MonacoOutlineSymbolInformationNode {
|
||||
export function is(node: TreeNode): node is MonacoOutlineSymbolInformationNode {
|
||||
return OutlineSymbolInformationNode.is(node) && 'uri' in node && 'range' in node;
|
||||
}
|
||||
export function insert(nodes: MonacoOutlineSymbolInformationNode[], node: MonacoOutlineSymbolInformationNode): void {
|
||||
const index = nodes.findIndex(current => compare(node, current) < 0);
|
||||
if (index === -1) {
|
||||
nodes.push(node);
|
||||
} else {
|
||||
nodes.splice(index, 0, node);
|
||||
}
|
||||
}
|
||||
export function compare(node: MonacoOutlineSymbolInformationNode, node2: MonacoOutlineSymbolInformationNode): number {
|
||||
const startLineComparison = node.range.start.line - node2.range.start.line;
|
||||
if (startLineComparison !== 0) {
|
||||
return startLineComparison;
|
||||
}
|
||||
const startColumnComparison = node.range.start.character - node2.range.start.character;
|
||||
if (startColumnComparison !== 0) {
|
||||
return startColumnComparison;
|
||||
}
|
||||
const endLineComparison = node2.range.end.line - node.range.end.line;
|
||||
if (endLineComparison !== 0) {
|
||||
return endLineComparison;
|
||||
}
|
||||
return node2.range.end.character - node.range.end.character;
|
||||
}
|
||||
}
|
||||
66
packages/monaco/src/browser/monaco-outline-decorator.ts
Normal file
66
packages/monaco/src/browser/monaco-outline-decorator.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 RedHat and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||
import { Tree } from '@theia/core/lib/browser/tree/tree';
|
||||
import { DepthFirstTreeIterator } from '@theia/core/lib/browser/tree/tree-iterator';
|
||||
import { TreeDecorator, TreeDecoration } from '@theia/core/lib/browser/tree/tree-decorator';
|
||||
import { MonacoOutlineSymbolInformationNode } from './monaco-outline-contribution';
|
||||
|
||||
@injectable()
|
||||
export class MonacoOutlineDecorator implements TreeDecorator {
|
||||
|
||||
readonly id = 'theia-monaco-outline-decorator';
|
||||
|
||||
protected readonly emitter = new Emitter<(tree: Tree) => Map<string, TreeDecoration.Data>>();
|
||||
|
||||
async decorations(tree: Tree): Promise<Map<string, TreeDecoration.Data>> {
|
||||
return this.collectDecorations(tree);
|
||||
}
|
||||
|
||||
get onDidChangeDecorations(): Event<(tree: Tree) => Map<string, TreeDecoration.Data>> {
|
||||
return this.emitter.event;
|
||||
}
|
||||
|
||||
protected collectDecorations(tree: Tree): Map<string, TreeDecoration.Data> {
|
||||
const result = new Map();
|
||||
if (tree.root === undefined) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const treeNode of new DepthFirstTreeIterator(tree.root)) {
|
||||
if (MonacoOutlineSymbolInformationNode.is(treeNode) && treeNode.detail) {
|
||||
result.set(treeNode.id, this.toDecoration(treeNode));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected toDecoration(node: MonacoOutlineSymbolInformationNode): TreeDecoration.Data {
|
||||
const captionSuffixes: TreeDecoration.CaptionAffix[] = [{
|
||||
data: (node.detail || ''),
|
||||
fontData: {
|
||||
color: 'var(--theia-descriptionForeground)',
|
||||
}
|
||||
}];
|
||||
|
||||
return {
|
||||
captionSuffixes
|
||||
};
|
||||
}
|
||||
}
|
||||
112
packages/monaco/src/browser/monaco-quick-access-registry.ts
Normal file
112
packages/monaco/src/browser/monaco-quick-access-registry.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2021 Red Hat and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { KeybindingRegistry, QuickPickItem, QuickPickSeparator } from '@theia/core/lib/browser';
|
||||
import { QuickAccessProviderDescriptor, QuickAccessRegistry } from '@theia/core/lib/browser/quick-input/quick-access';
|
||||
import { CancellationToken, Disposable } from '@theia/core/lib/common';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { MonacoQuickPickItem } from './monaco-quick-input-service';
|
||||
import {
|
||||
IPickerQuickAccessProviderOptions, PickerQuickAccessProvider, Picks, Pick, IPickerQuickAccessItem
|
||||
} from '@theia/monaco-editor-core/esm/vs/platform/quickinput/browser/pickerQuickAccess';
|
||||
import {
|
||||
Extensions,
|
||||
IQuickAccessProvider,
|
||||
IQuickAccessProviderDescriptor,
|
||||
IQuickAccessProviderHelp,
|
||||
IQuickAccessRegistry,
|
||||
QuickAccessRegistry as VSCodeQuickAccessRegistry,
|
||||
} from '@theia/monaco-editor-core/esm/vs/platform/quickinput/common/quickAccess';
|
||||
import { IQuickPickItem, IQuickPickItemWithResource } from '@theia/monaco-editor-core/esm/vs/platform/quickinput/common/quickInput';
|
||||
import { Registry } from '@theia/monaco-editor-core/esm/vs/platform/registry/common/platform';
|
||||
|
||||
interface IAnythingQuickPickItem extends IPickerQuickAccessItem, IQuickPickItemWithResource { }
|
||||
|
||||
abstract class MonacoPickerAccessProvider extends PickerQuickAccessProvider<IQuickPickItem> {
|
||||
constructor(prefix: string, options?: IPickerQuickAccessProviderOptions<IQuickPickItem>) {
|
||||
super(prefix, options);
|
||||
}
|
||||
|
||||
abstract getDescriptor(): QuickAccessProviderDescriptor;
|
||||
}
|
||||
|
||||
class TheiaQuickAccessDescriptor implements IQuickAccessProviderDescriptor {
|
||||
constructor(
|
||||
public readonly theiaDescriptor: QuickAccessProviderDescriptor,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
readonly ctor: { new(...services: any /* TS BrandedService but no clue how to type this properly */[]): IQuickAccessProvider },
|
||||
readonly prefix: string,
|
||||
readonly helpEntries: IQuickAccessProviderHelp[],
|
||||
readonly placeholder?: string) { }
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MonacoQuickAccessRegistry implements QuickAccessRegistry {
|
||||
@inject(KeybindingRegistry)
|
||||
protected readonly keybindingRegistry: KeybindingRegistry;
|
||||
|
||||
private get monacoRegistry(): IQuickAccessRegistry {
|
||||
return Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess);
|
||||
}
|
||||
|
||||
registerQuickAccessProvider(descriptor: QuickAccessProviderDescriptor): Disposable {
|
||||
const toMonacoPick = (item: QuickPickItem): Pick<IAnythingQuickPickItem> => {
|
||||
if (QuickPickSeparator.is(item)) {
|
||||
return item;
|
||||
} else {
|
||||
return new MonacoQuickPickItem(item, this.keybindingRegistry);
|
||||
}
|
||||
};
|
||||
|
||||
const inner =
|
||||
class extends MonacoPickerAccessProvider {
|
||||
getDescriptor(): QuickAccessProviderDescriptor {
|
||||
return descriptor;
|
||||
}
|
||||
constructor() {
|
||||
super(descriptor.prefix);
|
||||
}
|
||||
|
||||
protected override async _getPicks(filter: string, disposables: unknown, token: CancellationToken): Promise<Picks<IQuickPickItem>> {
|
||||
const result = await Promise.resolve(descriptor.getInstance().getPicks(filter, token));
|
||||
return result.map(toMonacoPick);
|
||||
}
|
||||
};
|
||||
|
||||
return this.monacoRegistry.registerQuickAccessProvider(new TheiaQuickAccessDescriptor(
|
||||
descriptor,
|
||||
inner,
|
||||
descriptor.prefix,
|
||||
descriptor.helpEntries,
|
||||
descriptor.placeholder
|
||||
));
|
||||
}
|
||||
|
||||
getQuickAccessProviders(): QuickAccessProviderDescriptor[] {
|
||||
return this.monacoRegistry.getQuickAccessProviders()
|
||||
.filter(provider => provider instanceof TheiaQuickAccessDescriptor)
|
||||
.map(provider => (provider as TheiaQuickAccessDescriptor).theiaDescriptor);
|
||||
}
|
||||
getQuickAccessProvider(prefix: string): QuickAccessProviderDescriptor | undefined {
|
||||
const monacoDescriptor = this.monacoRegistry.getQuickAccessProvider(prefix);
|
||||
return monacoDescriptor ? (monacoDescriptor as TheiaQuickAccessDescriptor).theiaDescriptor : undefined;
|
||||
}
|
||||
clear(): void {
|
||||
if (this.monacoRegistry instanceof VSCodeQuickAccessRegistry) {
|
||||
this.monacoRegistry.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
739
packages/monaco/src/browser/monaco-quick-input-service.ts
Normal file
739
packages/monaco/src/browser/monaco-quick-input-service.ts
Normal file
@@ -0,0 +1,739 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import {
|
||||
ApplicationShell,
|
||||
InputBox, InputOptions, KeybindingRegistry, PickOptions,
|
||||
QuickInputButton, QuickInputHideReason, QuickInputService, QuickPick, QuickPickItem,
|
||||
QuickPickItemButtonEvent, QuickPickItemHighlights, QuickPickOptions, QuickPickSeparator
|
||||
} from '@theia/core/lib/browser';
|
||||
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
IInputBox, IInputOptions, IKeyMods, IPickOptions, IQuickInput, IQuickInputButton,
|
||||
IQuickInputService, IQuickNavigateConfiguration, IQuickPick, IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator, IQuickWidget, QuickPickInput
|
||||
} from '@theia/monaco-editor-core/esm/vs/platform/quickinput/common/quickInput';
|
||||
import { IQuickInputOptions, IQuickInputStyles } from '@theia/monaco-editor-core/esm/vs/platform/quickinput/browser/quickInput';
|
||||
import { QuickInputController } from '@theia/monaco-editor-core/esm/vs/platform/quickinput/browser/quickInputController';
|
||||
import { MonacoResolvedKeybinding } from './monaco-resolved-keybinding';
|
||||
import { IQuickAccessController } from '@theia/monaco-editor-core/esm/vs/platform/quickinput/common/quickAccess';
|
||||
import { QuickAccessController } from '@theia/monaco-editor-core/esm/vs/platform/quickinput/browser/quickAccess';
|
||||
import { IContextKey, IContextKeyService, IScopedContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { ResolvedKeybinding } from '@theia/monaco-editor-core/esm/vs/base/common/keybindings';
|
||||
import { IInstantiationService } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/instantiation';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { IMatch } from '@theia/monaco-editor-core/esm/vs/base/common/filters';
|
||||
import { CancellationToken, Event } from '@theia/core';
|
||||
import { MonacoColorRegistry } from './monaco-color-registry';
|
||||
import { ThemeService } from '@theia/core/lib/browser/theming';
|
||||
import { IStandaloneThemeService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/common/standaloneTheme';
|
||||
import { ILayoutService } from '@theia/monaco-editor-core/esm/vs/platform/layout/browser/layoutService';
|
||||
import { IHoverDelegate, IHoverDelegateOptions } from '@theia/monaco-editor-core/esm/vs/base/browser/ui/hover/hoverDelegate';
|
||||
import { IHoverWidget } from '@theia/monaco-editor-core/esm/vs/base/browser/ui/hover/hover';
|
||||
|
||||
// Copied from @vscode/src/vs/base/parts/quickInput/browser/quickInputList.ts
|
||||
export interface IListElement {
|
||||
readonly index: number;
|
||||
readonly item: IQuickPickItem;
|
||||
readonly saneLabel: string;
|
||||
readonly saneAriaLabel: string;
|
||||
readonly saneDescription?: string;
|
||||
readonly saneDetail?: string;
|
||||
readonly labelHighlights?: IMatch[];
|
||||
readonly descriptionHighlights?: IMatch[];
|
||||
readonly detailHighlights?: IMatch[];
|
||||
readonly checked: boolean;
|
||||
readonly separator?: IQuickPickSeparator;
|
||||
readonly fireButtonTriggered: (event: IQuickPickItemButtonEvent<IQuickPickItem>) => void;
|
||||
}
|
||||
|
||||
class HoverDelegate implements IHoverDelegate {
|
||||
showHover(options: IHoverDelegateOptions, focus?: boolean | undefined): IHoverWidget | undefined {
|
||||
return undefined;
|
||||
}
|
||||
onDidHideHover?: (() => void) | undefined;
|
||||
delay: number;
|
||||
placement?: 'mouse' | 'element' | undefined;
|
||||
showNativeHover?: boolean | undefined;
|
||||
|
||||
}
|
||||
@injectable()
|
||||
export class MonacoQuickInputImplementation implements IQuickInputService {
|
||||
get currentQuickInput(): IQuickInput | undefined {
|
||||
return this.controller.currentQuickInput;
|
||||
}
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
controller: QuickInputController;
|
||||
quickAccess: IQuickAccessController;
|
||||
|
||||
@inject(ApplicationShell)
|
||||
protected readonly shell: ApplicationShell;
|
||||
|
||||
@inject(MonacoColorRegistry)
|
||||
protected readonly colorRegistry: MonacoColorRegistry;
|
||||
|
||||
@inject(ThemeService)
|
||||
protected readonly themeService: ThemeService;
|
||||
|
||||
protected container: HTMLElement;
|
||||
|
||||
protected inQuickOpen: IContextKey<boolean>;
|
||||
|
||||
/**
|
||||
* Help keybinding service prioritize keybindings when this element is focused.
|
||||
*/
|
||||
protected scopedInQuickOpen: IContextKey<boolean>;
|
||||
|
||||
/**
|
||||
* Help keybinding service prioritize keybindings when this element is focused.
|
||||
*/
|
||||
protected scopedContextKeyService: IScopedContextKeyService;
|
||||
|
||||
get backButton(): IQuickInputButton { return this.controller.backButton; }
|
||||
get onShow(): monaco.IEvent<void> { return this.controller.onShow; }
|
||||
get onHide(): monaco.IEvent<void> { return this.controller.onHide; }
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.initContainer();
|
||||
this.initController();
|
||||
this.quickAccess = new QuickAccessController(this, StandaloneServices.get(IInstantiationService));
|
||||
|
||||
const contextService = StandaloneServices.get(IContextKeyService);
|
||||
|
||||
this.inQuickOpen = contextService.createKey<boolean>('inQuickOpen', false);
|
||||
|
||||
this.scopedContextKeyService = contextService.createScoped(this.container);
|
||||
this.scopedInQuickOpen = this.scopedContextKeyService.createKey<boolean>('inQuickOpen', false);
|
||||
|
||||
this.controller.onShow(() => {
|
||||
this.container.style.top = this.shell.mainPanel.node.getBoundingClientRect().top + 'px';
|
||||
this.inQuickOpen.set(true);
|
||||
this.scopedInQuickOpen.set(true);
|
||||
});
|
||||
this.controller.onHide(() => {
|
||||
this.inQuickOpen.set(false);
|
||||
this.scopedInQuickOpen.set(false);
|
||||
});
|
||||
|
||||
this.themeService.initialized.then(() => this.controller.applyStyles(this.computeStyles()));
|
||||
// Hook into the theming service of Monaco to ensure that the updates are ready.
|
||||
StandaloneServices.get(IStandaloneThemeService).onDidColorThemeChange(() => this.controller.applyStyles(this.computeStyles()));
|
||||
window.addEventListener('resize', () => this.updateLayout());
|
||||
}
|
||||
|
||||
setContextKey(key: string | undefined): void {
|
||||
if (key) {
|
||||
StandaloneServices.get(IContextKeyService).createKey<string>(key, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
createQuickWidget(): IQuickWidget {
|
||||
return this.controller.createQuickWidget();
|
||||
}
|
||||
|
||||
createQuickPick<T extends IQuickPickItem>(options: { useSeparators: true; }): IQuickPick<T, { useSeparators: true; }>;
|
||||
createQuickPick<T extends IQuickPickItem>(options: { useSeparators: false; }): IQuickPick<T, { useSeparators: false; }>;
|
||||
createQuickPick<T extends IQuickPickItem>(options: { useSeparators: boolean }): IQuickPick<T, { useSeparators: true; }> | IQuickPick<T, { useSeparators: false; }> {
|
||||
return this.controller.createQuickPick({
|
||||
useSeparators: options.useSeparators
|
||||
});
|
||||
}
|
||||
createInputBox(): IInputBox {
|
||||
return this.controller.createInputBox();
|
||||
}
|
||||
|
||||
open(filter: string): void {
|
||||
this.quickAccess.show(filter);
|
||||
}
|
||||
|
||||
input(options?: IInputOptions, token?: monaco.CancellationToken): Promise<string | undefined> {
|
||||
return this.controller.input(options, token);
|
||||
}
|
||||
|
||||
pick<T extends IQuickPickItem, O extends IPickOptions<T>>(
|
||||
picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: O, token?: monaco.CancellationToken
|
||||
): Promise<(O extends { canPickMany: true; } ? T[] : T) | undefined> {
|
||||
return this.controller.pick(picks, options, token);
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.controller.hide();
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.controller.focus();
|
||||
}
|
||||
|
||||
toggle(): void {
|
||||
this.controller.toggle();
|
||||
}
|
||||
|
||||
applyStyles(styles: IQuickInputStyles): void {
|
||||
this.controller.applyStyles(styles);
|
||||
}
|
||||
|
||||
layout(dimension: monaco.editor.IDimension, titleBarOffset: number): void {
|
||||
this.controller.layout(dimension, titleBarOffset);
|
||||
}
|
||||
|
||||
navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void {
|
||||
this.controller.navigate(next, quickNavigate);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.scopedContextKeyService.dispose();
|
||||
this.controller.dispose();
|
||||
}
|
||||
|
||||
async cancel(): Promise<void> {
|
||||
this.controller.cancel();
|
||||
}
|
||||
|
||||
async back(): Promise<void> {
|
||||
this.controller.back();
|
||||
}
|
||||
|
||||
async accept(keyMods?: IKeyMods): Promise<void> {
|
||||
this.controller.accept(keyMods);
|
||||
}
|
||||
|
||||
private initContainer(): void {
|
||||
const container = this.container = document.createElement('div');
|
||||
container.id = 'quick-input-container';
|
||||
document.body.appendChild(this.container);
|
||||
}
|
||||
|
||||
private initController(): void {
|
||||
const contextKeyService = StandaloneServices.get(IContextKeyService);
|
||||
const instantiationService = StandaloneServices.get(IInstantiationService);
|
||||
const layoutService = StandaloneServices.get(ILayoutService);
|
||||
|
||||
const options: IQuickInputOptions = {
|
||||
idPrefix: 'quickInput_',
|
||||
container: this.container,
|
||||
styles: this.computeStyles(),
|
||||
ignoreFocusOut: () => false,
|
||||
backKeybindingLabel: () => undefined,
|
||||
setContextKey: (id?: string) => this.setContextKey(id),
|
||||
returnFocus: () => this.container.focus(),
|
||||
hoverDelegate: new HoverDelegate(),
|
||||
linkOpenerDelegate: () => {
|
||||
// @monaco-uplift: not sure what to do here
|
||||
}
|
||||
};
|
||||
this.controller = new QuickInputController(options, layoutService, instantiationService, contextKeyService);
|
||||
this.updateLayout();
|
||||
}
|
||||
|
||||
private updateLayout(): void {
|
||||
// Initialize the layout using screen dimensions as monaco computes the actual sizing.
|
||||
// https://github.com/microsoft/vscode/blob/6261075646f055b99068d3688932416f2346dd3b/src/vs/base/parts/quickinput/browser/quickInput.ts#L1799
|
||||
this.controller.layout(this.getClientDimension(), 0);
|
||||
}
|
||||
|
||||
private getClientDimension(): monaco.editor.IDimension {
|
||||
return { width: window.innerWidth, height: window.innerHeight };
|
||||
}
|
||||
|
||||
// @monaco-uplift
|
||||
// Keep the styles up to date with https://github.com/microsoft/vscode/blob/7888ff3a6b104e9e2e3d0f7890ca92dd0828215f/src/vs/platform/quickinput/browser/quickInput.ts#L171.
|
||||
private computeStyles(): IQuickInputStyles {
|
||||
return {
|
||||
toggle: {
|
||||
inputActiveOptionBorder: this.colorRegistry.toCssVariableName('inputOption.activeBorder'),
|
||||
inputActiveOptionForeground: this.colorRegistry.toCssVariableName('inputOption.activeForeground'),
|
||||
inputActiveOptionBackground: this.colorRegistry.toCssVariableName('inputOption.activeBackground')
|
||||
},
|
||||
pickerGroup: {
|
||||
pickerGroupBorder: this.colorRegistry.toCssVariableName('pickerGroup.Border'),
|
||||
pickerGroupForeground: this.colorRegistry.toCssVariableName('pickerGroupForeground')
|
||||
},
|
||||
widget: {
|
||||
quickInputBackground: this.colorRegistry.toCssVariableName('quickInput.background'),
|
||||
quickInputForeground: this.colorRegistry.toCssVariableName('quickInput.foreground'),
|
||||
quickInputTitleBackground: this.colorRegistry.toCssVariableName('quickInputTitle.background'),
|
||||
widgetBorder: this.colorRegistry.toCssVariableName('widget.border'),
|
||||
widgetShadow: this.colorRegistry.toCssVariableName('widget.shadow')
|
||||
},
|
||||
list: {
|
||||
listBackground: this.colorRegistry.toCssVariableName('quickInput.background'),
|
||||
listInactiveFocusForeground: this.colorRegistry.toCssVariableName('quickInputList.focusForeground'),
|
||||
listInactiveSelectionIconForeground: this.colorRegistry.toCssVariableName('quickInputList.focusIconForeground'),
|
||||
listInactiveFocusBackground: this.colorRegistry.toCssVariableName('quickInputList.focusBackground'),
|
||||
listFocusOutline: this.colorRegistry.toCssVariableName('activeContrastBorder'),
|
||||
listInactiveFocusOutline: this.colorRegistry.toCssVariableName('activeContrastBorder'),
|
||||
|
||||
listFocusBackground: this.colorRegistry.toCssVariableName('list.focusBackground'),
|
||||
listFocusForeground: this.colorRegistry.toCssVariableName('list.focusForeground'),
|
||||
listActiveSelectionBackground: this.colorRegistry.toCssVariableName('list.activeSelectionBackground'),
|
||||
listActiveSelectionForeground: this.colorRegistry.toCssVariableName('list.ActiveSelectionForeground'),
|
||||
listActiveSelectionIconForeground: this.colorRegistry.toCssVariableName('list.ActiveSelectionIconForeground'),
|
||||
listFocusAndSelectionOutline: this.colorRegistry.toCssVariableName('list.FocusAndSelectionOutline'),
|
||||
listFocusAndSelectionBackground: this.colorRegistry.toCssVariableName('list.ActiveSelectionBackground'),
|
||||
listFocusAndSelectionForeground: this.colorRegistry.toCssVariableName('list.ActiveSelectionForeground'),
|
||||
listInactiveSelectionBackground: this.colorRegistry.toCssVariableName('list.InactiveSelectionBackground'),
|
||||
listInactiveSelectionForeground: this.colorRegistry.toCssVariableName('list.InactiveSelectionForeground'),
|
||||
listHoverBackground: this.colorRegistry.toCssVariableName('list.HoverBackground'),
|
||||
listHoverForeground: this.colorRegistry.toCssVariableName('list.HoverForeground'),
|
||||
listDropOverBackground: this.colorRegistry.toCssVariableName('list.DropOverBackground'),
|
||||
listDropBetweenBackground: this.colorRegistry.toCssVariableName('list.DropBetweenBackground'),
|
||||
listSelectionOutline: this.colorRegistry.toCssVariableName('activeContrastBorder'),
|
||||
listHoverOutline: this.colorRegistry.toCssVariableName('activeContrastBorder'),
|
||||
treeIndentGuidesStroke: this.colorRegistry.toCssVariableName('tree.indentGuidesStroke'),
|
||||
treeInactiveIndentGuidesStroke: this.colorRegistry.toCssVariableName('tree.inactiveIndentGuidesStroke'),
|
||||
treeStickyScrollBackground: this.colorRegistry.toCssVariableName('tree.StickyScrollBackground'),
|
||||
treeStickyScrollBorder: this.colorRegistry.toCssVariableName('tree.tickyScrollBorde'),
|
||||
treeStickyScrollShadow: this.colorRegistry.toCssVariableName('tree.StickyScrollShadow'),
|
||||
tableColumnsBorder: this.colorRegistry.toCssVariableName('tree.tableColumnsBorder'),
|
||||
tableOddRowsBackgroundColor: this.colorRegistry.toCssVariableName('tree.tableOddRowsBackground'),
|
||||
|
||||
},
|
||||
inputBox: {
|
||||
inputForeground: this.colorRegistry.toCssVariableName('inputForeground'),
|
||||
inputBackground: this.colorRegistry.toCssVariableName('inputBackground'),
|
||||
inputBorder: this.colorRegistry.toCssVariableName('inputBorder'),
|
||||
inputValidationInfoBackground: this.colorRegistry.toCssVariableName('inputValidation.infoBackground'),
|
||||
inputValidationInfoForeground: this.colorRegistry.toCssVariableName('inputValidation.infoForeground'),
|
||||
inputValidationInfoBorder: this.colorRegistry.toCssVariableName('inputValidation.infoBorder'),
|
||||
inputValidationWarningBackground: this.colorRegistry.toCssVariableName('inputValidation.warningBackground'),
|
||||
inputValidationWarningForeground: this.colorRegistry.toCssVariableName('inputValidation.warningForeground'),
|
||||
inputValidationWarningBorder: this.colorRegistry.toCssVariableName('inputValidation.warningBorder'),
|
||||
inputValidationErrorBackground: this.colorRegistry.toCssVariableName('inputValidation.errorBackground'),
|
||||
inputValidationErrorForeground: this.colorRegistry.toCssVariableName('inputValidation.errorForeground'),
|
||||
inputValidationErrorBorder: this.colorRegistry.toCssVariableName('inputValidation.errorBorder'),
|
||||
},
|
||||
countBadge: {
|
||||
badgeBackground: this.colorRegistry.toCssVariableName('badge.background'),
|
||||
badgeForeground: this.colorRegistry.toCssVariableName('badge.foreground'),
|
||||
badgeBorder: this.colorRegistry.toCssVariableName('contrastBorder')
|
||||
},
|
||||
button: {
|
||||
buttonForeground: this.colorRegistry.toCssVariableName('button.foreground'),
|
||||
buttonBackground: this.colorRegistry.toCssVariableName('button.background'),
|
||||
buttonHoverBackground: this.colorRegistry.toCssVariableName('button.hoverBackground'),
|
||||
buttonBorder: this.colorRegistry.toCssVariableName('contrastBorder'),
|
||||
buttonSeparator: this.colorRegistry.toCssVariableName('button.Separator'),
|
||||
buttonSecondaryForeground: this.colorRegistry.toCssVariableName('button.secondaryForeground'),
|
||||
buttonSecondaryBackground: this.colorRegistry.toCssVariableName('button.secondaryBackground'),
|
||||
buttonSecondaryHoverBackground: this.colorRegistry.toCssVariableName('button.secondaryHoverBackground'),
|
||||
},
|
||||
progressBar: {
|
||||
progressBarBackground: this.colorRegistry.toCssVariableName('progressBar.background')
|
||||
},
|
||||
keybindingLabel: {
|
||||
keybindingLabelBackground: this.colorRegistry.toCssVariableName('keybindingLabel.background'),
|
||||
keybindingLabelForeground: this.colorRegistry.toCssVariableName('keybindingLabel.foreground'),
|
||||
keybindingLabelBorder: this.colorRegistry.toCssVariableName('keybindingLabel.border'),
|
||||
keybindingLabelBottomBorder: this.colorRegistry.toCssVariableName('keybindingLabel.bottomBorder'),
|
||||
keybindingLabelShadow: this.colorRegistry.toCssVariableName('widget.shadow')
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MonacoQuickInputService implements QuickInputService {
|
||||
@inject(MonacoQuickInputImplementation)
|
||||
private monacoService: MonacoQuickInputImplementation;
|
||||
|
||||
@inject(KeybindingRegistry)
|
||||
protected readonly keybindingRegistry: KeybindingRegistry;
|
||||
|
||||
get backButton(): QuickInputButton {
|
||||
// need to cast because of vscode issue https://github.com/microsoft/vscode/issues/190584
|
||||
return this.monacoService.backButton as QuickInputButton;
|
||||
}
|
||||
|
||||
get onShow(): Event<void> { return this.monacoService.onShow; }
|
||||
get onHide(): Event<void> { return this.monacoService.onHide; }
|
||||
|
||||
open(filter: string): void {
|
||||
this.monacoService.open(filter);
|
||||
}
|
||||
|
||||
createInputBox(): InputBox {
|
||||
// need to cast because of vscode issue https://github.com/microsoft/vscode/issues/190584
|
||||
return this.monacoService.createInputBox() as InputBox;
|
||||
}
|
||||
|
||||
input(options?: InputOptions, token?: monaco.CancellationToken): Promise<string | undefined> {
|
||||
let inputOptions: IInputOptions | undefined;
|
||||
if (options) {
|
||||
const { validateInput, ...props } = options;
|
||||
inputOptions = { ...props };
|
||||
if (validateInput) {
|
||||
inputOptions.validateInput = async input => validateInput(input);
|
||||
}
|
||||
}
|
||||
return this.monacoService.input(inputOptions, token);
|
||||
}
|
||||
|
||||
async pick<T extends QuickPickItem, O extends PickOptions<T> = PickOptions<T>>(
|
||||
picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: O, token?: CancellationToken
|
||||
): Promise<T[] | T | undefined> {
|
||||
return this.monacoService.pick(picks, options, token);
|
||||
}
|
||||
|
||||
showQuickPick<T extends QuickPickItem>(items: Array<T | QuickPickSeparator>, options?: QuickPickOptions<T>): Promise<T | undefined> {
|
||||
return new Promise<T | undefined>((resolve, reject) => {
|
||||
const wrapped = this.createQuickPick<T>();
|
||||
wrapped.items = items;
|
||||
|
||||
if (options) {
|
||||
wrapped.canSelectMany = !!options.canSelectMany;
|
||||
wrapped.contextKey = options.contextKey;
|
||||
wrapped.description = options.description;
|
||||
wrapped.enabled = options.enabled ?? true;
|
||||
wrapped.ignoreFocusOut = !!options.ignoreFocusOut;
|
||||
wrapped.matchOnDescription = options.matchOnDescription ?? true;
|
||||
wrapped.matchOnDetail = options.matchOnDetail ?? true;
|
||||
wrapped.keepScrollPosition = options.keepScrollPosition ?? false;
|
||||
wrapped.placeholder = options.placeholder;
|
||||
wrapped.step = options.step;
|
||||
wrapped.title = options.title;
|
||||
wrapped.totalSteps = options.totalSteps;
|
||||
|
||||
if (options.activeItem) {
|
||||
wrapped.activeItems = [options.activeItem];
|
||||
}
|
||||
|
||||
wrapped.onDidChangeValue((filter: string) => {
|
||||
if (options.onDidChangeValue) {
|
||||
options.onDidChangeValue(wrapped, filter);
|
||||
}
|
||||
});
|
||||
wrapped.onDidChangeActive((activeItems: Array<T>) => {
|
||||
if (options.onDidChangeActive) {
|
||||
options.onDidChangeActive(wrapped, activeItems);
|
||||
}
|
||||
});
|
||||
wrapped.onDidTriggerButton((button: IQuickInputButton) => {
|
||||
if (options.onDidTriggerButton) {
|
||||
// need to cast because of vscode issue https://github.com/microsoft/vscode/issues/190584
|
||||
options.onDidTriggerButton(button as QuickInputButton);
|
||||
}
|
||||
});
|
||||
wrapped.onDidTriggerItemButton((event: QuickPickItemButtonEvent<T>) => {
|
||||
if (options.onDidTriggerItemButton) {
|
||||
// https://github.com/theia-ide/vscode/blob/standalone/0.23.x/src/vs/base/parts/quickinput/browser/quickInput.ts#L1387
|
||||
options.onDidTriggerItemButton(
|
||||
{
|
||||
...event,
|
||||
removeItem: () => {
|
||||
wrapped.items = wrapped.items.filter(item => item !== event.item);
|
||||
wrapped.activeItems = wrapped.activeItems.filter(item => item !== event.item);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
wrapped.onDidChangeSelection((selectedItems: Array<T>) => {
|
||||
if (options.onDidChangeSelection) {
|
||||
options.onDidChangeSelection(wrapped, selectedItems);
|
||||
}
|
||||
});
|
||||
}
|
||||
wrapped.onDidAccept(() => {
|
||||
if (options?.onDidAccept) {
|
||||
options.onDidAccept();
|
||||
}
|
||||
wrapped.hide();
|
||||
resolve(wrapped.selectedItems[0]);
|
||||
});
|
||||
wrapped.onDidHide(() => {
|
||||
if (options?.onDidHide) {
|
||||
options?.onDidHide();
|
||||
};
|
||||
wrapped.dispose();
|
||||
setTimeout(() => resolve(undefined));
|
||||
});
|
||||
wrapped.show();
|
||||
}).then(item => {
|
||||
if (item?.execute) {
|
||||
item.execute();
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
createQuickPick<T extends QuickPickItem>(): QuickPick<T> {
|
||||
const quickPick = this.monacoService.createQuickPick<MonacoQuickPickItem<T>>({ useSeparators: true });
|
||||
return this.wrapQuickPick(quickPick);
|
||||
}
|
||||
|
||||
wrapQuickPick<T extends QuickPickItem>(wrapped: IQuickPick<MonacoQuickPickItem<T>, { useSeparators: true }>): QuickPick<T> {
|
||||
return new MonacoQuickPick(wrapped, this.keybindingRegistry);
|
||||
}
|
||||
|
||||
protected convertItems<T extends QuickPickItem>(item: T): MonacoQuickPickItem<T> {
|
||||
return new MonacoQuickPickItem(item, this.keybindingRegistry);
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
return this.monacoService.hide();
|
||||
}
|
||||
}
|
||||
|
||||
class MonacoQuickInput {
|
||||
constructor(protected readonly wrapped: IQuickInput) {
|
||||
}
|
||||
|
||||
get onDidHide(): Event<{ reason: QuickInputHideReason }> { return this.wrapped.onDidHide; }
|
||||
get onDispose(): Event<void> { return this.wrapped.onDispose; }
|
||||
|
||||
get title(): string | undefined {
|
||||
return this.wrapped.title;
|
||||
}
|
||||
|
||||
set title(v: string | undefined) {
|
||||
this.wrapped.title = v;
|
||||
}
|
||||
|
||||
get description(): string | undefined {
|
||||
return this.wrapped.description;
|
||||
}
|
||||
|
||||
set description(v: string | undefined) {
|
||||
this.wrapped.description = v;
|
||||
}
|
||||
get step(): number | undefined {
|
||||
return this.wrapped.step;
|
||||
}
|
||||
|
||||
set step(v: number | undefined) {
|
||||
this.wrapped.step = v;
|
||||
}
|
||||
|
||||
get enabled(): boolean {
|
||||
return this.wrapped.enabled;
|
||||
}
|
||||
|
||||
set enabled(v: boolean) {
|
||||
this.wrapped.enabled = v;
|
||||
}
|
||||
get totalSteps(): number | undefined {
|
||||
return this.wrapped.totalSteps;
|
||||
}
|
||||
|
||||
set totalSteps(v: number | undefined) {
|
||||
this.wrapped.totalSteps = v;
|
||||
}
|
||||
get contextKey(): string | undefined {
|
||||
return this.wrapped.contextKey;
|
||||
}
|
||||
|
||||
set contextKey(v: string | undefined) {
|
||||
this.wrapped.contextKey = v;
|
||||
}
|
||||
|
||||
get busy(): boolean {
|
||||
return this.wrapped.busy;
|
||||
}
|
||||
|
||||
set busy(v: boolean) {
|
||||
this.wrapped.busy = v;
|
||||
}
|
||||
|
||||
get ignoreFocusOut(): boolean {
|
||||
return this.wrapped.ignoreFocusOut;
|
||||
}
|
||||
|
||||
set ignoreFocusOut(v: boolean) {
|
||||
this.wrapped.ignoreFocusOut = v;
|
||||
}
|
||||
|
||||
show(): void {
|
||||
this.wrapped.show();
|
||||
}
|
||||
hide(): void {
|
||||
this.wrapped.hide();
|
||||
}
|
||||
dispose(): void {
|
||||
this.wrapped.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MonacoQuickPick<T extends QuickPickItem> extends MonacoQuickInput implements QuickPick<T> {
|
||||
constructor(protected override readonly wrapped: IQuickPick<MonacoQuickPickItem<T>, { useSeparators: true }>, protected readonly keybindingRegistry: KeybindingRegistry) {
|
||||
super(wrapped);
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
return this.wrapped.value;
|
||||
};
|
||||
|
||||
set value(v: string) {
|
||||
this.wrapped.value = v;
|
||||
}
|
||||
|
||||
get placeholder(): string | undefined {
|
||||
return this.wrapped.placeholder;
|
||||
}
|
||||
|
||||
set placeholder(v: string | undefined) {
|
||||
this.wrapped.placeholder = v;
|
||||
}
|
||||
|
||||
get canSelectMany(): boolean {
|
||||
return this.wrapped.canSelectMany;
|
||||
}
|
||||
|
||||
set canSelectMany(v: boolean) {
|
||||
this.wrapped.canSelectMany = v;
|
||||
}
|
||||
|
||||
get matchOnDescription(): boolean {
|
||||
return this.wrapped.matchOnDescription;
|
||||
}
|
||||
|
||||
set matchOnDescription(v: boolean) {
|
||||
this.wrapped.matchOnDescription = v;
|
||||
}
|
||||
|
||||
get matchOnDetail(): boolean {
|
||||
return this.wrapped.matchOnDetail;
|
||||
}
|
||||
|
||||
set matchOnDetail(v: boolean) {
|
||||
this.wrapped.matchOnDetail = v;
|
||||
}
|
||||
|
||||
get keepScrollPosition(): boolean {
|
||||
return this.wrapped.keepScrollPosition;
|
||||
}
|
||||
|
||||
set keepScrollPosition(v: boolean) {
|
||||
this.wrapped.keepScrollPosition = v;
|
||||
}
|
||||
|
||||
get items(): readonly (T | QuickPickSeparator)[] {
|
||||
// need to cast because of vscode issue https://github.com/microsoft/vscode/issues/190584
|
||||
return this.wrapped.items.map(item => {
|
||||
if (item instanceof MonacoQuickPickItem) {
|
||||
return item.item;
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get buttons(): ReadonlyArray<QuickInputButton> {
|
||||
return this.wrapped.buttons as QuickInputButton[];
|
||||
}
|
||||
|
||||
set buttons(buttons: ReadonlyArray<QuickInputButton>) {
|
||||
this.wrapped.buttons = buttons;
|
||||
}
|
||||
|
||||
set items(itemList: readonly (T | QuickPickSeparator)[]) {
|
||||
// We need to store and apply the currently selected active items.
|
||||
// Since monaco compares these items by reference equality, creating new wrapped items will unmark any active items.
|
||||
// Assigning the `activeItems` again will restore all active items even after the items array has changed.
|
||||
// See also the `findMonacoItemReferences` method.
|
||||
const active = this.activeItems;
|
||||
this.wrapped.items = itemList.map(item => QuickPickSeparator.is(item) ? item : new MonacoQuickPickItem<T>(item, this.keybindingRegistry));
|
||||
if (active.length !== 0) {
|
||||
this.activeItems = active; // If this is done with an empty activeItems array, then it will undo first item focus on quick menus.
|
||||
}
|
||||
}
|
||||
|
||||
set activeItems(itemList: readonly T[]) {
|
||||
this.wrapped.activeItems = this.findMonacoItemReferences(this.wrapped.items, itemList);
|
||||
}
|
||||
|
||||
get activeItems(): readonly (T)[] {
|
||||
return this.wrapped.activeItems.map(item => item.item);
|
||||
}
|
||||
|
||||
set selectedItems(itemList: readonly T[]) {
|
||||
this.wrapped.selectedItems = this.findMonacoItemReferences(this.wrapped.items, itemList);
|
||||
}
|
||||
|
||||
get selectedItems(): readonly (T)[] {
|
||||
return this.wrapped.selectedItems.map(item => item.item);
|
||||
}
|
||||
|
||||
readonly onDidAccept: Event<{ inBackground: boolean }> = this.wrapped.onDidAccept;
|
||||
readonly onDidChangeValue: Event<string> = this.wrapped.onDidChangeValue;
|
||||
|
||||
// need to cast because of vscode issue https://github.com/microsoft/vscode/issues/190584
|
||||
readonly onDidTriggerButton: Event<QuickInputButton> = this.wrapped.onDidTriggerButton as Event<QuickInputButton>;
|
||||
readonly onDidTriggerItemButton: Event<QuickPickItemButtonEvent<T>> =
|
||||
Event.map(this.wrapped.onDidTriggerItemButton, (evt: IQuickPickItemButtonEvent<MonacoQuickPickItem<T>>) => ({
|
||||
item: evt.item.item,
|
||||
button: evt.button
|
||||
})) as Event<QuickPickItemButtonEvent<T>>;
|
||||
readonly onDidChangeActive: Event<T[]> = Event.map(
|
||||
this.wrapped.onDidChangeActive,
|
||||
(items: MonacoQuickPickItem<T>[]) => items.map(item => item.item));
|
||||
readonly onDidChangeSelection: Event<T[]> = Event.map(
|
||||
this.wrapped.onDidChangeSelection, (items: MonacoQuickPickItem<T>[]) => items.map(item => item.item));
|
||||
|
||||
/**
|
||||
* Monaco doesn't check for deep equality when setting the `activeItems` or `selectedItems`.
|
||||
* Instead we have to find the references of the monaco wrappers that contain the selected/active items
|
||||
*/
|
||||
protected findMonacoItemReferences(source: readonly (MonacoQuickPickItem<T> | IQuickPickSeparator)[], items: readonly QuickPickItem[]): MonacoQuickPickItem<T>[] {
|
||||
const monacoReferences: MonacoQuickPickItem<T>[] = [];
|
||||
for (const item of items) {
|
||||
for (const wrappedItem of source) {
|
||||
if (wrappedItem instanceof MonacoQuickPickItem && wrappedItem.item === item) {
|
||||
monacoReferences.push(wrappedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return monacoReferences;
|
||||
}
|
||||
}
|
||||
|
||||
export class MonacoQuickPickItem<T extends QuickPickItem> implements IQuickPickItem {
|
||||
readonly type?: 'item';
|
||||
readonly id?: string;
|
||||
readonly label: string;
|
||||
readonly meta?: string;
|
||||
readonly ariaLabel?: string;
|
||||
readonly description?: string;
|
||||
readonly detail?: string;
|
||||
readonly keybinding?: ResolvedKeybinding;
|
||||
readonly iconClasses?: string[];
|
||||
buttons?: IQuickInputButton[];
|
||||
readonly alwaysShow?: boolean;
|
||||
readonly highlights?: QuickPickItemHighlights;
|
||||
|
||||
constructor(readonly item: T, kbRegistry: KeybindingRegistry) {
|
||||
this.type = item.type;
|
||||
this.id = item.id;
|
||||
this.label = item.label;
|
||||
this.meta = item.meta;
|
||||
this.ariaLabel = item.ariaLabel;
|
||||
this.description = item.description;
|
||||
this.detail = item.detail;
|
||||
this.keybinding = item.keySequence ? new MonacoResolvedKeybinding(item.keySequence, kbRegistry) : undefined;
|
||||
this.iconClasses = item.iconClasses;
|
||||
this.buttons = item.buttons;
|
||||
this.alwaysShow = item.alwaysShow;
|
||||
this.highlights = item.highlights;
|
||||
}
|
||||
|
||||
accept(): void {
|
||||
if (this.item.execute) {
|
||||
this.item.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
162
packages/monaco/src/browser/monaco-resolved-keybinding.ts
Normal file
162
packages/monaco/src/browser/monaco-resolved-keybinding.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { KeyCode as MonacoKeyCode } from '@theia/monaco-editor-core/esm/vs/base/common/keyCodes';
|
||||
import {
|
||||
ResolvedKeybinding, ResolvedChord, SingleModifierChord, KeyCodeChord, Chord
|
||||
} from '@theia/monaco-editor-core/esm/vs/base/common/keybindings';
|
||||
import { ElectronAcceleratorLabelProvider, UILabelProvider, UserSettingsLabelProvider } from '@theia/monaco-editor-core/esm/vs/base/common/keybindingLabels';
|
||||
import { USLayoutResolvedKeybinding } from '@theia/monaco-editor-core/esm/vs/platform/keybinding/common/usLayoutResolvedKeybinding';
|
||||
import * as MonacoPlatform from '@theia/monaco-editor-core/esm/vs/base/common/platform';
|
||||
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
||||
import { KeyCode, KeySequence, Keystroke, Key, KeyModifier } from '@theia/core/lib/browser/keys';
|
||||
import { isOSX } from '@theia/core/lib/common/os';
|
||||
import { KEY_CODE_MAP } from './monaco-keycode-map';
|
||||
|
||||
export class MonacoResolvedKeybinding extends ResolvedKeybinding {
|
||||
|
||||
protected readonly chords: ResolvedChord[];
|
||||
|
||||
constructor(protected readonly keySequence: KeySequence, keybindingService: KeybindingRegistry) {
|
||||
super();
|
||||
this.chords = keySequence.map(keyCode => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const keyLabel = keyCode.key ? keybindingService.acceleratorForKey(keyCode.key) : null;
|
||||
const keyAriaLabel = keyLabel;
|
||||
return new ResolvedChord(
|
||||
keyCode.ctrl,
|
||||
keyCode.shift,
|
||||
keyCode.alt,
|
||||
keyCode.meta,
|
||||
keyLabel,
|
||||
keyAriaLabel
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getLabel(): string | null {
|
||||
return UILabelProvider.toLabel(MonacoPlatform.OS, this.chords, p => p.keyLabel);
|
||||
}
|
||||
|
||||
getAriaLabel(): string | null {
|
||||
return UILabelProvider.toLabel(MonacoPlatform.OS, this.chords, p => p.keyAriaLabel);
|
||||
}
|
||||
|
||||
getElectronAccelerator(): string | null {
|
||||
if (this.hasMultipleChords()) {
|
||||
// Electron cannot handle chords
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
return null;
|
||||
}
|
||||
return ElectronAcceleratorLabelProvider.toLabel(MonacoPlatform.OS, this.chords, p => p.keyLabel);
|
||||
}
|
||||
|
||||
getUserSettingsLabel(): string | null {
|
||||
return UserSettingsLabelProvider.toLabel(MonacoPlatform.OS, this.chords, p => p.keyLabel);
|
||||
}
|
||||
|
||||
isWYSIWYG(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
hasMultipleChords(): boolean {
|
||||
return this.chords.length > 1;
|
||||
}
|
||||
|
||||
getDispatchChords(): (string | null)[] {
|
||||
return this.keySequence.map(keyCode => USLayoutResolvedKeybinding.getDispatchStr(this.toKeybinding(keyCode)));
|
||||
}
|
||||
|
||||
getSingleModifierDispatchChords(): (SingleModifierChord | null)[] {
|
||||
return this.keySequence.map(keybinding => this.getSingleModifierDispatchPart(keybinding));
|
||||
}
|
||||
|
||||
protected getSingleModifierDispatchPart(code: KeyCode): SingleModifierChord | null {
|
||||
if (code.key?.keyCode === undefined) {
|
||||
return null; // eslint-disable-line no-null/no-null
|
||||
}
|
||||
if (KEY_CODE_MAP[code.key?.keyCode] === MonacoKeyCode.Ctrl && !code.shift && !code.alt && !code.meta) {
|
||||
return 'ctrl';
|
||||
}
|
||||
if (KEY_CODE_MAP[code.key?.keyCode] === MonacoKeyCode.Shift && !code.ctrl && !code.alt && !code.meta) {
|
||||
return 'shift';
|
||||
}
|
||||
if (KEY_CODE_MAP[code.key?.keyCode] === MonacoKeyCode.Alt && !code.shift && !code.ctrl && !code.meta) {
|
||||
return 'alt';
|
||||
}
|
||||
if (KEY_CODE_MAP[code.key?.keyCode] === MonacoKeyCode.Meta && !code.shift && !code.alt && !code.ctrl) {
|
||||
return 'meta';
|
||||
}
|
||||
return null; // eslint-disable-line no-null/no-null
|
||||
}
|
||||
|
||||
private toKeybinding(keyCode: KeyCode): KeyCodeChord {
|
||||
return new KeyCodeChord(
|
||||
keyCode.ctrl,
|
||||
keyCode.shift,
|
||||
keyCode.alt,
|
||||
keyCode.meta,
|
||||
KEY_CODE_MAP[keyCode.key!.keyCode]
|
||||
);
|
||||
}
|
||||
|
||||
public getChords(): ResolvedChord[] {
|
||||
return this.chords;
|
||||
}
|
||||
|
||||
static toKeybinding(keybindings: Array<Chord>): string {
|
||||
return keybindings.map(binding => this.keyCode(binding)).join(' ');
|
||||
}
|
||||
|
||||
static keyCode(keybinding: Chord): KeyCode {
|
||||
const keyCode = keybinding instanceof KeyCodeChord ? keybinding.keyCode : USLayoutResolvedKeybinding['_scanCodeToKeyCode'](keybinding.scanCode);
|
||||
const sequence: Keystroke = {
|
||||
first: Key.getKey(this.monaco2BrowserKeyCode(keyCode & 0xff)),
|
||||
modifiers: []
|
||||
};
|
||||
if (keybinding.ctrlKey) {
|
||||
if (isOSX) {
|
||||
sequence.modifiers!.push(KeyModifier.MacCtrl);
|
||||
} else {
|
||||
sequence.modifiers!.push(KeyModifier.CtrlCmd);
|
||||
}
|
||||
}
|
||||
if (keybinding.shiftKey) {
|
||||
sequence.modifiers!.push(KeyModifier.Shift);
|
||||
}
|
||||
if (keybinding.altKey) {
|
||||
sequence.modifiers!.push(KeyModifier.Alt);
|
||||
}
|
||||
if (keybinding.metaKey && sequence.modifiers!.indexOf(KeyModifier.CtrlCmd) === -1) {
|
||||
sequence.modifiers!.push(KeyModifier.CtrlCmd);
|
||||
}
|
||||
return KeyCode.createKeyCode(sequence);
|
||||
}
|
||||
|
||||
static keySequence(keybinding: Chord[]): KeySequence {
|
||||
return keybinding.map(part => this.keyCode(part));
|
||||
}
|
||||
|
||||
private static monaco2BrowserKeyCode(keyCode: MonacoKeyCode): number {
|
||||
for (let i = 0; i < KEY_CODE_MAP.length; i++) {
|
||||
if (KEY_CODE_MAP[i] === keyCode) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
306
packages/monaco/src/browser/monaco-snippet-suggest-provider.ts
Normal file
306
packages/monaco/src/browser/monaco-snippet-suggest-provider.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as jsoncparser from 'jsonc-parser';
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
import { FileOperationError } from '@theia/filesystem/lib/common/files';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { SnippetParser } from '@theia/monaco-editor-core/esm/vs/editor/contrib/snippet/browser/snippetParser';
|
||||
import { isObject } from '@theia/core/lib/common';
|
||||
|
||||
@injectable()
|
||||
export class MonacoSnippetSuggestProvider implements monaco.languages.CompletionItemProvider {
|
||||
|
||||
private static readonly _maxPrefix = 10000;
|
||||
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
|
||||
protected readonly snippets = new Map<string, Snippet[]>();
|
||||
protected readonly pendingSnippets = new Map<string, Promise<void>[]>();
|
||||
|
||||
async provideCompletionItems(model: monaco.editor.ITextModel, position: monaco.Position,
|
||||
context: monaco.languages.CompletionContext): Promise<monaco.languages.CompletionList | undefined> {
|
||||
|
||||
// copied and modified from https://github.com/microsoft/vscode/blob/master/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts
|
||||
if (position.column >= MonacoSnippetSuggestProvider._maxPrefix) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (context.triggerKind === monaco.languages.CompletionTriggerKind.TriggerCharacter && context.triggerCharacter === ' ') {
|
||||
// no snippets when suggestions have been triggered by space
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const languageId = model.getLanguageId(); // TODO: look up a language id at the position
|
||||
await this.loadSnippets(languageId);
|
||||
const snippetsForLanguage = this.snippets.get(languageId) || [];
|
||||
|
||||
const pos = { lineNumber: position.lineNumber, column: 1 };
|
||||
const lineOffsets: number[] = [];
|
||||
const linePrefixLow = model.getLineContent(position.lineNumber).substring(0, position.column - 1).toLowerCase();
|
||||
const endsInWhitespace = linePrefixLow.match(/\s$/);
|
||||
|
||||
while (pos.column < position.column) {
|
||||
const word = model.getWordAtPosition(pos);
|
||||
if (word) {
|
||||
// at a word
|
||||
lineOffsets.push(word.startColumn - 1);
|
||||
pos.column = word.endColumn + 1;
|
||||
if (word.endColumn - 1 < linePrefixLow.length && !/\s/.test(linePrefixLow[word.endColumn - 1])) {
|
||||
lineOffsets.push(word.endColumn - 1);
|
||||
}
|
||||
} else if (!/\s/.test(linePrefixLow[pos.column - 1])) {
|
||||
// at a none-whitespace character
|
||||
lineOffsets.push(pos.column - 1);
|
||||
pos.column += 1;
|
||||
} else {
|
||||
// always advance!
|
||||
pos.column += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const availableSnippets = new Set<Snippet>();
|
||||
snippetsForLanguage.forEach(availableSnippets.add, availableSnippets);
|
||||
const suggestions: MonacoSnippetSuggestion[] = [];
|
||||
for (const start of lineOffsets) {
|
||||
availableSnippets.forEach(snippet => {
|
||||
if (this.isPatternInWord(linePrefixLow, start, linePrefixLow.length, snippet.prefix.toLowerCase(), 0, snippet.prefix.length)) {
|
||||
suggestions.push(new MonacoSnippetSuggestion(snippet, monaco.Range.fromPositions(position.delta(0, -(linePrefixLow.length - start)), position)));
|
||||
availableSnippets.delete(snippet);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (endsInWhitespace || lineOffsets.length === 0) {
|
||||
// add remaining snippets when the current prefix ends in whitespace or when no
|
||||
// interesting positions have been found
|
||||
availableSnippets.forEach(snippet => {
|
||||
suggestions.push(new MonacoSnippetSuggestion(snippet, monaco.Range.fromPositions(position)));
|
||||
});
|
||||
}
|
||||
|
||||
// disambiguate suggestions with same labels
|
||||
suggestions.sort(MonacoSnippetSuggestion.compareByLabel);
|
||||
return { suggestions };
|
||||
}
|
||||
|
||||
resolveCompletionItem?(item: monaco.languages.CompletionItem, token: monaco.CancellationToken): monaco.languages.CompletionItem {
|
||||
return item instanceof MonacoSnippetSuggestion ? item.resolve() : item;
|
||||
}
|
||||
|
||||
protected async loadSnippets(scope: string): Promise<void> {
|
||||
const pending: Promise<void>[] = [];
|
||||
pending.push(...(this.pendingSnippets.get(scope) || []));
|
||||
pending.push(...(this.pendingSnippets.get('*') || []));
|
||||
if (pending.length) {
|
||||
await Promise.all(pending);
|
||||
}
|
||||
}
|
||||
|
||||
fromURI(uri: string | URI, options: SnippetLoadOptions): Disposable {
|
||||
const toDispose = new DisposableCollection(Disposable.create(() => { /* mark as not disposed */ }));
|
||||
const pending = this.loadURI(uri, options, toDispose);
|
||||
const { language } = options;
|
||||
const scopes = Array.isArray(language) ? language : !!language ? [language] : ['*'];
|
||||
for (const scope of scopes) {
|
||||
const pendingSnippets = this.pendingSnippets.get(scope) || [];
|
||||
pendingSnippets.push(pending);
|
||||
this.pendingSnippets.set(scope, pendingSnippets);
|
||||
toDispose.push(Disposable.create(() => {
|
||||
const index = pendingSnippets.indexOf(pending);
|
||||
if (index !== -1) {
|
||||
pendingSnippets.splice(index, 1);
|
||||
}
|
||||
}));
|
||||
}
|
||||
return toDispose;
|
||||
}
|
||||
|
||||
/**
|
||||
* should NOT throw to prevent load errors on suggest
|
||||
*/
|
||||
protected async loadURI(uri: string | URI, options: SnippetLoadOptions, toDispose: DisposableCollection): Promise<void> {
|
||||
try {
|
||||
const resource = typeof uri === 'string' ? new URI(uri) : uri;
|
||||
const { value } = await this.fileService.read(resource);
|
||||
if (toDispose.disposed) {
|
||||
return;
|
||||
}
|
||||
const snippets = value && jsoncparser.parse(value, undefined, { disallowComments: false });
|
||||
toDispose.push(this.fromJSON(snippets, options));
|
||||
} catch (e) {
|
||||
if (!(e instanceof FileOperationError)) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fromJSON(snippets: JsonSerializedSnippets | undefined, { language, source }: SnippetLoadOptions): Disposable {
|
||||
const toDispose = new DisposableCollection();
|
||||
this.parseSnippets(snippets, (name, snippet) => {
|
||||
const { isFileTemplate, prefix, body, description } = snippet;
|
||||
const parsedBody = Array.isArray(body) ? body.join('\n') : body;
|
||||
const parsedPrefixes = !prefix ? [''] : Array.isArray(prefix) ? prefix : [prefix];
|
||||
|
||||
if (typeof parsedBody !== 'string') {
|
||||
return;
|
||||
}
|
||||
const scopes: string[] = [];
|
||||
if (language) {
|
||||
if (Array.isArray(language)) {
|
||||
scopes.push(...language);
|
||||
} else {
|
||||
scopes.push(language);
|
||||
}
|
||||
} else if (typeof snippet.scope === 'string') {
|
||||
for (const rawScope of snippet.scope.split(',')) {
|
||||
const scope = rawScope.trim();
|
||||
if (scope) {
|
||||
scopes.push(scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
parsedPrefixes.forEach(parsedPrefix => toDispose.push(this.push({
|
||||
isFileTemplate: Boolean(isFileTemplate),
|
||||
scopes,
|
||||
name,
|
||||
prefix: parsedPrefix,
|
||||
description,
|
||||
body: parsedBody,
|
||||
source
|
||||
})));
|
||||
});
|
||||
return toDispose;
|
||||
}
|
||||
protected parseSnippets(snippets: JsonSerializedSnippets | undefined, accept: (name: string, snippet: JsonSerializedSnippet) => void): void {
|
||||
for (const [name, scopeOrTemplate] of Object.entries(snippets ?? {})) {
|
||||
if (JsonSerializedSnippet.is(scopeOrTemplate)) {
|
||||
accept(name, scopeOrTemplate);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
for (const [name, template] of Object.entries(scopeOrTemplate)) {
|
||||
accept(name, template);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
push(...snippets: Snippet[]): Disposable {
|
||||
const toDispose = new DisposableCollection();
|
||||
for (const snippet of snippets) {
|
||||
for (const scope of snippet.scopes) {
|
||||
const languageSnippets = this.snippets.get(scope) || [];
|
||||
languageSnippets.push(snippet);
|
||||
this.snippets.set(scope, languageSnippets);
|
||||
toDispose.push(Disposable.create(() => {
|
||||
const index = languageSnippets.indexOf(snippet);
|
||||
if (index !== -1) {
|
||||
languageSnippets.splice(index, 1);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
return toDispose;
|
||||
}
|
||||
|
||||
protected isPatternInWord(patternLow: string, patternPos: number, patternLen: number, wordLow: string, wordPos: number, wordLen: number): boolean {
|
||||
while (patternPos < patternLen && wordPos < wordLen) {
|
||||
if (patternLow[patternPos] === wordLow[wordPos]) {
|
||||
patternPos += 1;
|
||||
}
|
||||
wordPos += 1;
|
||||
}
|
||||
return patternPos === patternLen; // pattern must be exhausted
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface SnippetLoadOptions {
|
||||
language?: string | string[]
|
||||
source: string
|
||||
}
|
||||
|
||||
export interface JsonSerializedSnippets {
|
||||
[name: string]: JsonSerializedSnippet | { [name: string]: JsonSerializedSnippet };
|
||||
}
|
||||
export interface JsonSerializedSnippet {
|
||||
isFileTemplate?: boolean;
|
||||
body: string | string[];
|
||||
scope?: string;
|
||||
prefix?: string | string[];
|
||||
description: string;
|
||||
}
|
||||
export namespace JsonSerializedSnippet {
|
||||
export function is(obj: unknown): obj is JsonSerializedSnippet {
|
||||
return isObject(obj) && 'body' in obj;
|
||||
}
|
||||
}
|
||||
|
||||
export interface Snippet {
|
||||
readonly isFileTemplate: boolean
|
||||
readonly scopes: string[]
|
||||
readonly name: string
|
||||
readonly prefix: string
|
||||
readonly description: string
|
||||
readonly body: string
|
||||
readonly source: string
|
||||
}
|
||||
|
||||
export class MonacoSnippetSuggestion implements monaco.languages.CompletionItem {
|
||||
|
||||
readonly label: string;
|
||||
readonly detail: string;
|
||||
readonly sortText: string;
|
||||
readonly noAutoAccept = true;
|
||||
readonly kind = monaco.languages.CompletionItemKind.Snippet;
|
||||
readonly insertTextRules = monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet;
|
||||
|
||||
insertText: string;
|
||||
documentation?: monaco.IMarkdownString;
|
||||
|
||||
constructor(
|
||||
protected readonly snippet: Snippet,
|
||||
readonly range: monaco.Range
|
||||
) {
|
||||
this.label = snippet.prefix;
|
||||
this.detail = `${snippet.description || snippet.name} (${snippet.source})`;
|
||||
this.insertText = snippet.body;
|
||||
this.sortText = `z-${snippet.prefix}`;
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
protected resolved = false;
|
||||
resolve(): MonacoSnippetSuggestion {
|
||||
if (!this.resolved) {
|
||||
const codeSnippet = new SnippetParser().parse(this.snippet.body).toString();
|
||||
this.documentation = { value: '```\n' + codeSnippet + '```' };
|
||||
this.resolved = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
static compareByLabel(a: MonacoSnippetSuggestion, b: MonacoSnippetSuggestion): number {
|
||||
return a.label > b.label ? 1 : a.label < b.label ? -1 : 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 STMicroelectronics and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { IDisposable } from '@theia/monaco-editor-core/esm/vs/base/common/lifecycle';
|
||||
import { StandaloneThemeService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneThemeService';
|
||||
|
||||
export class MonacoStandaloneThemeService extends StandaloneThemeService {
|
||||
protected get styleElements(): HTMLStyleElement[] {
|
||||
// access private style element array
|
||||
return (this as any)._styleElements;
|
||||
}
|
||||
|
||||
protected get allCSS(): string {
|
||||
return (this as any)._allCSS;
|
||||
}
|
||||
|
||||
override registerEditorContainer(domNode: HTMLElement): IDisposable {
|
||||
const style = domNode.ownerDocument.createElement('style');
|
||||
style.type = 'text/css';
|
||||
style.media = 'screen';
|
||||
style.className = 'monaco-colors';
|
||||
style.textContent = this.allCSS;
|
||||
domNode.ownerDocument.head.appendChild(style);
|
||||
this.styleElements.push(style);
|
||||
return {
|
||||
dispose: () => {
|
||||
for (let i = 0; i < this.styleElements.length; i++) {
|
||||
if (this.styleElements[i] === style) {
|
||||
this.styleElements.splice(i, 1);
|
||||
style.remove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
107
packages/monaco/src/browser/monaco-status-bar-contribution.ts
Normal file
107
packages/monaco/src/browser/monaco-status-bar-contribution.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 Ericsson
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { DisposableCollection, nls } from '@theia/core';
|
||||
import { StatusBar, StatusBarAlignment, Widget, WidgetStatusBarContribution } from '@theia/core/lib/browser';
|
||||
import { EditorCommands, EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { MonacoEditor } from './monaco-editor';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
|
||||
export const EDITOR_STATUS_TABBING_CONFIG = 'editor-status-tabbing-config';
|
||||
export const EDITOR_STATUS_EOL = 'editor-status-eol';
|
||||
|
||||
@injectable()
|
||||
export class MonacoStatusBarContribution implements WidgetStatusBarContribution<EditorWidget> {
|
||||
|
||||
protected readonly toDispose = new DisposableCollection();
|
||||
|
||||
canHandle(widget: Widget): widget is EditorWidget {
|
||||
if (widget instanceof EditorWidget) {
|
||||
return Boolean(this.getModel(widget));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
activate(statusBar: StatusBar, editor: EditorWidget): void {
|
||||
this.toDispose.dispose();
|
||||
const editorModel = this.getModel(editor);
|
||||
if (editorModel) {
|
||||
this.setConfigTabSizeWidget(statusBar, editorModel);
|
||||
this.setLineEndingWidget(statusBar, editorModel);
|
||||
this.toDispose.push(editorModel.onDidChangeOptions(() => {
|
||||
this.setConfigTabSizeWidget(statusBar, editorModel);
|
||||
this.setLineEndingWidget(statusBar, editorModel);
|
||||
}));
|
||||
let previous = editorModel.getEOL();
|
||||
this.toDispose.push(editorModel.onDidChangeContent(e => {
|
||||
if (previous !== e.eol) {
|
||||
previous = e.eol;
|
||||
this.setLineEndingWidget(statusBar, editorModel);
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
this.deactivate(statusBar);
|
||||
}
|
||||
}
|
||||
|
||||
deactivate(statusBar: StatusBar): void {
|
||||
this.toDispose.dispose();
|
||||
this.removeConfigTabSizeWidget(statusBar);
|
||||
this.removeLineEndingWidget(statusBar);
|
||||
}
|
||||
|
||||
protected setConfigTabSizeWidget(statusBar: StatusBar, model: monaco.editor.ITextModel): void {
|
||||
const modelOptions = model.getOptions();
|
||||
const tabSize = modelOptions.tabSize;
|
||||
const indentSize = modelOptions.indentSize;
|
||||
const spaceOrTabSizeMessage = modelOptions.insertSpaces
|
||||
? nls.localizeByDefault('Spaces: {0}', indentSize)
|
||||
: nls.localizeByDefault('Tab Size: {0}', tabSize);
|
||||
statusBar.setElement(EDITOR_STATUS_TABBING_CONFIG, {
|
||||
text: spaceOrTabSizeMessage,
|
||||
alignment: StatusBarAlignment.RIGHT,
|
||||
priority: 10,
|
||||
command: EditorCommands.CONFIG_INDENTATION.id,
|
||||
tooltip: nls.localizeByDefault('Select Indentation')
|
||||
});
|
||||
}
|
||||
|
||||
protected removeConfigTabSizeWidget(statusBar: StatusBar): void {
|
||||
statusBar.removeElement(EDITOR_STATUS_TABBING_CONFIG);
|
||||
}
|
||||
|
||||
protected setLineEndingWidget(statusBar: StatusBar, model: monaco.editor.ITextModel): void {
|
||||
const eol = model.getEOL();
|
||||
const text = eol === '\n' ? 'LF' : 'CRLF';
|
||||
statusBar.setElement(EDITOR_STATUS_EOL, {
|
||||
text: `${text}`,
|
||||
alignment: StatusBarAlignment.RIGHT,
|
||||
priority: 11,
|
||||
command: EditorCommands.CONFIG_EOL.id,
|
||||
tooltip: nls.localizeByDefault('Select End of Line Sequence')
|
||||
});
|
||||
}
|
||||
|
||||
protected removeLineEndingWidget(statusBar: StatusBar): void {
|
||||
statusBar.removeElement(EDITOR_STATUS_EOL);
|
||||
}
|
||||
|
||||
protected getModel(editor: EditorWidget | undefined): monaco.editor.ITextModel | undefined {
|
||||
const monacoEditor = MonacoEditor.get(editor);
|
||||
return monacoEditor && monacoEditor.getControl().getModel() || undefined;
|
||||
}
|
||||
}
|
||||
183
packages/monaco/src/browser/monaco-text-model-service.ts
Normal file
183
packages/monaco/src/browser/monaco-text-model-service.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { ResourceProvider, ReferenceCollection, Event, MaybePromise, Resource, ContributionProvider, OS, Emitter } from '@theia/core';
|
||||
import { EditorPreferences, EditorPreferenceChange } from '@theia/editor/lib/common/editor-preferences';
|
||||
import { MonacoEditorModel } from './monaco-editor-model';
|
||||
import { IDisposable, IReference } from '@theia/monaco-editor-core/esm/vs/base/common/lifecycle';
|
||||
import { MonacoToProtocolConverter } from './monaco-to-protocol-converter';
|
||||
import { ProtocolToMonacoConverter } from './protocol-to-monaco-converter';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { ITextModelService, ITextModelContentProvider } from '@theia/monaco-editor-core/esm/vs/editor/common/services/resolverService';
|
||||
import { ITextModelUpdateOptions } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { ITextResourcePropertiesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/textResourceConfiguration';
|
||||
|
||||
export const MonacoEditorModelFactory = Symbol('MonacoEditorModelFactory');
|
||||
export interface MonacoEditorModelFactory {
|
||||
|
||||
readonly scheme: string;
|
||||
|
||||
createModel(
|
||||
resource: Resource
|
||||
): MaybePromise<MonacoEditorModel>;
|
||||
|
||||
}
|
||||
|
||||
export const MonacoEditorModelFilter = Symbol('MonacoEditorModelFilter');
|
||||
/**
|
||||
* A filter that prevents firing the `onDidCreate` event for certain models.
|
||||
* Preventing this event from firing will also prevent the propagation of the model to the plugin host.
|
||||
*
|
||||
* This is useful for models that are not supposed to be opened in a dedicated monaco editor widgets.
|
||||
* This includes models for notebook cells.
|
||||
*/
|
||||
export interface MonacoEditorModelFilter {
|
||||
/**
|
||||
* Return `true` on models that should be filtered.
|
||||
*/
|
||||
filter(model: MonacoEditorModel): boolean;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MonacoTextModelService implements ITextModelService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
protected readonly _models = new ReferenceCollection<string, MonacoEditorModel>(
|
||||
uri => this.loadModel(new URI(uri))
|
||||
);
|
||||
|
||||
protected readonly _visibleModels = new Set<MonacoEditorModel>();
|
||||
|
||||
protected readonly onDidCreateEmitter = new Emitter<MonacoEditorModel>();
|
||||
|
||||
@inject(ResourceProvider)
|
||||
protected readonly resourceProvider: ResourceProvider;
|
||||
|
||||
@inject(EditorPreferences)
|
||||
protected readonly editorPreferences: EditorPreferences;
|
||||
|
||||
@inject(MonacoToProtocolConverter)
|
||||
protected readonly m2p: MonacoToProtocolConverter;
|
||||
|
||||
@inject(ProtocolToMonacoConverter)
|
||||
protected readonly p2m: ProtocolToMonacoConverter;
|
||||
|
||||
@inject(ContributionProvider)
|
||||
@named(MonacoEditorModelFactory)
|
||||
protected readonly factories: ContributionProvider<MonacoEditorModelFactory>;
|
||||
|
||||
@inject(ContributionProvider)
|
||||
@named(MonacoEditorModelFilter)
|
||||
protected readonly filters: ContributionProvider<MonacoEditorModelFilter>;
|
||||
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
const resourcePropertiesService = StandaloneServices.get(ITextResourcePropertiesService);
|
||||
|
||||
if (resourcePropertiesService) {
|
||||
resourcePropertiesService.getEOL = () => {
|
||||
const eol = this.editorPreferences['files.eol'];
|
||||
if (eol && eol !== 'auto') {
|
||||
return eol;
|
||||
}
|
||||
return OS.backend.EOL;
|
||||
};
|
||||
}
|
||||
this._models.onDidCreate(model => {
|
||||
const filters = this.filters.getContributions();
|
||||
if (filters.some(filter => filter.filter(model))) {
|
||||
return;
|
||||
}
|
||||
this._visibleModels.add(model);
|
||||
const dispose = model.onWillDispose(() => {
|
||||
this._visibleModels.delete(model);
|
||||
dispose.dispose();
|
||||
});
|
||||
this.onDidCreateEmitter.fire(model);
|
||||
});
|
||||
}
|
||||
|
||||
get models(): MonacoEditorModel[] {
|
||||
return Array.from(this._visibleModels);
|
||||
}
|
||||
|
||||
get(uri: string): MonacoEditorModel | undefined {
|
||||
return this._models.get(uri);
|
||||
}
|
||||
|
||||
get onDidCreate(): Event<MonacoEditorModel> {
|
||||
return this.onDidCreateEmitter.event;
|
||||
}
|
||||
|
||||
createModelReference(raw: monaco.Uri | URI): Promise<IReference<MonacoEditorModel>> {
|
||||
return this._models.acquire(raw.toString());
|
||||
}
|
||||
|
||||
async loadModel(uri: URI): Promise<MonacoEditorModel> {
|
||||
await this.editorPreferences.ready;
|
||||
const resource = await this.resourceProvider(uri);
|
||||
const model = await (await this.createModel(resource)).load();
|
||||
return model;
|
||||
}
|
||||
|
||||
protected createModel(resource: Resource): MaybePromise<MonacoEditorModel> {
|
||||
const factory = this.factories.getContributions().find(({ scheme }) => resource.uri.scheme === scheme);
|
||||
return factory ? factory.createModel(resource) : new MonacoEditorModel(resource, this.m2p, this.p2m, this.logger, this.editorPreferences);
|
||||
}
|
||||
|
||||
protected readonly modelOptions: { [name: string]: (keyof ITextModelUpdateOptions | undefined) } = {
|
||||
'editor.tabSize': 'tabSize',
|
||||
'editor.insertSpaces': 'insertSpaces',
|
||||
'editor.indentSize': 'indentSize'
|
||||
};
|
||||
|
||||
protected toModelOption(editorPreference: EditorPreferenceChange['preferenceName']): keyof ITextModelUpdateOptions | undefined {
|
||||
switch (editorPreference) {
|
||||
case 'editor.tabSize': return 'tabSize';
|
||||
case 'editor.indentSize': return 'indentSize';
|
||||
case 'editor.insertSpaces': return 'insertSpaces';
|
||||
case 'editor.bracketPairColorization.enabled':
|
||||
case 'editor.bracketPairColorization.independentColorPoolPerBracketType':
|
||||
return 'bracketColorizationOptions';
|
||||
case 'editor.trimAutoWhitespace': return 'trimAutoWhitespace';
|
||||
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable {
|
||||
return {
|
||||
dispose(): void {
|
||||
// no-op
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
canHandleResource(resource: monaco.Uri): boolean {
|
||||
return this.fileService.canHandleResource(URI.fromComponents(resource));
|
||||
}
|
||||
}
|
||||
204
packages/monaco/src/browser/monaco-theming-service.ts
Normal file
204
packages/monaco/src/browser/monaco-theming-service.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import * as jsoncparser from 'jsonc-parser';
|
||||
import * as plistparser from 'fast-plist';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { MonacoThemeRegistry } from './textmate/monaco-theme-registry';
|
||||
import { getThemes, putTheme, MonacoThemeState, stateToTheme, ThemeServiceWithDB } from './monaco-indexed-db';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
|
||||
export interface MonacoTheme {
|
||||
id?: string;
|
||||
label?: string;
|
||||
uiTheme?: monaco.editor.BuiltinTheme;
|
||||
description?: string;
|
||||
uri: string;
|
||||
}
|
||||
|
||||
export interface MonacoThemeJson {
|
||||
/**
|
||||
* theme id (optional), label is used if not provided
|
||||
*/
|
||||
id?: string;
|
||||
label: string;
|
||||
/**
|
||||
* theme type, `vs-dark` if not provided
|
||||
*/
|
||||
uiTheme?: monaco.editor.BuiltinTheme;
|
||||
description?: string;
|
||||
/**
|
||||
* Follow https://code.visualstudio.com/api/extension-guides/color-theme#create-a-new-color-theme to create a custom theme.
|
||||
*/
|
||||
json: any
|
||||
/**
|
||||
* Themes can include each other. It specifies how inclusions should be resolved.
|
||||
*/
|
||||
includes?: { [includePath: string]: any }
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MonacoThemingService {
|
||||
|
||||
@inject(FileService) protected readonly fileService: FileService;
|
||||
@inject(MonacoThemeRegistry) protected readonly monacoThemeRegistry: MonacoThemeRegistry;
|
||||
@inject(ThemeServiceWithDB) protected readonly themeService: ThemeServiceWithDB;
|
||||
|
||||
/** Register themes whose configuration needs to be loaded */
|
||||
register(theme: MonacoTheme, pending: { [uri: string]: Promise<any> } = {}): Disposable {
|
||||
const toDispose = new DisposableCollection(Disposable.create(() => { /* mark as not disposed */ }));
|
||||
this.doRegister(theme, pending, toDispose);
|
||||
return toDispose;
|
||||
}
|
||||
|
||||
protected async doRegister(theme: MonacoTheme,
|
||||
pending: { [uri: string]: Promise<any> },
|
||||
toDispose: DisposableCollection
|
||||
): Promise<void> {
|
||||
try {
|
||||
const includes = {};
|
||||
const json = await this.loadTheme(theme.uri, includes, pending, toDispose);
|
||||
if (toDispose.disposed) {
|
||||
return;
|
||||
}
|
||||
const label = theme.label || new URI(theme.uri).path.base;
|
||||
const { id, description, uiTheme } = theme;
|
||||
toDispose.push(this.registerParsedTheme({ id, label, description, uiTheme: uiTheme, json, includes }));
|
||||
} catch (e) {
|
||||
console.error('Failed to load theme from ' + theme.uri, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected async loadTheme(
|
||||
uri: string,
|
||||
includes: { [include: string]: any },
|
||||
pending: { [uri: string]: Promise<any> },
|
||||
toDispose: DisposableCollection
|
||||
): Promise<any> {
|
||||
const result = await this.fileService.read(new URI(uri));
|
||||
const content = result.value;
|
||||
if (toDispose.disposed) {
|
||||
return;
|
||||
}
|
||||
const themeUri = new URI(uri);
|
||||
if (themeUri.path.ext !== '.json') {
|
||||
const value = plistparser.parse(content);
|
||||
if (value && 'settings' in value && Array.isArray(value.settings)) {
|
||||
return { tokenColors: value.settings };
|
||||
}
|
||||
throw new Error(`Problem parsing tmTheme file: ${uri}. 'settings' is not array.`);
|
||||
}
|
||||
const json = jsoncparser.parse(content, undefined, { disallowComments: false });
|
||||
if ('tokenColors' in json && typeof json.tokenColors === 'string') {
|
||||
const value = await this.doLoadTheme(themeUri, json.tokenColors, includes, pending, toDispose);
|
||||
if (toDispose.disposed) {
|
||||
return;
|
||||
}
|
||||
json.tokenColors = value.tokenColors;
|
||||
}
|
||||
if (json.include) {
|
||||
includes[json.include] = await this.doLoadTheme(themeUri, json.include, includes, pending, toDispose);
|
||||
if (toDispose.disposed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.clean(json.colors);
|
||||
return json;
|
||||
}
|
||||
|
||||
protected doLoadTheme(
|
||||
themeUri: URI,
|
||||
referencedPath: string,
|
||||
includes: { [include: string]: any },
|
||||
pending: { [uri: string]: Promise<any> },
|
||||
toDispose: DisposableCollection
|
||||
): Promise<any> {
|
||||
const referencedUri = themeUri.parent.resolve(referencedPath).toString();
|
||||
if (!pending[referencedUri]) {
|
||||
pending[referencedUri] = this.loadTheme(referencedUri, includes, pending, toDispose);
|
||||
}
|
||||
return pending[referencedUri];
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
this.monacoThemeRegistry.initializeDefaultThemes();
|
||||
this.updateBodyUiTheme();
|
||||
this.themeService.onDidColorThemeChange(() => this.updateBodyUiTheme());
|
||||
this.themeService.onDidRetrieveTheme(theme => this.monacoThemeRegistry.setTheme(MonacoThemingService.toCssSelector(theme.id), theme.data));
|
||||
this.restore();
|
||||
}
|
||||
|
||||
/** register a theme whose configuration has already been loaded */
|
||||
registerParsedTheme(theme: MonacoThemeJson): Disposable {
|
||||
const uiTheme = theme.uiTheme || 'vs-dark';
|
||||
const { label, description, json, includes } = theme;
|
||||
const id = theme.id || label;
|
||||
const cssSelector = MonacoThemingService.toCssSelector(id);
|
||||
const data = this.monacoThemeRegistry.register(json, includes, cssSelector, uiTheme);
|
||||
return this.doRegisterParsedTheme({ id, label, description, uiTheme, data });
|
||||
}
|
||||
|
||||
protected toUpdateUiTheme = new DisposableCollection();
|
||||
protected updateBodyUiTheme(): void {
|
||||
this.toUpdateUiTheme.dispose();
|
||||
const type = this.themeService.getCurrentTheme().type;
|
||||
const uiTheme: monaco.editor.BuiltinTheme = type === 'hc' ? 'hc-black' : type === 'light' ? 'vs' : 'vs-dark';
|
||||
document.body.classList.add(uiTheme);
|
||||
this.toUpdateUiTheme.push(Disposable.create(() => document.body.classList.remove(uiTheme)));
|
||||
}
|
||||
|
||||
protected doRegisterParsedTheme(state: MonacoThemeState): Disposable {
|
||||
return new DisposableCollection(
|
||||
this.themeService.register(stateToTheme(state)),
|
||||
putTheme(state)
|
||||
);
|
||||
}
|
||||
|
||||
protected async restore(): Promise<void> {
|
||||
try {
|
||||
const themes = await getThemes();
|
||||
for (const state of themes) {
|
||||
this.monacoThemeRegistry.setTheme(state.data.name!, state.data);
|
||||
this.doRegisterParsedTheme(state);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to restore monaco themes', e);
|
||||
}
|
||||
}
|
||||
|
||||
/* remove all characters that are not allowed in css */
|
||||
protected static toCssSelector(str: string): string {
|
||||
str = str.replace(/[^\-a-zA-Z0-9]/g, '-');
|
||||
if (str.charAt(0).match(/[0-9\-]/)) {
|
||||
str = '-' + str;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/** removes all invalid theming values */
|
||||
private clean(obj: any): void {
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] !== 'string') {
|
||||
delete obj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
packages/monaco/src/browser/monaco-to-protocol-converter.ts
Normal file
89
packages/monaco/src/browser/monaco-to-protocol-converter.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { Position, Range } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import { RecursivePartial } from '@theia/core/lib/common/types';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { Selection } from '@theia/editor/lib/browser';
|
||||
|
||||
export interface MonacoRangeReplace {
|
||||
insert: monaco.IRange;
|
||||
replace: monaco.IRange
|
||||
};
|
||||
export namespace MonacoRangeReplace {
|
||||
export function is(v: Partial<monaco.IRange> | MonacoRangeReplace): v is MonacoRangeReplace {
|
||||
return (v as MonacoRangeReplace).insert !== undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MonacoToProtocolConverter {
|
||||
|
||||
asPosition(lineNumber: undefined | null, column: undefined | null): {};
|
||||
asPosition(lineNumber: number, column: undefined | null): Pick<Position, 'line'>;
|
||||
asPosition(lineNumber: undefined | null, column: number): Pick<Position, 'character'>;
|
||||
asPosition(lineNumber: number, column: number): Position;
|
||||
asPosition(lineNumber: number | undefined | null, column: number | undefined | null): Partial<Position>;
|
||||
asPosition(lineNumber: number | undefined | null, column: number | undefined | null): Partial<Position> {
|
||||
const line = typeof lineNumber !== 'number' ? undefined : lineNumber - 1;
|
||||
const character = typeof column !== 'number' ? undefined : column - 1;
|
||||
return {
|
||||
line, character
|
||||
};
|
||||
}
|
||||
|
||||
asRange(range: undefined): undefined;
|
||||
asRange(range: monaco.IRange): Range;
|
||||
asRange(range: monaco.IRange | undefined): Range | undefined;
|
||||
asRange(range: monaco.IRange | { insert: monaco.IRange; replace: monaco.IRange }): Range;
|
||||
asRange(range: Partial<monaco.IRange>): RecursivePartial<Range>;
|
||||
asRange(range: Partial<monaco.IRange> | undefined): RecursivePartial<Range> | undefined;
|
||||
asRange(range: Partial<monaco.IRange> | undefined | MonacoRangeReplace): RecursivePartial<Range> | undefined {
|
||||
if (range === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (MonacoRangeReplace.is(range)) {
|
||||
return this.asRange(range.insert);
|
||||
|
||||
} else {
|
||||
const start = this.asPosition(range.startLineNumber, range.startColumn);
|
||||
const end = this.asPosition(range.endLineNumber, range.endColumn);
|
||||
return {
|
||||
start, end
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
asSelection(selection: monaco.Selection | null): Selection {
|
||||
if (!selection) {
|
||||
return {
|
||||
start: { line: 0, character: 0 },
|
||||
end: { line: 0, character: 0 },
|
||||
direction: 'ltr'
|
||||
};
|
||||
}
|
||||
const start = this.asPosition(selection.selectionStartLineNumber, selection.selectionStartColumn);
|
||||
const end = this.asPosition(selection.positionLineNumber, selection.positionColumn);
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
direction: selection.getDirection() === monaco.SelectionDirection.LTR ? 'ltr' : 'rtl'
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
64
packages/monaco/src/browser/monaco-undo-redo-handler.ts
Normal file
64
packages/monaco/src/browser/monaco-undo-redo-handler.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { UndoRedoHandler } from '@theia/core/lib/browser';
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { ICodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditorService } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/codeEditorService';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
|
||||
@injectable()
|
||||
export abstract class AbstractMonacoUndoRedoHandler implements UndoRedoHandler<ICodeEditor> {
|
||||
priority: number;
|
||||
abstract select(): ICodeEditor | undefined;
|
||||
undo(item: ICodeEditor): void {
|
||||
item.trigger('MonacoUndoRedoHandler', 'undo', undefined);
|
||||
}
|
||||
redo(item: ICodeEditor): void {
|
||||
item.trigger('MonacoUndoRedoHandler', 'redo', undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class FocusedMonacoUndoRedoHandler extends AbstractMonacoUndoRedoHandler {
|
||||
override priority = 10000;
|
||||
|
||||
protected codeEditorService = StandaloneServices.get(ICodeEditorService);
|
||||
|
||||
override select(): ICodeEditor | undefined {
|
||||
const focusedEditor = this.codeEditorService.getFocusedCodeEditor();
|
||||
if (focusedEditor && focusedEditor.hasTextFocus()) {
|
||||
return focusedEditor;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ActiveMonacoUndoRedoHandler extends AbstractMonacoUndoRedoHandler {
|
||||
override priority = 0;
|
||||
|
||||
protected codeEditorService = StandaloneServices.get(ICodeEditorService);
|
||||
|
||||
override select(): ICodeEditor | undefined {
|
||||
const focusedEditor = this.codeEditorService.getActiveCodeEditor();
|
||||
if (focusedEditor) {
|
||||
focusedEditor.focus();
|
||||
return focusedEditor;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
46
packages/monaco/src/browser/monaco-utilities.ts
Normal file
46
packages/monaco/src/browser/monaco-utilities.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 EclipseSource GmbH.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { MonacoEditorModel } from './monaco-editor-model';
|
||||
|
||||
export function insertFinalNewline(editorModel: MonacoEditorModel): void {
|
||||
const model = editorModel.textEditorModel;
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = model?.getLineCount();
|
||||
if (lines === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastLine = model?.getLineContent(lines);
|
||||
if (lastLine.trim() === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastLineMaxColumn = model?.getLineMaxColumn(lines);
|
||||
const range = {
|
||||
startLineNumber: lines,
|
||||
startColumn: lastLineMaxColumn,
|
||||
endLineNumber: lines,
|
||||
endColumn: lastLineMaxColumn
|
||||
};
|
||||
model.applyEdits([{
|
||||
range,
|
||||
text: model?.getEOL()
|
||||
}]);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 1C-Soft LLC and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { Emitter } from '@theia/monaco-editor-core/esm/vs/base/common/event';
|
||||
import { URI } from '@theia/monaco-editor-core/esm/vs/base/common/uri';
|
||||
import {
|
||||
ISingleFolderWorkspaceIdentifier,
|
||||
IWorkspace,
|
||||
IWorkspaceContextService,
|
||||
IWorkspaceFolder,
|
||||
IWorkspaceFoldersChangeEvent,
|
||||
IWorkspaceFoldersWillChangeEvent,
|
||||
IWorkspaceIdentifier,
|
||||
UNKNOWN_EMPTY_WINDOW_WORKSPACE,
|
||||
WorkbenchState
|
||||
} from '@theia/monaco-editor-core/esm/vs/platform/workspace/common/workspace';
|
||||
|
||||
/**
|
||||
* A minimal implementation of {@link IWorkspaceContextService} to replace the `StandaloneWorkspaceContextService` in Monaco
|
||||
* as a workaround for the issue of showing no context menu for editor minimap (#15217).
|
||||
*/
|
||||
@injectable()
|
||||
export class MonacoWorkspaceContextService implements IWorkspaceContextService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
protected readonly onDidChangeWorkbenchStateEmitter = new Emitter<WorkbenchState>();
|
||||
readonly onDidChangeWorkbenchState = this.onDidChangeWorkbenchStateEmitter.event;
|
||||
|
||||
protected readonly onDidChangeWorkspaceNameEmitter = new Emitter<void>();
|
||||
readonly onDidChangeWorkspaceName = this.onDidChangeWorkspaceNameEmitter.event;
|
||||
|
||||
protected readonly onWillChangeWorkspaceFoldersEmitter = new Emitter<IWorkspaceFoldersWillChangeEvent>();
|
||||
readonly onWillChangeWorkspaceFolders = this.onWillChangeWorkspaceFoldersEmitter.event;
|
||||
|
||||
protected readonly onDidChangeWorkspaceFoldersEmitter = new Emitter<IWorkspaceFoldersChangeEvent>();
|
||||
readonly onDidChangeWorkspaceFolders = this.onDidChangeWorkspaceFoldersEmitter.event;
|
||||
|
||||
protected workspace: IWorkspace = { id: UNKNOWN_EMPTY_WINDOW_WORKSPACE.id, folders: [] };
|
||||
|
||||
getCompleteWorkspace(): Promise<IWorkspace> {
|
||||
return Promise.resolve(this.getWorkspace());
|
||||
}
|
||||
|
||||
getWorkspace(): IWorkspace {
|
||||
return this.workspace;
|
||||
}
|
||||
|
||||
getWorkbenchState(): WorkbenchState {
|
||||
return WorkbenchState.EMPTY;
|
||||
}
|
||||
|
||||
getWorkspaceFolder(resource: URI): IWorkspaceFolder | null {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
return null;
|
||||
}
|
||||
|
||||
isCurrentWorkspace(workspaceIdOrFolder: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
isInsideWorkspace(resource: URI): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
411
packages/monaco/src/browser/monaco-workspace.ts
Normal file
411
packages/monaco/src/browser/monaco-workspace.ts
Normal file
@@ -0,0 +1,411 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
/* eslint-disable no-null/no-null */
|
||||
|
||||
import { URI as Uri } from '@theia/core/shared/vscode-uri';
|
||||
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { FileSystemPreferences } from '@theia/filesystem/lib/common';
|
||||
import { EditorManager } from '@theia/editor/lib/browser';
|
||||
import { MonacoTextModelService } from './monaco-text-model-service';
|
||||
import { MonacoEditorModel, MonacoModelContentChangedEvent } from './monaco-editor-model';
|
||||
import { MonacoEditor } from './monaco-editor';
|
||||
import { ProblemManager } from '@theia/markers/lib/browser';
|
||||
import { ArrayUtils } from '@theia/core/lib/common/types';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
import { FileSystemProviderCapabilities } from '@theia/filesystem/lib/common/files';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import {
|
||||
IBulkEditOptions,
|
||||
IBulkEditResult, ResourceEdit, ResourceFileEdit as MonacoResourceFileEdit,
|
||||
ResourceTextEdit as MonacoResourceTextEdit
|
||||
} from '@theia/monaco-editor-core/esm/vs/editor/browser/services/bulkEditService';
|
||||
import { IEditorWorkerService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/editorWorker';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { EndOfLineSequence } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
|
||||
import { SnippetParser } from '@theia/monaco-editor-core/esm/vs/editor/contrib/snippet/browser/snippetParser';
|
||||
import { TextEdit } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
|
||||
import { SnippetController2 } from '@theia/monaco-editor-core/esm/vs/editor/contrib/snippet/browser/snippetController2';
|
||||
import { isObject, MaybePromise, nls } from '@theia/core/lib/common';
|
||||
import { SaveableService } from '@theia/core/lib/browser';
|
||||
import { EditorPreferences } from '@theia/editor/lib/common/editor-preferences';
|
||||
|
||||
export namespace WorkspaceFileEdit {
|
||||
export function is(arg: Edit): arg is monaco.languages.IWorkspaceFileEdit {
|
||||
return ('oldResource' in arg && monaco.Uri.isUri(arg.oldResource)) ||
|
||||
('newResource' in arg && monaco.Uri.isUri(arg.newResource));
|
||||
}
|
||||
}
|
||||
|
||||
export namespace WorkspaceTextEdit {
|
||||
export function is(arg: Edit): arg is monaco.languages.IWorkspaceTextEdit {
|
||||
return isObject<monaco.languages.IWorkspaceTextEdit>(arg)
|
||||
&& monaco.Uri.isUri(arg.resource)
|
||||
&& isObject(arg.textEdit);
|
||||
}
|
||||
}
|
||||
|
||||
export type Edit = monaco.languages.IWorkspaceFileEdit | monaco.languages.IWorkspaceTextEdit;
|
||||
|
||||
export namespace ResourceFileEdit {
|
||||
export function is(arg: ResourceEdit): arg is MonacoResourceFileEdit {
|
||||
return isObject<MonacoResourceFileEdit>(arg) && (monaco.Uri.isUri(arg.oldResource) || monaco.Uri.isUri(arg.newResource));
|
||||
}
|
||||
}
|
||||
|
||||
export namespace ResourceTextEdit {
|
||||
export function is(arg: ResourceEdit): arg is MonacoResourceTextEdit {
|
||||
return ('resource' in arg && monaco.Uri.isUri((arg as MonacoResourceTextEdit).resource));
|
||||
}
|
||||
}
|
||||
|
||||
export interface WorkspaceFoldersChangeEvent {
|
||||
readonly added: WorkspaceFolder[];
|
||||
readonly removed: WorkspaceFolder[];
|
||||
}
|
||||
|
||||
export interface WorkspaceFolder {
|
||||
readonly uri: Uri;
|
||||
readonly name: string;
|
||||
readonly index: number;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MonacoWorkspace {
|
||||
|
||||
protected resolveReady: () => void;
|
||||
readonly ready = new Promise<void>(resolve => {
|
||||
this.resolveReady = resolve;
|
||||
});
|
||||
|
||||
protected readonly onDidOpenTextDocumentEmitter = new Emitter<MonacoEditorModel>();
|
||||
readonly onDidOpenTextDocument = this.onDidOpenTextDocumentEmitter.event;
|
||||
|
||||
protected readonly onDidCloseTextDocumentEmitter = new Emitter<MonacoEditorModel>();
|
||||
readonly onDidCloseTextDocument = this.onDidCloseTextDocumentEmitter.event;
|
||||
|
||||
protected readonly onDidChangeTextDocumentEmitter = new Emitter<MonacoModelContentChangedEvent>();
|
||||
readonly onDidChangeTextDocument = this.onDidChangeTextDocumentEmitter.event;
|
||||
|
||||
protected readonly onDidSaveTextDocumentEmitter = new Emitter<MonacoEditorModel>();
|
||||
readonly onDidSaveTextDocument = this.onDidSaveTextDocumentEmitter.event;
|
||||
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
|
||||
@inject(FileSystemPreferences)
|
||||
protected readonly filePreferences: FileSystemPreferences;
|
||||
|
||||
@inject(EditorPreferences)
|
||||
protected readonly editorPreferences: EditorPreferences;
|
||||
|
||||
@inject(MonacoTextModelService)
|
||||
protected readonly textModelService: MonacoTextModelService;
|
||||
|
||||
@inject(EditorManager)
|
||||
protected readonly editorManager: EditorManager;
|
||||
|
||||
@inject(ProblemManager)
|
||||
protected readonly problems: ProblemManager;
|
||||
|
||||
@inject(SaveableService)
|
||||
protected readonly saveService: SaveableService;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.resolveReady();
|
||||
|
||||
for (const model of this.textModelService.models) {
|
||||
this.fireDidOpen(model);
|
||||
}
|
||||
this.textModelService.onDidCreate(model => this.fireDidOpen(model));
|
||||
}
|
||||
|
||||
get textDocuments(): MonacoEditorModel[] {
|
||||
return this.textModelService.models;
|
||||
}
|
||||
|
||||
getTextDocument(uri: string): MonacoEditorModel | undefined {
|
||||
return this.textModelService.get(uri);
|
||||
}
|
||||
|
||||
protected fireDidOpen(model: MonacoEditorModel): void {
|
||||
this.doFireDidOpen(model);
|
||||
model.textEditorModel.onDidChangeLanguage(e => {
|
||||
this.problems.cleanAllMarkers(new URI(model.uri));
|
||||
model.setLanguageId(e.oldLanguage);
|
||||
try {
|
||||
this.fireDidClose(model);
|
||||
} finally {
|
||||
model.setLanguageId(undefined);
|
||||
}
|
||||
this.doFireDidOpen(model);
|
||||
});
|
||||
model.onDidChangeContent(event => this.fireDidChangeContent(event));
|
||||
model.onDidSaveModel(() => this.fireDidSave(model));
|
||||
model.onDirtyChanged(() => this.openEditorIfDirty(model));
|
||||
model.onDispose(() => this.fireDidClose(model));
|
||||
}
|
||||
|
||||
protected doFireDidOpen(model: MonacoEditorModel): void {
|
||||
this.onDidOpenTextDocumentEmitter.fire(model);
|
||||
}
|
||||
|
||||
protected fireDidClose(model: MonacoEditorModel): void {
|
||||
this.onDidCloseTextDocumentEmitter.fire(model);
|
||||
}
|
||||
|
||||
protected fireDidChangeContent(event: MonacoModelContentChangedEvent): void {
|
||||
this.onDidChangeTextDocumentEmitter.fire(event);
|
||||
}
|
||||
|
||||
protected fireDidSave(model: MonacoEditorModel): void {
|
||||
this.onDidSaveTextDocumentEmitter.fire(model);
|
||||
}
|
||||
|
||||
protected readonly suppressedOpenIfDirty: MonacoEditorModel[] = [];
|
||||
|
||||
protected openEditorIfDirty(model: MonacoEditorModel): void {
|
||||
if (model.suppressOpenEditorWhenDirty || this.suppressedOpenIfDirty.indexOf(model) !== -1) {
|
||||
return;
|
||||
}
|
||||
if (model.dirty && MonacoEditor.findByDocument(this.editorManager, model).length === 0) {
|
||||
// create a new reference to make sure the model is not disposed before it is
|
||||
// acquired by the editor, thus losing the changes that made it dirty.
|
||||
this.textModelService.createModelReference(model.textEditorModel.uri).then(ref => {
|
||||
(
|
||||
this.saveService.autoSave !== 'off' ? new Promise(resolve => model.onDidSaveModel(resolve)) :
|
||||
this.editorManager.open(new URI(model.uri), { mode: 'open' })
|
||||
).then(
|
||||
() => ref.dispose()
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected async suppressOpenIfDirty(model: MonacoEditorModel, cb: () => MaybePromise<void>): Promise<void> {
|
||||
this.suppressedOpenIfDirty.push(model);
|
||||
try {
|
||||
await cb();
|
||||
} finally {
|
||||
const i = this.suppressedOpenIfDirty.indexOf(model);
|
||||
if (i !== -1) {
|
||||
this.suppressedOpenIfDirty.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies given edits to the given model.
|
||||
* The model is saved if no editors is opened for it.
|
||||
*/
|
||||
applyBackgroundEdit(model: MonacoEditorModel, editOperations: monaco.editor.IIdentifiedSingleEditOperation[],
|
||||
shouldSave?: boolean | ((openEditor: MonacoEditor | undefined, wasDirty: boolean) => boolean)): Promise<void> {
|
||||
return this.suppressOpenIfDirty(model, async () => {
|
||||
const editor = MonacoEditor.findByDocument(this.editorManager, model)[0];
|
||||
const wasDirty = !!editor?.document.dirty;
|
||||
const cursorState = editor && editor.getControl().getSelections() || [];
|
||||
model.textEditorModel.pushStackElement();
|
||||
model.textEditorModel.pushEditOperations(cursorState, editOperations, () => cursorState);
|
||||
model.textEditorModel.pushStackElement();
|
||||
if ((typeof shouldSave === 'function' && shouldSave(editor, wasDirty)) || (!editor && shouldSave)) {
|
||||
await model.save();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async applyBulkEdit(edits: ResourceEdit[], options?: IBulkEditOptions): Promise<IBulkEditResult> {
|
||||
try {
|
||||
let totalEdits = 0;
|
||||
let totalFiles = 0;
|
||||
const fileEdits = edits.filter(edit => edit instanceof MonacoResourceFileEdit);
|
||||
const [snippetEdits, textEdits] = ArrayUtils.partition(edits.filter(edit => edit instanceof MonacoResourceTextEdit) as MonacoResourceTextEdit[],
|
||||
edit => edit.textEdit.insertAsSnippet && (edit.resource.toString() === this.editorManager.activeEditor?.getResourceUri()?.toString()));
|
||||
|
||||
if (fileEdits.length > 0) {
|
||||
await this.performFileEdits(<MonacoResourceFileEdit[]>fileEdits);
|
||||
}
|
||||
|
||||
if (textEdits.length > 0) {
|
||||
const result = await this.performTextEdits(<MonacoResourceTextEdit[]>textEdits);
|
||||
totalEdits += result.totalEdits;
|
||||
totalFiles += result.totalFiles;
|
||||
}
|
||||
|
||||
if (snippetEdits.length > 0) {
|
||||
await this.performSnippetEdits(<MonacoResourceTextEdit[]>snippetEdits);
|
||||
}
|
||||
|
||||
// when enabled (option AND setting) loop over all dirty working copies and trigger save
|
||||
// for those that were involved in this bulk edit operation.
|
||||
const resources = new Set<string>(
|
||||
edits
|
||||
.filter((edit): edit is MonacoResourceTextEdit => edit instanceof MonacoResourceTextEdit)
|
||||
.map(edit => edit.resource.toString())
|
||||
);
|
||||
if (resources.size > 0 && options?.respectAutoSaveConfig && this.editorPreferences.get('files.refactoring.autoSave') === true) {
|
||||
await this.saveAll(resources);
|
||||
}
|
||||
|
||||
const ariaSummary = this.getAriaSummary(totalEdits, totalFiles);
|
||||
return { ariaSummary, isApplied: true };
|
||||
} catch (e) {
|
||||
console.error('Failed to apply Resource edits:', e);
|
||||
return {
|
||||
ariaSummary: `Error applying Resource edits: ${e.toString()}`,
|
||||
isApplied: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected async saveAll(resources: Set<string>): Promise<void> {
|
||||
await Promise.all(Array.from(resources.values()).map(uri => this.textModelService.get(uri)?.save()));
|
||||
}
|
||||
|
||||
protected getAriaSummary(totalEdits: number, totalFiles: number): string {
|
||||
if (totalEdits === 0) {
|
||||
return nls.localizeByDefault('Made no edits');
|
||||
}
|
||||
if (totalEdits > 1 && totalFiles > 1) {
|
||||
return nls.localizeByDefault('Made {0} text edits in {1} files', totalEdits, totalFiles);
|
||||
}
|
||||
return nls.localizeByDefault('Made {0} text edits in one file', totalEdits);
|
||||
}
|
||||
|
||||
protected async performTextEdits(edits: MonacoResourceTextEdit[]): Promise<{
|
||||
totalEdits: number,
|
||||
totalFiles: number
|
||||
}> {
|
||||
let totalEdits = 0;
|
||||
let totalFiles = 0;
|
||||
const resourceEdits = new Map<string, MonacoResourceTextEdit[]>();
|
||||
for (const edit of edits) {
|
||||
if (typeof edit.versionId === 'number') {
|
||||
const model = this.textModelService.get(edit.resource.toString());
|
||||
if (model && model.textEditorModel.getVersionId() !== edit.versionId) {
|
||||
throw new Error(`${model.uri} has changed in the meantime`);
|
||||
}
|
||||
}
|
||||
const key = edit.resource.toString();
|
||||
let array = resourceEdits.get(key);
|
||||
if (!array) {
|
||||
array = [];
|
||||
resourceEdits.set(key, array);
|
||||
}
|
||||
array.push(edit);
|
||||
}
|
||||
const pending: Promise<void>[] = [];
|
||||
for (const [key, value] of resourceEdits) {
|
||||
pending.push((async () => {
|
||||
const uri = monaco.Uri.parse(key);
|
||||
let eol: EndOfLineSequence | undefined;
|
||||
const editOperations: monaco.editor.IIdentifiedSingleEditOperation[] = [];
|
||||
const minimalEdits = await StandaloneServices.get(IEditorWorkerService)
|
||||
.computeMoreMinimalEdits(uri, value.map(edit => this.transformSnippetStringToInsertText(edit)));
|
||||
if (minimalEdits) {
|
||||
for (const textEdit of minimalEdits) {
|
||||
if (typeof textEdit.eol === 'number') {
|
||||
eol = textEdit.eol;
|
||||
}
|
||||
if (monaco.Range.isEmpty(textEdit.range) && !textEdit.text) {
|
||||
// skip no-op
|
||||
continue;
|
||||
}
|
||||
editOperations.push({
|
||||
forceMoveMarkers: false,
|
||||
range: monaco.Range.lift(textEdit.range),
|
||||
text: textEdit.text
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!editOperations.length && eol === undefined) {
|
||||
return;
|
||||
}
|
||||
const reference = await this.textModelService.createModelReference(uri);
|
||||
try {
|
||||
const document = reference.object as MonacoEditorModel;
|
||||
const model = document.textEditorModel;
|
||||
const editor = MonacoEditor.findByDocument(this.editorManager, document)[0];
|
||||
const cursorState = editor?.getControl().getSelections() ?? [];
|
||||
// start a fresh operation
|
||||
model.pushStackElement();
|
||||
if (editOperations.length) {
|
||||
model.pushEditOperations(cursorState, editOperations, () => cursorState);
|
||||
}
|
||||
if (eol !== undefined) {
|
||||
model.pushEOL(eol);
|
||||
}
|
||||
// push again to make this change an undoable operation
|
||||
model.pushStackElement();
|
||||
totalFiles += 1;
|
||||
totalEdits += editOperations.length;
|
||||
} finally {
|
||||
reference.dispose();
|
||||
}
|
||||
})());
|
||||
}
|
||||
await Promise.all(pending);
|
||||
return { totalEdits, totalFiles };
|
||||
}
|
||||
|
||||
protected async performFileEdits(edits: MonacoResourceFileEdit[]): Promise<void> {
|
||||
for (const edit of edits) {
|
||||
const options = edit.options || {};
|
||||
if (edit.newResource && edit.oldResource) {
|
||||
// rename
|
||||
if (options.overwrite === undefined && options.ignoreIfExists && await this.fileService.exists(URI.fromComponents(edit.newResource))) {
|
||||
return; // not overwriting, but ignoring, and the target file exists
|
||||
}
|
||||
await this.fileService.move(URI.fromComponents(edit.oldResource), URI.fromComponents(edit.newResource), { overwrite: options.overwrite });
|
||||
} else if (!edit.newResource && edit.oldResource) {
|
||||
// delete file
|
||||
if (await this.fileService.exists(URI.fromComponents(edit.oldResource))) {
|
||||
let useTrash = this.filePreferences['files.enableTrash'];
|
||||
if (useTrash && !(this.fileService.hasCapability(URI.fromComponents(edit.oldResource), FileSystemProviderCapabilities.Trash))) {
|
||||
useTrash = false; // not supported by provider
|
||||
}
|
||||
await this.fileService.delete(URI.fromComponents(edit.oldResource), { useTrash, recursive: options.recursive });
|
||||
} else if (!options.ignoreIfNotExists) {
|
||||
throw new Error(`${edit.oldResource} does not exist and can not be deleted`);
|
||||
}
|
||||
} else if (edit.newResource && !edit.oldResource) {
|
||||
// create file
|
||||
if (options.overwrite === undefined && options.ignoreIfExists && await this.fileService.exists(URI.fromComponents(edit.newResource))) {
|
||||
return; // not overwriting, but ignoring, and the target file exists
|
||||
}
|
||||
await this.fileService.create(URI.fromComponents(edit.newResource), undefined, { overwrite: options.overwrite });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async performSnippetEdits(edits: MonacoResourceTextEdit[]): Promise<void> {
|
||||
const activeEditor = MonacoEditor.getActive(this.editorManager)?.getControl();
|
||||
if (activeEditor) {
|
||||
const snippetController: SnippetController2 = activeEditor.getContribution('snippetController2')!;
|
||||
snippetController.apply(edits.map(edit => ({ range: monaco.Range.lift(edit.textEdit.range), template: edit.textEdit.text })));
|
||||
}
|
||||
}
|
||||
|
||||
protected transformSnippetStringToInsertText(resourceEdit: MonacoResourceTextEdit): TextEdit & { insertAsSnippet?: boolean } {
|
||||
if (resourceEdit.textEdit.insertAsSnippet) {
|
||||
return { ...resourceEdit.textEdit, insertAsSnippet: false, text: SnippetParser.asInsertText(resourceEdit.textEdit.text) };
|
||||
} else {
|
||||
return resourceEdit.textEdit;
|
||||
}
|
||||
}
|
||||
}
|
||||
158
packages/monaco/src/browser/protocol-to-monaco-converter.ts
Normal file
158
packages/monaco/src/browser/protocol-to-monaco-converter.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { Position, Range, Location, TextEdit, Diagnostic, DiagnosticRelatedInformation } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import { RecursivePartial } from '@theia/core/lib/common/types';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
|
||||
@injectable()
|
||||
export class ProtocolToMonacoConverter {
|
||||
|
||||
asRange(range: undefined): undefined;
|
||||
asRange(range: Range): monaco.Range;
|
||||
asRange(range: Range | undefined): monaco.Range | undefined;
|
||||
asRange(range: RecursivePartial<Range>): Partial<monaco.IRange>;
|
||||
asRange(range: RecursivePartial<Range> | undefined): monaco.Range | Partial<monaco.IRange> | undefined;
|
||||
asRange(range: RecursivePartial<Range> | undefined): monaco.Range | Partial<monaco.IRange> | undefined {
|
||||
if (range === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const start = this.asPosition(range.start);
|
||||
const end = this.asPosition(range.end);
|
||||
if (start instanceof monaco.Position && end instanceof monaco.Position) {
|
||||
return new monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column);
|
||||
}
|
||||
const startLineNumber = !start || start.lineNumber === undefined ? undefined : start.lineNumber;
|
||||
const startColumn = !start || start.column === undefined ? undefined : start.column;
|
||||
const endLineNumber = !end || end.lineNumber === undefined ? undefined : end.lineNumber;
|
||||
const endColumn = !end || end.column === undefined ? undefined : end.column;
|
||||
return { startLineNumber, startColumn, endLineNumber, endColumn };
|
||||
}
|
||||
|
||||
asPosition(position: undefined): undefined;
|
||||
asPosition(position: Position): monaco.Position;
|
||||
asPosition(position: Position | undefined): monaco.Position | undefined;
|
||||
asPosition(position: Partial<Position>): Partial<monaco.IPosition>;
|
||||
asPosition(position: Partial<Position> | undefined): monaco.Position | Partial<monaco.IPosition> | undefined;
|
||||
asPosition(position: Partial<Position> | undefined): monaco.Position | Partial<monaco.IPosition> | undefined {
|
||||
if (position === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const { line, character } = position;
|
||||
const lineNumber = line === undefined ? undefined : line + 1;
|
||||
const column = character === undefined ? undefined : character + 1;
|
||||
if (lineNumber !== undefined && column !== undefined) {
|
||||
return new monaco.Position(lineNumber, column);
|
||||
}
|
||||
return { lineNumber, column };
|
||||
}
|
||||
|
||||
asLocation(item: Location): monaco.languages.Location;
|
||||
asLocation(item: undefined): undefined;
|
||||
asLocation(item: Location | undefined): monaco.languages.Location | undefined;
|
||||
asLocation(item: Location | undefined): monaco.languages.Location | undefined {
|
||||
if (!item) {
|
||||
return undefined;
|
||||
}
|
||||
const uri = monaco.Uri.parse(item.uri);
|
||||
const range = this.asRange(item.range)!;
|
||||
return {
|
||||
uri, range
|
||||
};
|
||||
}
|
||||
|
||||
asTextEdit(edit: TextEdit): monaco.languages.TextEdit;
|
||||
asTextEdit(edit: undefined): undefined;
|
||||
asTextEdit(edit: TextEdit | undefined): undefined;
|
||||
asTextEdit(edit: TextEdit | undefined): monaco.languages.TextEdit | undefined {
|
||||
if (!edit) {
|
||||
return undefined;
|
||||
}
|
||||
const range = this.asRange(edit.range)!;
|
||||
return {
|
||||
range,
|
||||
text: edit.newText
|
||||
};
|
||||
}
|
||||
|
||||
asTextEdits(items: TextEdit[]): monaco.languages.TextEdit[];
|
||||
asTextEdits(items: undefined): undefined;
|
||||
asTextEdits(items: TextEdit[] | undefined): monaco.languages.TextEdit[] | undefined;
|
||||
asTextEdits(items: TextEdit[] | undefined): monaco.languages.TextEdit[] | undefined {
|
||||
if (!items) {
|
||||
return undefined;
|
||||
}
|
||||
return items.map(item => this.asTextEdit(item));
|
||||
}
|
||||
|
||||
asSeverity(severity?: number): monaco.MarkerSeverity {
|
||||
if (severity === 1) {
|
||||
return monaco.MarkerSeverity.Error;
|
||||
}
|
||||
if (severity === 2) {
|
||||
return monaco.MarkerSeverity.Warning;
|
||||
}
|
||||
if (severity === 3) {
|
||||
return monaco.MarkerSeverity.Info;
|
||||
}
|
||||
return monaco.MarkerSeverity.Hint;
|
||||
}
|
||||
|
||||
asDiagnostics(diagnostics: undefined): undefined;
|
||||
asDiagnostics(diagnostics: Diagnostic[]): monaco.editor.IMarkerData[];
|
||||
asDiagnostics(diagnostics: Diagnostic[] | undefined): monaco.editor.IMarkerData[] | undefined;
|
||||
asDiagnostics(diagnostics: Diagnostic[] | undefined): monaco.editor.IMarkerData[] | undefined {
|
||||
if (!diagnostics) {
|
||||
return undefined;
|
||||
}
|
||||
return diagnostics.map(diagnostic => this.asDiagnostic(diagnostic));
|
||||
}
|
||||
|
||||
asDiagnostic(diagnostic: Diagnostic): monaco.editor.IMarkerData {
|
||||
return {
|
||||
code: typeof diagnostic.code === 'number' ? diagnostic.code.toString() : diagnostic.code,
|
||||
severity: this.asSeverity(diagnostic.severity),
|
||||
message: diagnostic.message,
|
||||
source: diagnostic.source,
|
||||
startLineNumber: diagnostic.range.start.line + 1,
|
||||
startColumn: diagnostic.range.start.character + 1,
|
||||
endLineNumber: diagnostic.range.end.line + 1,
|
||||
endColumn: diagnostic.range.end.character + 1,
|
||||
relatedInformation: this.asRelatedInformations(diagnostic.relatedInformation),
|
||||
tags: diagnostic.tags
|
||||
};
|
||||
}
|
||||
|
||||
asRelatedInformations(relatedInformation?: DiagnosticRelatedInformation[]): monaco.editor.IRelatedInformation[] | undefined {
|
||||
if (!relatedInformation) {
|
||||
return undefined;
|
||||
}
|
||||
return relatedInformation.map(item => this.asRelatedInformation(item));
|
||||
}
|
||||
|
||||
asRelatedInformation(relatedInformation: DiagnosticRelatedInformation): monaco.editor.IRelatedInformation {
|
||||
return {
|
||||
resource: monaco.Uri.parse(relatedInformation.location.uri),
|
||||
startLineNumber: relatedInformation.location.range.start.line + 1,
|
||||
startColumn: relatedInformation.location.range.start.character + 1,
|
||||
endLineNumber: relatedInformation.location.range.end.line + 1,
|
||||
endColumn: relatedInformation.location.range.end.character + 1,
|
||||
message: relatedInformation.message
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
234
packages/monaco/src/browser/simple-monaco-editor.ts
Normal file
234
packages/monaco/src/browser/simple-monaco-editor.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2023 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { EditorServiceOverrides, MonacoEditor, MonacoEditorServices } from './monaco-editor';
|
||||
import { CodeEditorWidget, ICodeEditorWidgetOptions } from '@theia/monaco-editor-core/esm/vs/editor/browser/widget/codeEditor/codeEditorWidget';
|
||||
import { IInstantiationService } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/instantiation';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { ServiceCollection } from '@theia/monaco-editor-core/esm/vs/platform/instantiation/common/serviceCollection';
|
||||
import { Disposable, DisposableCollection, Emitter, Event, TextDocumentContentChangeDelta, URI } from '@theia/core';
|
||||
import { MonacoEditorModel } from './monaco-editor-model';
|
||||
import { Dimension, EditorMouseEvent, MouseTarget, Position, TextDocumentChangeEvent } from '@theia/editor/lib/browser';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { ElementExt } from '@theia/core/shared/@lumino/domutils';
|
||||
import { Selection } from '@theia/editor/lib/browser/editor';
|
||||
import { SelectionDirection } from '@theia/monaco-editor-core/esm/vs/editor/common/core/selection';
|
||||
import { ShowLightbulbIconMode } from '@theia/monaco-editor-core/esm/vs/editor/common/config/editorOptions';
|
||||
|
||||
export class SimpleMonacoEditor extends MonacoEditorServices implements Disposable {
|
||||
|
||||
protected editor: CodeEditorWidget;
|
||||
protected readonly toDispose = new DisposableCollection();
|
||||
|
||||
protected readonly onCursorPositionChangedEmitter = new Emitter<Position>();
|
||||
protected readonly onFocusChangedEmitter = new Emitter<boolean>();
|
||||
protected readonly onDocumentContentChangedEmitter = new Emitter<TextDocumentChangeEvent>();
|
||||
readonly onDocumentContentChanged = this.onDocumentContentChangedEmitter.event;
|
||||
protected readonly onMouseDownEmitter = new Emitter<EditorMouseEvent>();
|
||||
readonly onDidChangeReadOnly = this.document.onDidChangeReadOnly;
|
||||
protected readonly onLanguageChangedEmitter = new Emitter<string>();
|
||||
readonly onLanguageChanged = this.onLanguageChangedEmitter.event;
|
||||
protected readonly onScrollChangedEmitter = new Emitter<void>();
|
||||
readonly onEncodingChanged = this.document.onDidChangeEncoding;
|
||||
protected readonly onResizeEmitter = new Emitter<Dimension | null>();
|
||||
readonly onDidResize = this.onResizeEmitter.event;
|
||||
get onDispose(): Event<void> {
|
||||
return this.editor.onDidDispose;
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly uri: URI,
|
||||
readonly document: MonacoEditorModel,
|
||||
readonly node: HTMLElement,
|
||||
services: MonacoEditorServices,
|
||||
options?: MonacoEditor.IOptions,
|
||||
override?: EditorServiceOverrides,
|
||||
widgetOptions?: ICodeEditorWidgetOptions
|
||||
) {
|
||||
super(services);
|
||||
this.toDispose.pushAll([
|
||||
this.onCursorPositionChangedEmitter,
|
||||
this.onFocusChangedEmitter,
|
||||
this.onDocumentContentChangedEmitter,
|
||||
this.onMouseDownEmitter,
|
||||
this.onLanguageChangedEmitter,
|
||||
this.onScrollChangedEmitter
|
||||
]);
|
||||
this.toDispose.push(this.create({
|
||||
...MonacoEditor.createReadOnlyOptions(document.readOnly),
|
||||
...options,
|
||||
model: undefined,
|
||||
}, override, widgetOptions));
|
||||
this.addHandlers(this.editor);
|
||||
this.editor.setModel(document.textEditorModel);
|
||||
}
|
||||
|
||||
getControl(): CodeEditorWidget {
|
||||
return this.editor;
|
||||
}
|
||||
|
||||
onSelectionChanged(listener: (range: Selection) => void): Disposable {
|
||||
return this.editor.onDidChangeCursorSelection(event =>
|
||||
listener({
|
||||
...this.m2p.asRange(event.selection),
|
||||
direction: event.selection.getDirection() === SelectionDirection.LTR ? 'ltr' : 'rtl'
|
||||
}));
|
||||
}
|
||||
|
||||
protected create(options?: MonacoEditor.IOptions, override?: EditorServiceOverrides, widgetOptions?: ICodeEditorWidgetOptions): Disposable {
|
||||
const combinedOptions = {
|
||||
...options,
|
||||
lightbulb: { enabled: ShowLightbulbIconMode.On },
|
||||
fixedOverflowWidgets: true,
|
||||
automaticLayout: true,
|
||||
scrollbar: {
|
||||
useShadows: false,
|
||||
verticalHasArrows: false,
|
||||
horizontalHasArrows: false,
|
||||
verticalScrollbarSize: 10,
|
||||
horizontalScrollbarSize: 10,
|
||||
...options?.scrollbar,
|
||||
}
|
||||
};
|
||||
const instantiator = this.getInstantiatorWithOverrides(override);
|
||||
return this.editor = instantiator.createInstance(CodeEditorWidget, this.node, {
|
||||
...combinedOptions,
|
||||
dimension: {
|
||||
width: 0,
|
||||
height: 0
|
||||
},
|
||||
}, widgetOptions ?? {});
|
||||
}
|
||||
|
||||
protected addHandlers(codeEditor: CodeEditorWidget): void {
|
||||
this.toDispose.push(codeEditor.onDidChangeModelLanguage(e =>
|
||||
this.fireLanguageChanged(e.newLanguage)
|
||||
));
|
||||
this.toDispose.push(codeEditor.onDidChangeConfiguration(() => this.refresh()));
|
||||
this.toDispose.push(codeEditor.onDidChangeModel(() => this.refresh()));
|
||||
this.toDispose.push(codeEditor.onDidChangeModelContent(e => {
|
||||
this.refresh();
|
||||
this.onDocumentContentChangedEmitter.fire({ document: this.document, contentChanges: e.changes.map(this.mapModelContentChange.bind(this)) });
|
||||
}));
|
||||
this.toDispose.push(codeEditor.onMouseDown(e => {
|
||||
const { element, position, range } = e.target;
|
||||
this.onMouseDownEmitter.fire({
|
||||
target: {
|
||||
...(e.target as unknown as MouseTarget),
|
||||
element: element || undefined,
|
||||
mouseColumn: this.m2p.asPosition(undefined, e.target.mouseColumn).character,
|
||||
range: range && this.m2p.asRange(range) || undefined,
|
||||
position: position && this.m2p.asPosition(position.lineNumber, position.column) || undefined,
|
||||
detail: undefined
|
||||
},
|
||||
event: e.event.browserEvent
|
||||
});
|
||||
}));
|
||||
this.toDispose.push(codeEditor.onDidScrollChange(e => {
|
||||
this.onScrollChangedEmitter.fire(undefined);
|
||||
}));
|
||||
this.toDispose.push(this.onDidChangeReadOnly(readOnly => {
|
||||
codeEditor.updateOptions(MonacoEditor.createReadOnlyOptions(readOnly));
|
||||
}));
|
||||
}
|
||||
|
||||
setLanguage(languageId: string): void {
|
||||
monaco.editor.setModelLanguage(this.document.textEditorModel, languageId);
|
||||
}
|
||||
|
||||
protected fireLanguageChanged(languageId: string): void {
|
||||
this.onLanguageChangedEmitter.fire(languageId);
|
||||
}
|
||||
|
||||
protected getInstantiatorWithOverrides(override?: EditorServiceOverrides): IInstantiationService {
|
||||
const instantiator = StandaloneServices.get(IInstantiationService);
|
||||
if (override) {
|
||||
const overrideServices = new ServiceCollection(...override);
|
||||
const childService = instantiator.createChild(overrideServices);
|
||||
this.toDispose.push(childService);
|
||||
return childService;
|
||||
}
|
||||
return instantiator;
|
||||
}
|
||||
|
||||
protected mapModelContentChange(change: monaco.editor.IModelContentChange): TextDocumentContentChangeDelta {
|
||||
return {
|
||||
range: this.m2p.asRange(change.range),
|
||||
rangeLength: change.rangeLength,
|
||||
text: change.text
|
||||
};
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
this.autoresize();
|
||||
}
|
||||
|
||||
resizeToFit(): void {
|
||||
this.autoresize();
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
this.onResizeEmitter.fire(null);
|
||||
}
|
||||
|
||||
setSize(dimension: Dimension): void {
|
||||
this.resize(dimension);
|
||||
this.onResizeEmitter.fire(dimension);
|
||||
}
|
||||
|
||||
protected autoresize(): void {
|
||||
this.resize();
|
||||
}
|
||||
|
||||
protected resize(dimension?: Dimension): void {
|
||||
if (this.node) {
|
||||
const layoutSize = this.computeLayoutSize(this.node, dimension);
|
||||
this.editor.layout(layoutSize);
|
||||
}
|
||||
}
|
||||
|
||||
protected computeLayoutSize(hostNode: HTMLElement, dimension: monaco.editor.IDimension | undefined): monaco.editor.IDimension {
|
||||
if (dimension && dimension.width >= 0 && dimension.height >= 0) {
|
||||
return dimension;
|
||||
}
|
||||
const boxSizing = ElementExt.boxSizing(hostNode);
|
||||
|
||||
const width = (!dimension || dimension.width < 0) ?
|
||||
this.getWidth(hostNode, boxSizing) :
|
||||
dimension.width;
|
||||
|
||||
const height = (!dimension || dimension.height < 0) ?
|
||||
this.getHeight(hostNode, boxSizing) :
|
||||
dimension.height;
|
||||
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
protected getWidth(hostNode: HTMLElement, boxSizing: ElementExt.IBoxSizing): number {
|
||||
return hostNode.offsetWidth - boxSizing.horizontalSum;
|
||||
}
|
||||
|
||||
protected getHeight(hostNode: HTMLElement, boxSizing: ElementExt.IBoxSizing): number {
|
||||
return this.editor.getContentHeight();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
296
packages/monaco/src/browser/style/index.css
Normal file
296
packages/monaco/src/browser/style/index.css
Normal file
@@ -0,0 +1,296 @@
|
||||
.monaco-editor {
|
||||
padding-bottom: 5.6px;
|
||||
font-family: var(--theia-code-font-family);
|
||||
font-size: inherit !important;
|
||||
}
|
||||
|
||||
#quick-input-container {
|
||||
position: fixed;
|
||||
right: 50%;
|
||||
z-index: 1000000;
|
||||
}
|
||||
|
||||
/*
|
||||
* set z-index to 0, so tabs are not above overlay widgets
|
||||
*/
|
||||
.lm-TabBar.theia-app-centers {
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-editor .zone-widget {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.monaco-editor .zone-widget .zone-widget-container {
|
||||
border-top-style: solid;
|
||||
border-bottom-style: solid;
|
||||
border-top-width: 0;
|
||||
border-bottom-width: 0;
|
||||
border-top-color: var(--theia-peekView-border);
|
||||
border-bottom-color: var(--theia-peekView-border);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-editor .parameter-hints-widget > .wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* List highlight, see https://github.com/microsoft/vscode/blob/ff5f581425da6230b6f9216ecf19abf6c9d285a6/src/vs/workbench/browser/style.ts#L50 */
|
||||
.monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight,
|
||||
.monaco-list .monaco-list-row .monaco-highlighted-label .highlight {
|
||||
color: var(--theia-list-highlightForeground) !important;
|
||||
}
|
||||
|
||||
/* Scrollbars, see https://github.com/microsoft/vscode/blob/ff5f581425da6230b6f9216ecf19abf6c9d285a6/src/vs/workbench/browser/style.ts#L65 */
|
||||
.monaco-scrollable-element > .shadow.top {
|
||||
box-shadow: var(--theia-scrollbar-shadow) 0 6px 6px -6px inset !important;
|
||||
}
|
||||
|
||||
.monaco-scrollable-element > .shadow.left {
|
||||
box-shadow: var(--theia-scrollbar-shadow) 6px 0 6px -6px inset !important;
|
||||
}
|
||||
|
||||
.monaco-scrollable-element > .shadow.top.left {
|
||||
box-shadow: var(--theia-scrollbar-shadow) 6px 6px 6px -6px inset !important;
|
||||
}
|
||||
|
||||
.monaco-scrollable-element > .scrollbar > .slider {
|
||||
background: var(--theia-scrollbarSlider-background) !important;
|
||||
}
|
||||
|
||||
.monaco-scrollable-element > .scrollbar > .slider:hover {
|
||||
background: var(--theia-scrollbarSlider-hoverBackground) !important;
|
||||
}
|
||||
|
||||
.monaco-scrollable-element > .scrollbar > .slider.active {
|
||||
background: var(--theia-scrollbarSlider-activeBackground) !important;
|
||||
}
|
||||
|
||||
.monaco-editor .codicon.codicon-debug-start {
|
||||
color: var(--theia-debugIcon-startForeground) !important;
|
||||
}
|
||||
|
||||
.monaco-editor .codelens-decoration a {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.monaco-editor .reference-zone-widget .ref-tree .referenceMatch .highlight {
|
||||
color: unset !important;
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget .monaco-inputbox.synthetic-focus {
|
||||
outline: var(--theia-border-width) solid;
|
||||
outline-offset: calc(-1 * var(--theia-border-width));
|
||||
outline-color: var(--theia-focusBorder);
|
||||
}
|
||||
|
||||
.monaco-editor .rename-box input {
|
||||
color: var(--theia-editor-foreground);
|
||||
}
|
||||
|
||||
.monaco-editor .rename-box .rename-label {
|
||||
opacity: 0.8;
|
||||
padding: 3px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
/* Monaco Quick Input */
|
||||
.quick-input-widget {
|
||||
background-color: var(--theia-quickInput-background) !important;
|
||||
color: var(--theia-foreground) !important;
|
||||
}
|
||||
|
||||
.quick-input-list .monaco-list-row.focused {
|
||||
background-color: var(--theia-quickInputList-focusBackground) !important;
|
||||
}
|
||||
|
||||
.quick-input-list .monaco-keybinding > .monaco-keybinding-key {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.quick-input-list .monaco-list-row.focused,
|
||||
.quick-input-list .monaco-list-row.focused .monaco-highlighted-label,
|
||||
.quick-input-list .monaco-list-row.focused .monaco-highlighted-label .codicon,
|
||||
.quick-input-list .monaco-list-row.focused .quick-input-list-entry .quick-input-list-separator,
|
||||
.quick-input-list .monaco-list-row.focused .monaco-highlighted-label .monaco-keybinding .monaco-keybinding-key,
|
||||
.quick-input-list .monaco-list-row.focused .monaco-highlighted-label .monaco-keybinding .monaco-keybinding-key-separator {
|
||||
color: var(--theia-quickInputList-focusForeground) !important;
|
||||
}
|
||||
|
||||
.quick-input-list .monaco-list-row .codicon {
|
||||
color: var(--theia-foreground) !important;
|
||||
}
|
||||
|
||||
.quick-input-list .monaco-list-row.focused .codicon {
|
||||
color: var(--theia-list-foreground) !important;
|
||||
}
|
||||
|
||||
.quick-input-list .monaco-list-row.focused .monaco-highlighted-label .highlight {
|
||||
color: var(--theia-list-focusHighlightForeground) !important;
|
||||
}
|
||||
|
||||
.quick-input-titlebar .action-item .action-label {
|
||||
color: var(--theia-foreground) !important;
|
||||
}
|
||||
|
||||
.monaco-list-rows
|
||||
.monaco-list-row:not(:first-child)
|
||||
.quick-input-list-entry.quick-input-list-separator-border {
|
||||
border-top: 1px solid var(--theia-pickerGroup-border) !important;
|
||||
}
|
||||
.quick-input-list .quick-input-list-separator {
|
||||
color: var(--theia-pickerGroup-foreground) !important;
|
||||
}
|
||||
|
||||
.monaco-icon-label > .monaco-icon-label-container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.quick-input-list-rows
|
||||
.quick-input-list-row
|
||||
.monaco-icon-label
|
||||
.monaco-icon-description-container
|
||||
.label-description {
|
||||
font-family: var(--theia-ui-font-family);
|
||||
font-size: calc(var(--theia-ui-font-size1) * 0.9) !important;
|
||||
color: var(--theia-foreground) !important;
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
.quick-input-list-rows
|
||||
.quick-input-list-row
|
||||
.monaco-icon-label
|
||||
.monaco-icon-label-container
|
||||
.monaco-icon-name-container
|
||||
.label-name {
|
||||
font-family: var(--theia-ui-font-family);
|
||||
font-size: var(--theia-ui-font-size1) !important;
|
||||
color: var(--theia-foreground) !important;
|
||||
}
|
||||
|
||||
.quick-input-list .monaco-icon-label.codicon,
|
||||
.quick-input-list .monaco-icon-label.file-icon {
|
||||
display: flex;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.quick-input-list .monaco-icon-label::before {
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.codicon-file.default-file-icon.file-icon {
|
||||
padding-left: 2px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.codicon-file.default-file-icon.file-icon::before {
|
||||
margin-right: 4px;
|
||||
font-size: var(--theia-ui-font-size1);
|
||||
}
|
||||
|
||||
.quick-input-list .monaco-icon-label.codicon::before {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.quick-input-list .monaco-icon-label.theia-file-icons-js {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.quick-input-list .quick-input-list-label {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.quick-input-progress.active.infinite {
|
||||
background-color: var(--theia-progressBar-background);
|
||||
width: 3%;
|
||||
/* `progress-animation` is defined in `packages/core/src/browser/style/progress-bar.css` */
|
||||
animation: progress-animation 1.3s 0s infinite cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
|
||||
.monaco-list:not(.drop-target) .monaco-list-row:hover:not(.selected):not(.focused) {
|
||||
background: var(--theia-list-hoverBackground);
|
||||
}
|
||||
|
||||
.monaco-editor .peekview-widget .head .peekview-title {
|
||||
font-family: var(--theia-ui-font-family);
|
||||
}
|
||||
|
||||
.monaco-editor .peekview-widget .referenceMatch {
|
||||
font-family: var(--theia-ui-font-family);
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget .monaco-findInput .input,
|
||||
.monaco-editor .find-widget .matchesCount {
|
||||
font-family: var(--theia-ui-font-family);
|
||||
}
|
||||
|
||||
.monaco-editor .monaco-action-bar .action-label.codicon {
|
||||
color: var(--theia-foreground);
|
||||
}
|
||||
|
||||
.monaco-editor .monaco-action-bar .action-item.active {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.monaco-editor .peekview-widget .monaco-list-row.focused.selected .label-name,
|
||||
.monaco-editor .peekview-widget .monaco-list-row.focused.selected .label-description,
|
||||
.monaco-editor .peekview-widget .monaco-list-row.focused.selected .monaco-highlighted-label,
|
||||
.monaco-editor .peekview-widget .monaco-list-row.focused.selected .codicon {
|
||||
color: var(--theia-list-activeSelectionForeground) !important;
|
||||
}
|
||||
|
||||
.quick-input-titlebar {
|
||||
background-color: var(--theia-quickInputTitle-background);
|
||||
}
|
||||
|
||||
.quick-input-widget input {
|
||||
background-color: var(--theia-input-background) !important;
|
||||
}
|
||||
|
||||
.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused>.contents>.main .monaco-highlighted-label .highlight {
|
||||
color: var(--vscode-editorSuggestWidget-focusHighlightForeground) !important;
|
||||
}
|
||||
|
||||
.symbol-icon-center {
|
||||
align-self: center;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.monaco-inputbox.error > .ibwrapper > .input,
|
||||
.monaco-inputbox.warning > .ibwrapper > .input,
|
||||
.monaco-inputbox.info > .ibwrapper > .input {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
.monaco-inputbox > .ibwrapper > .input {
|
||||
min-height: 28px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.quick-input-header > .quick-input-inline-action-bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-editor .overlay-button {
|
||||
padding: 6px 11px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
z-index: 1;
|
||||
background-color: var(--theia-button-background);
|
||||
color: var(--theia-button-foreground);
|
||||
border: 1px solid var(--theia-contrast-border);
|
||||
font-family: var(--theia-ui-font-family);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.monaco-editor .overlay-button.theia-mod-disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-diff-editor .gutter .action-item .action-label {
|
||||
color: var(--theia-editorGutter-itemGlyphForeground);
|
||||
}
|
||||
20
packages/monaco/src/browser/textmate/index.ts
Normal file
20
packages/monaco/src/browser/textmate/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
export * from './textmate-registry';
|
||||
export * from './textmate-contribution';
|
||||
export * from './monaco-textmate-service';
|
||||
export * from './monaco-textmate-frontend-bindings';
|
||||
@@ -0,0 +1,90 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { interfaces } from '@theia/core/shared/inversify';
|
||||
import { FrontendApplicationContribution, isBasicWasmSupported } from '@theia/core/lib/browser';
|
||||
import { bindContributionProvider } from '@theia/core';
|
||||
import { TextmateRegistry } from './textmate-registry';
|
||||
import { LanguageGrammarDefinitionContribution } from './textmate-contribution';
|
||||
import { MonacoTextmateService } from './monaco-textmate-service';
|
||||
import { MonacoThemeRegistry } from './monaco-theme-registry';
|
||||
import { loadWASM, createOnigScanner, OnigScanner, createOnigString, OnigString } from 'vscode-oniguruma';
|
||||
import { IOnigLib, IRawGrammar, parseRawGrammar, Registry } from 'vscode-textmate';
|
||||
import { OnigasmProvider, TextmateRegistryFactory, ThemeMix } from './monaco-theme-types';
|
||||
|
||||
export class OnigasmLib implements IOnigLib {
|
||||
createOnigScanner(sources: string[]): OnigScanner {
|
||||
return createOnigScanner(sources);
|
||||
}
|
||||
createOnigString(sources: string): OnigString {
|
||||
return createOnigString(sources);
|
||||
}
|
||||
}
|
||||
|
||||
export default (bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
|
||||
const onigLib = createOnigasmLib();
|
||||
bind(OnigasmProvider).toConstantValue(() => onigLib);
|
||||
bind(MonacoTextmateService).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(MonacoTextmateService);
|
||||
bindContributionProvider(bind, LanguageGrammarDefinitionContribution);
|
||||
bind(TextmateRegistry).toSelf().inSingletonScope();
|
||||
bind(MonacoThemeRegistry).toSelf().inSingletonScope();
|
||||
bind(TextmateRegistryFactory).toFactory(({ container }) => (theme?: ThemeMix) => {
|
||||
const onigProvider = container.get<OnigasmProvider>(OnigasmProvider);
|
||||
const textmateRegistry = container.get(TextmateRegistry);
|
||||
return new Registry({
|
||||
onigLib: onigProvider(),
|
||||
theme,
|
||||
loadGrammar: async (scopeName: string) => {
|
||||
const provider = textmateRegistry.getProvider(scopeName);
|
||||
if (provider) {
|
||||
const definition = await provider.getGrammarDefinition();
|
||||
let rawGrammar: IRawGrammar;
|
||||
if (typeof definition.content === 'string') {
|
||||
rawGrammar = parseRawGrammar(definition.content, definition.format === 'json' ? 'grammar.json' : 'grammar.plist');
|
||||
} else {
|
||||
rawGrammar = definition.content as IRawGrammar;
|
||||
}
|
||||
return rawGrammar;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
getInjections: (scopeName: string) => {
|
||||
const provider = textmateRegistry.getProvider(scopeName);
|
||||
if (provider && provider.getInjections) {
|
||||
return provider.getInjections(scopeName);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export async function createOnigasmLib(): Promise<IOnigLib> {
|
||||
if (!isBasicWasmSupported) {
|
||||
throw new Error('wasm not supported');
|
||||
}
|
||||
const wasm = await fetchOnigasm();
|
||||
await loadWASM(wasm);
|
||||
return new OnigasmLib();
|
||||
}
|
||||
|
||||
export async function fetchOnigasm(): Promise<ArrayBuffer> {
|
||||
// Using Webpack's wasm loader should give us a URL to fetch the resource from:
|
||||
const onigasmPath: string = require('vscode-oniguruma/release/onig.wasm');
|
||||
const response = await fetch(onigasmPath, { method: 'GET' });
|
||||
return response.arrayBuffer();
|
||||
}
|
||||
187
packages/monaco/src/browser/textmate/monaco-textmate-service.ts
Normal file
187
packages/monaco/src/browser/textmate/monaco-textmate-service.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 Redhat, Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject, named } from '@theia/core/shared/inversify';
|
||||
import { Registry } from 'vscode-textmate';
|
||||
import { ILogger, ContributionProvider, DisposableCollection, Disposable } from '@theia/core';
|
||||
import { FrontendApplicationContribution, isBasicWasmSupported } from '@theia/core/lib/browser';
|
||||
import { ThemeService } from '@theia/core/lib/browser/theming';
|
||||
import { LanguageGrammarDefinitionContribution, getEncodedLanguageId } from './textmate-contribution';
|
||||
import { createTextmateTokenizer, TokenizerOption } from './textmate-tokenizer';
|
||||
import { TextmateRegistry } from './textmate-registry';
|
||||
import { MonacoThemeRegistry } from './monaco-theme-registry';
|
||||
import { EditorPreferences } from '@theia/editor/lib/common/editor-preferences';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { TokenizationRegistry } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
|
||||
import { IStandaloneThemeService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/common/standaloneTheme';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { ILanguageService } from '@theia/monaco-editor-core/esm/vs/editor/common/languages/language';
|
||||
import { TokenizationSupportAdapter } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneLanguages';
|
||||
import { LanguageService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageService';
|
||||
import { OnigasmProvider, TextmateRegistryFactory } from './monaco-theme-types';
|
||||
|
||||
@injectable()
|
||||
export class MonacoTextmateService implements FrontendApplicationContribution {
|
||||
|
||||
protected readonly tokenizerOption: TokenizerOption = {
|
||||
lineLimit: 400
|
||||
};
|
||||
|
||||
protected readonly _activatedLanguages = new Set<string>();
|
||||
|
||||
protected grammarRegistry: Registry;
|
||||
|
||||
@inject(ContributionProvider) @named(LanguageGrammarDefinitionContribution)
|
||||
protected readonly grammarProviders: ContributionProvider<LanguageGrammarDefinitionContribution>;
|
||||
|
||||
@inject(TextmateRegistry)
|
||||
protected readonly textmateRegistry: TextmateRegistry;
|
||||
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
@inject(OnigasmProvider)
|
||||
protected readonly onigasmProvider: OnigasmProvider;
|
||||
|
||||
@inject(ThemeService)
|
||||
protected readonly themeService: ThemeService;
|
||||
|
||||
@inject(MonacoThemeRegistry)
|
||||
protected readonly monacoThemeRegistry: MonacoThemeRegistry;
|
||||
|
||||
@inject(EditorPreferences)
|
||||
protected readonly preferences: EditorPreferences;
|
||||
|
||||
@inject(TextmateRegistryFactory)
|
||||
protected readonly registryFactory: TextmateRegistryFactory;
|
||||
|
||||
initialize(): void {
|
||||
if (!isBasicWasmSupported) {
|
||||
console.log('Textmate support deactivated because WebAssembly is not detected.');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const grammarProvider of this.grammarProviders.getContributions()) {
|
||||
try {
|
||||
grammarProvider.registerTextmateLanguage(this.textmateRegistry);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
this.grammarRegistry = this.registryFactory(this.monacoThemeRegistry.getThemeData(this.currentEditorTheme));
|
||||
|
||||
this.tokenizerOption.lineLimit = this.preferences['editor.maxTokenizationLineLength'];
|
||||
this.preferences.onPreferenceChanged(e => {
|
||||
if (e.preferenceName === 'editor.maxTokenizationLineLength') {
|
||||
this.tokenizerOption.lineLimit = this.preferences['editor.maxTokenizationLineLength'];
|
||||
}
|
||||
});
|
||||
|
||||
this.updateTheme();
|
||||
this.themeService.onDidColorThemeChange(() => this.updateTheme());
|
||||
|
||||
for (const id of this.textmateRegistry.languages) {
|
||||
this.activateLanguage(id);
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly toDisposeOnUpdateTheme = new DisposableCollection();
|
||||
|
||||
protected updateTheme(): void {
|
||||
this.toDisposeOnUpdateTheme.dispose();
|
||||
|
||||
const currentEditorTheme = this.currentEditorTheme;
|
||||
document.body.classList.add(currentEditorTheme);
|
||||
this.toDisposeOnUpdateTheme.push(Disposable.create(() => document.body.classList.remove(currentEditorTheme)));
|
||||
|
||||
// first update registry to run tokenization with the proper theme
|
||||
const theme = this.monacoThemeRegistry.getThemeData(currentEditorTheme);
|
||||
if (theme) {
|
||||
this.grammarRegistry.setTheme(theme);
|
||||
}
|
||||
|
||||
// then trigger tokenization by setting monaco theme
|
||||
monaco.editor.setTheme(currentEditorTheme);
|
||||
}
|
||||
|
||||
protected get currentEditorTheme(): string {
|
||||
return this.themeService.getCurrentTheme().editorTheme || MonacoThemeRegistry.DARK_DEFAULT_THEME;
|
||||
}
|
||||
|
||||
activateLanguage(language: string): Disposable {
|
||||
const toDispose = new DisposableCollection(
|
||||
Disposable.create(() => { /* mark as not disposed */ })
|
||||
);
|
||||
toDispose.push(this.waitForLanguage(language, () =>
|
||||
this.doActivateLanguage(language, toDispose)
|
||||
));
|
||||
return toDispose;
|
||||
}
|
||||
|
||||
protected async doActivateLanguage(languageId: string, toDispose: DisposableCollection): Promise<void> {
|
||||
if (this._activatedLanguages.has(languageId)) {
|
||||
return;
|
||||
}
|
||||
this._activatedLanguages.add(languageId);
|
||||
toDispose.push(Disposable.create(() => this._activatedLanguages.delete(languageId)));
|
||||
|
||||
const scopeName = this.textmateRegistry.getScope(languageId);
|
||||
if (!scopeName) {
|
||||
return;
|
||||
}
|
||||
const provider = this.textmateRegistry.getProvider(scopeName);
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
|
||||
const configuration = this.textmateRegistry.getGrammarConfiguration(languageId);
|
||||
const initialLanguage = getEncodedLanguageId(languageId);
|
||||
|
||||
await this.onigasmProvider();
|
||||
if (toDispose.disposed) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const grammar = await this.grammarRegistry.loadGrammarWithConfiguration(scopeName, initialLanguage, configuration);
|
||||
if (toDispose.disposed) {
|
||||
return;
|
||||
}
|
||||
if (!grammar) {
|
||||
throw new Error(`no grammar for ${scopeName}, ${initialLanguage}, ${JSON.stringify(configuration)}`);
|
||||
}
|
||||
const options = configuration.tokenizerOption ? configuration.tokenizerOption : this.tokenizerOption;
|
||||
const tokenizer = createTextmateTokenizer(grammar, options);
|
||||
toDispose.push(monaco.languages.setTokensProvider(languageId, tokenizer));
|
||||
const support = TokenizationRegistry.get(languageId);
|
||||
const themeService = StandaloneServices.get(IStandaloneThemeService);
|
||||
const languageService = StandaloneServices.get(ILanguageService);
|
||||
const adapter = new TokenizationSupportAdapter(languageId, tokenizer, languageService, themeService);
|
||||
support!.tokenize = adapter.tokenize.bind(adapter);
|
||||
} catch (error) {
|
||||
this.logger.warn('No grammar for this language id', languageId, error);
|
||||
}
|
||||
}
|
||||
|
||||
protected waitForLanguage(language: string, cb: () => {}): Disposable {
|
||||
const languageService = StandaloneServices.get(ILanguageService) as LanguageService;
|
||||
if (languageService['_requestedBasicLanguages'].has(language)) {
|
||||
cb();
|
||||
return Disposable.NULL;
|
||||
}
|
||||
return monaco.languages.onLanguage(language, cb);
|
||||
}
|
||||
}
|
||||
176
packages/monaco/src/browser/textmate/monaco-theme-registry.ts
Normal file
176
packages/monaco/src/browser/textmate/monaco-theme-registry.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { IRawTheme } from 'vscode-textmate';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { IStandaloneThemeService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/common/standaloneTheme';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { StandaloneThemeService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneThemeService';
|
||||
import { Color } from '@theia/monaco-editor-core/esm/vs/base/common/color';
|
||||
import { MixStandaloneTheme, TextmateRegistryFactory, ThemeMix } from './monaco-theme-types';
|
||||
|
||||
@injectable()
|
||||
export class MonacoThemeRegistry {
|
||||
|
||||
@inject(TextmateRegistryFactory) protected readonly registryFactory: TextmateRegistryFactory;
|
||||
|
||||
initializeDefaultThemes(): void {
|
||||
this.register(require('../../../data/monaco-themes/vscode/dark_theia.json'), {
|
||||
'./dark_vs.json': require('../../../data/monaco-themes/vscode/dark_vs.json'),
|
||||
'./dark_plus.json': require('../../../data/monaco-themes/vscode/dark_plus.json')
|
||||
}, 'dark-theia', 'vs-dark');
|
||||
this.register(require('../../../data/monaco-themes/vscode/light_theia.json'), {
|
||||
'./light_vs.json': require('../../../data/monaco-themes/vscode/light_vs.json'),
|
||||
'./light_plus.json': require('../../../data/monaco-themes/vscode/light_plus.json'),
|
||||
}, 'light-theia', 'vs');
|
||||
this.register(require('../../../data/monaco-themes/vscode/hc_theia.json'), {
|
||||
'./hc_black.json': require('../../../data/monaco-themes/vscode/hc_black.json')
|
||||
}, 'hc-theia', 'hc-black');
|
||||
this.register(require('../../../data/monaco-themes/vscode/hc_theia_light.json'), {
|
||||
'./hc_light.json': require('../../../data/monaco-themes/vscode/hc_light.json')
|
||||
}, 'hc-theia-light', 'hc-light');
|
||||
}
|
||||
|
||||
getThemeData(): ThemeMix;
|
||||
getThemeData(name: string): ThemeMix | undefined;
|
||||
getThemeData(name?: string): ThemeMix | undefined {
|
||||
const theme = this.doGetTheme(name);
|
||||
return theme && theme.themeData;
|
||||
}
|
||||
|
||||
getTheme(): MixStandaloneTheme;
|
||||
getTheme(name: string): MixStandaloneTheme | undefined;
|
||||
getTheme(name?: string): MixStandaloneTheme | undefined {
|
||||
return this.doGetTheme(name);
|
||||
}
|
||||
|
||||
protected doGetTheme(name: string | undefined): MixStandaloneTheme | undefined {
|
||||
const standaloneThemeService = StandaloneServices.get(IStandaloneThemeService) as StandaloneThemeService;
|
||||
const theme = !name ? standaloneThemeService.getColorTheme() : standaloneThemeService['_knownThemes'].get(name);
|
||||
return theme as MixStandaloneTheme | undefined;
|
||||
}
|
||||
|
||||
setTheme(name: string, data: ThemeMix): void {
|
||||
// monaco auto refreshes a theme with new data
|
||||
monaco.editor.defineTheme(name, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register VS Code compatible themes
|
||||
*/
|
||||
register(json: any, includes?: { [includePath: string]: any }, givenName?: string, monacoBase?: monaco.editor.BuiltinTheme): ThemeMix {
|
||||
const name = givenName || json.name!;
|
||||
const result: ThemeMix = {
|
||||
name,
|
||||
base: monacoBase || 'vs',
|
||||
inherit: true,
|
||||
colors: {},
|
||||
rules: [],
|
||||
settings: []
|
||||
};
|
||||
if (typeof json.include !== 'undefined') {
|
||||
if (!includes || !includes[json.include]) {
|
||||
console.error(`Couldn't resolve includes theme ${json.include}.`);
|
||||
} else {
|
||||
const parentTheme = this.register(includes[json.include], includes);
|
||||
Object.assign(result.colors, parentTheme.colors);
|
||||
result.rules.push(...parentTheme.rules);
|
||||
result.settings.push(...parentTheme.settings);
|
||||
}
|
||||
}
|
||||
const tokenColors: IRawTheme['settings'] = json.tokenColors;
|
||||
if (Array.isArray(tokenColors)) {
|
||||
for (const tokenColor of tokenColors) {
|
||||
if (tokenColor.scope && tokenColor.settings) {
|
||||
result.settings.push({
|
||||
scope: tokenColor.scope,
|
||||
settings: {
|
||||
foreground: this.normalizeColor(tokenColor.settings.foreground),
|
||||
background: this.normalizeColor(tokenColor.settings.background),
|
||||
fontStyle: tokenColor.settings.fontStyle
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (json.colors) {
|
||||
Object.assign(result.colors, json.colors);
|
||||
result.encodedTokensColors = Object.keys(result.colors).map(key => result.colors[key]);
|
||||
}
|
||||
if (monacoBase && givenName) {
|
||||
for (const setting of result.settings) {
|
||||
this.transform(setting, rule => result.rules.push(rule));
|
||||
}
|
||||
|
||||
// the default rule (scope empty) is always the first rule. Ignore all other default rules.
|
||||
const defaultTheme = (StandaloneServices.get(IStandaloneThemeService) as StandaloneThemeService)['_knownThemes'].get(result.base)!;
|
||||
const foreground = result.colors['editor.foreground'] || defaultTheme.getColor('editor.foreground');
|
||||
const background = result.colors['editor.background'] || defaultTheme.getColor('editor.background');
|
||||
result.settings.unshift({
|
||||
settings: {
|
||||
foreground: this.normalizeColor(foreground),
|
||||
background: this.normalizeColor(background)
|
||||
}
|
||||
});
|
||||
|
||||
const reg = this.registryFactory(result);
|
||||
result.encodedTokensColors = reg.getColorMap();
|
||||
// index 0 has to be set to null as it is 'undefined' by default, but monaco code expects it to be null
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
result.encodedTokensColors[0] = null!;
|
||||
this.setTheme(givenName, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected transform(tokenColor: any, acceptor: (rule: monaco.editor.ITokenThemeRule) => void): void {
|
||||
if (typeof tokenColor.scope === 'undefined') {
|
||||
tokenColor.scope = [''];
|
||||
} else if (typeof tokenColor.scope === 'string') {
|
||||
tokenColor.scope = tokenColor.scope.split(',').map((scope: string) => scope.trim());
|
||||
}
|
||||
|
||||
for (const scope of tokenColor.scope) {
|
||||
acceptor({
|
||||
...tokenColor.settings, token: scope
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected normalizeColor(color: string | Color | undefined): string | undefined {
|
||||
if (!color) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = String(color).replace(/^\#/, '').slice(0, 6);
|
||||
if (normalized.length < 6 || !(normalized).match(/^[0-9A-Fa-f]{6}$/)) {
|
||||
// ignoring not normalized colors to avoid breaking token color indexes between monaco and vscode-textmate
|
||||
console.error(`Color '${normalized}' is NOT normalized, it must have 6 positions.`);
|
||||
return undefined;
|
||||
}
|
||||
return '#' + normalized;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace MonacoThemeRegistry {
|
||||
export const DARK_DEFAULT_THEME = 'dark-theia';
|
||||
export const LIGHT_DEFAULT_THEME = 'light-theia';
|
||||
export const HC_DEFAULT_THEME = 'hc-theia';
|
||||
export const HC_LIGHT_THEME = 'hc-theia-light';
|
||||
}
|
||||
36
packages/monaco/src/browser/textmate/monaco-theme-types.ts
Normal file
36
packages/monaco/src/browser/textmate/monaco-theme-types.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { IStandaloneTheme } from '@theia/monaco-editor-core/esm/vs/editor/standalone/common/standaloneTheme';
|
||||
import { IOnigLib, IRawTheme, Registry } from 'vscode-textmate';
|
||||
|
||||
export interface ThemeMix extends IRawTheme, monaco.editor.IStandaloneThemeData { }
|
||||
export interface MixStandaloneTheme extends IStandaloneTheme {
|
||||
themeData: ThemeMix
|
||||
}
|
||||
|
||||
export const OnigasmProvider = Symbol('OnigasmProvider');
|
||||
export type OnigasmProvider = () => Promise<IOnigLib>;
|
||||
export const TextmateRegistryFactory = Symbol('TextmateRegistryFactory');
|
||||
export type TextmateRegistryFactory = (currentTheme?: ThemeMix) => Registry;
|
||||
|
||||
export type MonacoThemeColor = monaco.editor.IColors;
|
||||
export interface MonacoTokenRule extends monaco.editor.ITokenThemeRule { };
|
||||
export type MonacoBuiltinTheme = monaco.editor.BuiltinTheme;
|
||||
export interface MonacoTheme extends monaco.editor.IStandaloneThemeData {
|
||||
name: string;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { TextmateRegistry } from './textmate-registry';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
|
||||
/**
|
||||
* Callback for extensions to contribute language grammar definitions
|
||||
*/
|
||||
export const LanguageGrammarDefinitionContribution = Symbol('LanguageGrammarDefinitionContribution');
|
||||
export interface LanguageGrammarDefinitionContribution {
|
||||
registerTextmateLanguage(registry: TextmateRegistry): void;
|
||||
}
|
||||
export function getEncodedLanguageId(languageId: string): number {
|
||||
return monaco.languages.getEncodedLanguageId(languageId);
|
||||
}
|
||||
129
packages/monaco/src/browser/textmate/textmate-registry.ts
Normal file
129
packages/monaco/src/browser/textmate/textmate-registry.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { IGrammarConfiguration } from 'vscode-textmate';
|
||||
import { TokenizerOption } from './textmate-tokenizer';
|
||||
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||
|
||||
export interface TextmateGrammarConfiguration extends IGrammarConfiguration {
|
||||
|
||||
/**
|
||||
* Optional options to further refine the tokenization of the grammar.
|
||||
*/
|
||||
readonly tokenizerOption?: TokenizerOption;
|
||||
|
||||
}
|
||||
|
||||
export interface GrammarDefinitionProvider {
|
||||
getGrammarDefinition(): Promise<GrammarDefinition>;
|
||||
getInjections?(scopeName: string): string[];
|
||||
}
|
||||
|
||||
export interface GrammarDefinition {
|
||||
format: 'json' | 'plist';
|
||||
content: object | string;
|
||||
location?: string;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class TextmateRegistry {
|
||||
|
||||
protected readonly scopeToProvider = new Map<string, GrammarDefinitionProvider[]>();
|
||||
protected readonly languageToConfig = new Map<string, TextmateGrammarConfiguration[]>();
|
||||
protected readonly languageIdToScope = new Map<string, string[]>();
|
||||
|
||||
get languages(): IterableIterator<string> {
|
||||
return this.languageIdToScope.keys();
|
||||
}
|
||||
|
||||
registerTextmateGrammarScope(scope: string, provider: GrammarDefinitionProvider): Disposable {
|
||||
const providers = this.scopeToProvider.get(scope) || [];
|
||||
const existingProvider = providers[0];
|
||||
if (existingProvider) {
|
||||
Promise.all([existingProvider.getGrammarDefinition(), provider.getGrammarDefinition()]).then(([a, b]) => {
|
||||
if (a.location !== b.location || !a.location && !b.location) {
|
||||
console.warn(`a registered grammar provider for '${scope}' scope is overridden`);
|
||||
}
|
||||
});
|
||||
}
|
||||
providers.unshift(provider);
|
||||
this.scopeToProvider.set(scope, providers);
|
||||
return Disposable.create(() => {
|
||||
const index = providers.indexOf(provider);
|
||||
if (index !== -1) {
|
||||
providers.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getProvider(scope: string): GrammarDefinitionProvider | undefined {
|
||||
const providers = this.scopeToProvider.get(scope);
|
||||
return providers && providers[0];
|
||||
}
|
||||
|
||||
mapLanguageIdToTextmateGrammar(languageId: string, scope: string): Disposable {
|
||||
const scopes = this.languageIdToScope.get(languageId) || [];
|
||||
const existingScope = scopes[0];
|
||||
if (typeof existingScope === 'string') {
|
||||
console.warn(`'${languageId}' language is remapped from '${existingScope}' to '${scope}' scope`);
|
||||
}
|
||||
scopes.unshift(scope);
|
||||
this.languageIdToScope.set(languageId, scopes);
|
||||
return Disposable.create(() => {
|
||||
const index = scopes.indexOf(scope);
|
||||
if (index !== -1) {
|
||||
scopes.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getScope(languageId: string): string | undefined {
|
||||
const scopes = this.languageIdToScope.get(languageId);
|
||||
return scopes && scopes[0];
|
||||
}
|
||||
|
||||
getLanguageId(scope: string): string | undefined {
|
||||
for (const languageId of this.languageIdToScope.keys()) {
|
||||
if (this.getScope(languageId) === scope) {
|
||||
return languageId;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
registerGrammarConfiguration(languageId: string, config: TextmateGrammarConfiguration): Disposable {
|
||||
const configs = this.languageToConfig.get(languageId) || [];
|
||||
const existingConfig = configs[0];
|
||||
if (existingConfig) {
|
||||
console.warn(`a registered grammar configuration for '${languageId}' language is overridden`);
|
||||
}
|
||||
configs.unshift(config);
|
||||
this.languageToConfig.set(languageId, configs);
|
||||
return Disposable.create(() => {
|
||||
const index = configs.indexOf(config);
|
||||
if (index !== -1) {
|
||||
configs.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getGrammarConfiguration(languageId: string): TextmateGrammarConfiguration {
|
||||
const configs = this.languageToConfig.get(languageId);
|
||||
return configs && configs[0] || {};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { SnippetParser } from '@theia/monaco-editor-core/esm/vs/editor/contrib/snippet/browser/snippetParser';
|
||||
|
||||
/**
|
||||
* @deprecated use MonacoSnippetSuggestProvider instead
|
||||
*/
|
||||
export class TextmateSnippetCompletionProvider implements monaco.languages.CompletionItemProvider {
|
||||
|
||||
private items: monaco.languages.CompletionItem[];
|
||||
|
||||
constructor(protected config: TextmateSnippets, protected mdLanguage: string = '') {
|
||||
this.items = [];
|
||||
for (const name of Object.keys(config)) {
|
||||
const textmateSnippet = config[name];
|
||||
const insertText = Array.isArray(textmateSnippet.body) ? textmateSnippet.body.join('\n') : textmateSnippet.body;
|
||||
this.items.push({
|
||||
label: textmateSnippet.prefix,
|
||||
detail: textmateSnippet.description,
|
||||
kind: monaco.languages.CompletionItemKind.Snippet,
|
||||
documentation: {
|
||||
value: '```' + this.mdLanguage + '\n' + this.replaceVariables(insertText) + '```'
|
||||
},
|
||||
insertText: insertText,
|
||||
range: undefined!
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected replaceVariables(textmateSnippet: string): string {
|
||||
return new SnippetParser().parse(textmateSnippet).toString();
|
||||
}
|
||||
|
||||
provideCompletionItems(document: monaco.editor.ITextModel,
|
||||
position: monaco.Position,
|
||||
context: monaco.languages.CompletionContext,
|
||||
token: monaco.CancellationToken): monaco.languages.CompletionList {
|
||||
return {
|
||||
suggestions: this.items
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use JsonSerializedSnippets & MonacoSnippetSuggestProvider instead
|
||||
*/
|
||||
export interface TextmateSnippets {
|
||||
[name: string]: TextmateSnippet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use JsonSerializedSnippet & MonacoSnippetSuggestProvider instead
|
||||
*/
|
||||
export interface TextmateSnippet {
|
||||
readonly prefix: string,
|
||||
readonly body: string[],
|
||||
readonly description: string
|
||||
}
|
||||
84
packages/monaco/src/browser/textmate/textmate-tokenizer.ts
Normal file
84
packages/monaco/src/browser/textmate/textmate-tokenizer.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { INITIAL, IGrammar, StateStack } from 'vscode-textmate';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
|
||||
export class TokenizerState implements monaco.languages.IState {
|
||||
|
||||
constructor(
|
||||
public readonly stateStack: StateStack
|
||||
) { }
|
||||
|
||||
clone(): monaco.languages.IState {
|
||||
return new TokenizerState(this.stateStack);
|
||||
}
|
||||
|
||||
equals(other: monaco.languages.IState): boolean {
|
||||
return other instanceof TokenizerState && (other === this || other.stateStack === this.stateStack);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the TextMate tokenizer.
|
||||
*/
|
||||
export interface TokenizerOption {
|
||||
|
||||
/**
|
||||
* Maximum line length that will be handled by the TextMate tokenizer. If the length of the actual line exceeds this
|
||||
* limit, the tokenizer terminates and the tokenization of any subsequent lines might be broken.
|
||||
*
|
||||
* If the `lineLimit` is not defined, it means, there are no line length limits. Otherwise, it must be a positive
|
||||
* integer or an error will be thrown.
|
||||
*/
|
||||
lineLimit?: number;
|
||||
|
||||
}
|
||||
|
||||
export function createTextmateTokenizer(grammar: IGrammar, options: TokenizerOption): monaco.languages.EncodedTokensProvider & monaco.languages.TokensProvider {
|
||||
if (options.lineLimit !== undefined && (options.lineLimit <= 0 || !Number.isInteger(options.lineLimit))) {
|
||||
throw new Error(`The 'lineLimit' must be a positive integer. It was ${options.lineLimit}.`);
|
||||
}
|
||||
return {
|
||||
getInitialState: () => new TokenizerState(INITIAL),
|
||||
tokenizeEncoded(line: string, state: TokenizerState): monaco.languages.IEncodedLineTokens {
|
||||
if (options.lineLimit !== undefined && line.length > options.lineLimit) {
|
||||
// Skip tokenizing the line if it exceeds the line limit.
|
||||
return { endState: state.stateStack, tokens: new Uint32Array() };
|
||||
}
|
||||
const result = grammar.tokenizeLine2(line, state.stateStack, 500);
|
||||
return {
|
||||
endState: new TokenizerState(result.ruleStack),
|
||||
tokens: result.tokens
|
||||
};
|
||||
},
|
||||
tokenize(line: string, state: TokenizerState): monaco.languages.ILineTokens {
|
||||
if (options.lineLimit !== undefined && line.length > options.lineLimit) {
|
||||
// Skip tokenizing the line if it exceeds the line limit.
|
||||
return { endState: state.stateStack, tokens: [] };
|
||||
}
|
||||
const result = grammar.tokenizeLine(line, state.stateStack, 500);
|
||||
return {
|
||||
endState: new TokenizerState(result.ruleStack),
|
||||
tokens: result.tokens.map(t => ({
|
||||
startIndex: t.startIndex,
|
||||
scopes: t.scopes.reverse().join(' ')
|
||||
}))
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
196
packages/monaco/src/browser/workspace-symbol-command.ts
Normal file
196
packages/monaco/src/browser/workspace-symbol-command.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import { environment } from '@theia/core/shared/@theia/application-package/lib/environment';
|
||||
import { KeybindingContribution, KeybindingRegistry, OpenerService, LabelProvider } from '@theia/core/lib/browser';
|
||||
import { QuickAccessContribution, QuickAccessProvider, QuickInputService, QuickAccessRegistry, QuickPicks, QuickPickItem, findMatches } from '@theia/core/lib/browser/quick-input';
|
||||
import {
|
||||
CommandRegistry, CommandHandler, Command, SelectionService, CancellationToken,
|
||||
CommandContribution, MenuContribution, MenuModelRegistry, nls
|
||||
} from '@theia/core/lib/common';
|
||||
import { Range, Position, SymbolInformation, WorkspaceSymbolParams } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import { MonacoLanguages, WorkspaceSymbolProvider } from './monaco-languages';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { EditorMainMenu } from '@theia/editor/lib/browser';
|
||||
|
||||
@injectable()
|
||||
export class WorkspaceSymbolCommand implements QuickAccessProvider, CommandContribution, KeybindingContribution, MenuContribution, CommandHandler, QuickAccessContribution {
|
||||
public static readonly PREFIX = '#';
|
||||
|
||||
private command = Command.toDefaultLocalizedCommand({
|
||||
id: 'languages.workspace.symbol',
|
||||
label: 'Go to Symbol in Workspace...'
|
||||
});
|
||||
|
||||
@inject(MonacoLanguages) protected readonly languages: MonacoLanguages;
|
||||
@inject(OpenerService) protected readonly openerService: OpenerService;
|
||||
@inject(QuickInputService) protected quickInputService: QuickInputService;
|
||||
@inject(QuickAccessRegistry) protected quickAccessRegistry: QuickAccessRegistry;
|
||||
@inject(SelectionService) protected selectionService: SelectionService;
|
||||
@inject(LabelProvider) protected readonly labelProvider: LabelProvider;
|
||||
|
||||
isEnabled(): boolean {
|
||||
return this.languages.workspaceSymbolProviders !== undefined;
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
this.quickInputService.open(WorkspaceSymbolCommand.PREFIX);
|
||||
}
|
||||
|
||||
registerCommands(commands: CommandRegistry): void {
|
||||
commands.registerCommand(this.command, this);
|
||||
}
|
||||
|
||||
registerMenus(menus: MenuModelRegistry): void {
|
||||
menus.registerMenuAction(EditorMainMenu.WORKSPACE_GROUP, {
|
||||
commandId: this.command.id,
|
||||
order: '2'
|
||||
});
|
||||
}
|
||||
|
||||
private isElectron(): boolean {
|
||||
return environment.electron.is();
|
||||
}
|
||||
|
||||
registerKeybindings(keybindings: KeybindingRegistry): void {
|
||||
keybindings.registerKeybinding({
|
||||
command: this.command.id,
|
||||
keybinding: this.isElectron() ? 'ctrlcmd+t' : 'ctrlcmd+o',
|
||||
});
|
||||
}
|
||||
|
||||
registerQuickAccessProvider(): void {
|
||||
this.quickAccessRegistry.registerQuickAccessProvider({
|
||||
getInstance: () => this,
|
||||
prefix: WorkspaceSymbolCommand.PREFIX,
|
||||
placeholder: '',
|
||||
helpEntries: [{ description: nls.localizeByDefault('Go to Symbol in Workspace'), needsEditor: false }]
|
||||
});
|
||||
}
|
||||
|
||||
async getPicks(filter: string, token: CancellationToken): Promise<QuickPicks> {
|
||||
const items: QuickPicks = [];
|
||||
if (this.languages.workspaceSymbolProviders) {
|
||||
const param: WorkspaceSymbolParams = {
|
||||
query: filter
|
||||
};
|
||||
|
||||
const workspaceProviderPromises = [];
|
||||
for (const provider of this.languages.workspaceSymbolProviders) {
|
||||
workspaceProviderPromises.push((async () => {
|
||||
const symbols = await provider.provideWorkspaceSymbols(param, token);
|
||||
if (symbols && !token.isCancellationRequested) {
|
||||
for (const symbol of symbols) {
|
||||
items.push(this.createItem(symbol, provider, filter, token));
|
||||
}
|
||||
}
|
||||
return symbols;
|
||||
})());
|
||||
}
|
||||
await Promise.all(workspaceProviderPromises.map(p => p.then(sym => sym, _ => undefined)))
|
||||
.then(symbols => {
|
||||
const filteredSymbols = symbols.filter(el => el && el.length !== 0);
|
||||
if (filteredSymbols.length === 0) {
|
||||
items.push({
|
||||
label: filter.length === 0
|
||||
? nls.localize('theia/monaco/typeToSearchForSymbols', 'Type to search for symbols')
|
||||
: nls.localize('theia/monaco/noSymbolsMatching', 'No symbols matching'),
|
||||
});
|
||||
}
|
||||
}).catch();
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
protected createItem(sym: SymbolInformation, provider: WorkspaceSymbolProvider, filter: string, token: CancellationToken): QuickPickItem {
|
||||
const uri = new URI(sym.location.uri);
|
||||
const iconClasses = this.toCssClassName(sym.kind);
|
||||
let parent = sym.containerName;
|
||||
if (parent) {
|
||||
parent += ' - ';
|
||||
}
|
||||
const description = (parent || '') + this.labelProvider.getName(uri);
|
||||
return ({
|
||||
label: sym.name,
|
||||
description,
|
||||
ariaLabel: uri.toString(),
|
||||
iconClasses,
|
||||
highlights: {
|
||||
label: findMatches(sym.name, filter),
|
||||
description: findMatches(description, filter)
|
||||
},
|
||||
execute: () => {
|
||||
if (provider.resolveWorkspaceSymbol) {
|
||||
provider.resolveWorkspaceSymbol(sym, token).then(resolvedSymbol => {
|
||||
if (resolvedSymbol) {
|
||||
this.openURL(uri, resolvedSymbol.location.range.start, resolvedSymbol.location.range.end);
|
||||
} else {
|
||||
// the symbol didn't resolve -> use given symbol
|
||||
this.openURL(uri, sym.location.range.start, sym.location.range.end);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// resolveWorkspaceSymbol wasn't specified
|
||||
this.openURL(uri, sym.location.range.start, sym.location.range.end);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected toCssClassName(symbolKind: SymbolKind, inline?: boolean): string[] | undefined {
|
||||
const kind = SymbolKind[symbolKind];
|
||||
if (!kind) {
|
||||
return undefined;
|
||||
}
|
||||
return ['codicon', `${inline ? 'inline' : 'block'}`, `codicon-symbol-${kind.toLowerCase() || 'property'}`];
|
||||
}
|
||||
|
||||
private openURL(uri: URI, start: Position, end: Position): void {
|
||||
this.openerService.getOpener(uri).then(opener => opener.open(uri, {
|
||||
selection: Range.create(start, end)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
enum SymbolKind {
|
||||
File = 1,
|
||||
Module = 2,
|
||||
Namespace = 3,
|
||||
Package = 4,
|
||||
Class = 5,
|
||||
Method = 6,
|
||||
Property = 7,
|
||||
Field = 8,
|
||||
Constructor = 9,
|
||||
Enum = 10,
|
||||
Interface = 11,
|
||||
Function = 12,
|
||||
Variable = 13,
|
||||
Constant = 14,
|
||||
String = 15,
|
||||
Number = 16,
|
||||
Boolean = 17,
|
||||
Array = 18,
|
||||
Object = 19,
|
||||
Key = 20,
|
||||
Null = 21,
|
||||
EnumMember = 22,
|
||||
Struct = 23,
|
||||
Event = 24,
|
||||
Operator = 25,
|
||||
TypeParameter = 26
|
||||
}
|
||||
28
packages/monaco/src/package.spec.ts
Normal file
28
packages/monaco/src/package.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2017 Ericsson and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
/* note: this bogus test file is required so that
|
||||
we are able to run mocha unit tests on this
|
||||
package, without having any actual unit tests in it.
|
||||
This way a coverage report will be generated,
|
||||
showing 0% coverage, instead of no report.
|
||||
This file can be removed once we have real unit
|
||||
tests in place. */
|
||||
|
||||
describe('monaco package', () => {
|
||||
|
||||
it('support code coverage statistics', () => true);
|
||||
});
|
||||
31
packages/monaco/tsconfig.json
Normal file
31
packages/monaco/tsconfig.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"extends": "../../configs/base.tsconfig",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../core"
|
||||
},
|
||||
{
|
||||
"path": "../editor"
|
||||
},
|
||||
{
|
||||
"path": "../filesystem"
|
||||
},
|
||||
{
|
||||
"path": "../markers"
|
||||
},
|
||||
{
|
||||
"path": "../outline-view"
|
||||
},
|
||||
{
|
||||
"path": "../workspace"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user