feat: Support authoring features (#977)

* feat: Initialize Language Server (#959)

* feat: Support compiling and pushing diagnostics (#962)

* feat: Support basic groovy syntax highlighting (#960)

* feat: Support semantic highlighting (#967)

* feat: Support document outline (#969)

* feat: Support auto completion in dependencies closure (#970)

* feat: Support Basic Auto Completion (#971)

* fix: Add null check for visitors (#974)

* feat: Show symbol detail for method calls (#973)

* chore: Prepare for 3.8.0 (#978)

* fix: Use padding to correct version order (#986)

* fix: Dependency completion doesn't work when multiple dependencies closures exist (#984)

* fix: Correct version completion kind (#985)

* fix: Handle multiple content changes (#992)

* feat: Support completion for settings.gradle (#988)

* fix: Offer completion results from supertypes (#987)

* feat: Provide dependencies content in outline (#998)

* feat: Support basic java plugin aware (#989)

* feat: Support basic tasks and dependencies (#1002)

* chore: Add thirdpartynotice

* chore: Fix ci
This commit is contained in:
Shi Chen 2021-09-26 10:08:22 +08:00 коммит произвёл GitHub
Родитель 8750a4d929
Коммит 12c59040b4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
53 изменённых файлов: 5207 добавлений и 936 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -27,3 +27,6 @@ bin/
# Ignore Gradle build output directory
build
# Jar files
*.jar

31
.vscode/launch.json поставляемый
Просмотреть файл

@ -41,6 +41,37 @@
"hidden": true
}
},
{
"name": "Debug Language Server: Launch Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceFolder}/extension"],
"outFiles": ["${workspaceFolder}/extension/dist/**/*.js"],
"preLaunchTask": "Gradle: Build",
"env": {
"VSCODE_DEBUG_LANGUAGE_SERVER": "true",
"VSCODE_GRADLE_PORT": "6006"
},
"presentation": {
"group": "debug",
"order": 4
}
},
{
"type": "java",
"name": "Debug Language Server: Launch Language Server",
"request": "launch",
"mainClass": "com.microsoft.gradle.GradleLanguageServer",
"projectName": "gradle-language-server",
"env": {
"VSCODE_GRADLE_PORT": "6006"
},
"presentation": {
"group": "debug",
"order": 5
}
},
{
"name": "Test: Groovy Default",
"type": "extensionHost",

Просмотреть файл

@ -4,18 +4,37 @@ All notable changes to the "vscode-gradle" extension will be documented in this
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## 3.8.0
### Added
- Support highlighting of Gradle file. [PR#960](https://github.com/microsoft/vscode-gradle/pull/960), [PR#967](https://github.com/microsoft/vscode-gradle/pull/967)
- Provide document outline of Gradle file. [PR#969](https://github.com/microsoft/vscode-gradle/pull/969)
- Show syntax diagnostics of Gradle file. [PR#962](https://github.com/microsoft/vscode-gradle/pull/962)
- Support auto completion for dependencies. [PR#970](https://github.com/microsoft/vscode-gradle/pull/970)
- Support auto completion for basic Gradle closures. [PR#971](https://github.com/microsoft/vscode-gradle/pull/971)
- Support basic projects view. [PR#1002](https://github.com/microsoft/vscode-gradle/pull/1002)
### Changed
- Upgrade vscode requirement to `1.60.0`. [PR#997](https://github.com/microsoft/vscode-gradle/pull/997)
- Adopt the new `folder-library` icon. [PR#997](https://github.com/microsoft/vscode-gradle/pull/997)
### Fixed
- [Bugs fixed](https://github.com/microsoft/vscode-gradle/issues?q=is%3Aissue+label%3Abug+milestone%3A3.8.0+is%3Aclosed)
## 3.7.1
### Fixed
- Fix the `Details` and `Changelog` tabs in the marketplace page. [PR#1012](https://github.com/microsoft/vscode-gradle/pull/1012)
## 3.7.0
### Added
- Support dependency view. [PR#887](https://github.com/microsoft/vscode-gradle/pull/887)
- Support local Gradle installation. [PR#926](https://github.com/microsoft/vscode-gradle/pull/926)
### Changed
- Rename `Gradle Tasks` view to `Gradle Projects` view.
- Hide `STOPPED` daemons in Gradle Daemons view by default. [PR#940](https://github.com/microsoft/vscode-gradle/pull/940)
- Refine UX when there is no item in pinned tasks and recent tasks view. [PR#937](https://github.com/microsoft/vscode-gradle/pull/937)
### Fixed
- [Bugs fixed](https://github.com/microsoft/vscode-gradle/issues?q=is%3Aissue+label%3Abug+milestone%3A3.7.0+is%3Aclosed)

Просмотреть файл

@ -0,0 +1,31 @@
{
"comments": {
"lineComment": "//",
"blockComment": ["/*", "*/"]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"],
{
"open": "/**",
"close": " */",
"notIn": ["string"]
}
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"],
["<", ">"]
]
}

1541
extension/package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -2,7 +2,7 @@
"name": "vscode-gradle",
"displayName": "Gradle Tasks",
"description": "Run Gradle tasks in VS Code",
"version": "3.7.1",
"version": "3.8.0",
"private": true,
"publisher": "vscjava",
"aiKey": "b4aae7d0-c65b-4819-92bf-1d2f537ae7ce",
@ -37,6 +37,25 @@
],
"main": "./dist/index.js",
"contributes": {
"languages": [
{
"id": "gradle",
"extensions": [
".gradle"
],
"aliases": [
"Gradle"
],
"configuration": "./gradle-language-configuration.json"
}
],
"grammars": [
{
"language": "gradle",
"scopeName": "source.groovy",
"path": "./syntaxes/groovy.tmLanguage.json"
}
],
"problemMatchers": [
{
"owner": "gradle",
@ -57,7 +76,12 @@
{
"id": "gradleTasksView",
"name": "Gradle Projects",
"when": "gradle:activated"
"when": "gradle:activated && !gradle:defaultView"
},
{
"id": "gradleDefaultProjectsView",
"name": "Gradle Projects",
"when": "gradle:activated && gradle:defaultView"
},
{
"id": "pinnedTasksView",
@ -437,7 +461,7 @@
},
{
"command": "gradle.openSettings",
"when": "view == gradleTasksView"
"when": "view == gradleTasksView || view == gradleDefaultProjectsView"
},
{
"command": "gradle.findTask",
@ -445,7 +469,7 @@
},
{
"command": "gradle.runBuild",
"when": "view == gradleTasksView",
"when": "view == gradleTasksView || view == gradleDefaultProjectsView",
"group": "navigation@0"
},
{
@ -497,12 +521,12 @@
"view/item/context": [
{
"command": "gradle.runTask",
"when": "view =~ /^gradleTasksView$|^pinnedTasksView$|^recentTasksView$/ && viewItem =~ /^debugTask.*$|^task.*$/",
"when": "view =~ /^gradleTasksView$|^pinnedTasksView$|^recentTasksView$|^gradleDefaultProjectsView$/ && viewItem =~ /^debugTask.*$|^task.*$/",
"group": "contextGroup1@0"
},
{
"command": "gradle.runTaskWithArgs",
"when": "view =~ /^gradleTasksView$|^pinnedTasksView$|^recentTasksView$/ && viewItem =~ /^debugTask(WithTerminals)?$|^task(WithTerminals)?$/",
"when": "view =~ /^gradleTasksView$|^pinnedTasksView$|^recentTasksView$|^gradleDefaultProjectsView$/ && viewItem =~ /^debugTask(WithTerminals)?$|^task(WithTerminals)?$/",
"group": "contextGroup1@1"
},
{
@ -532,7 +556,7 @@
},
{
"command": "gradle.runTask",
"when": "view =~ /^gradleTasksView$|^pinnedTasksView$|^recentTasksView$/ && viewItem =~ /^debugTask.*$|^task.*$/",
"when": "view =~ /^gradleTasksView$|^pinnedTasksView$|^recentTasksView$|^gradleDefaultProjectsView$/ && viewItem =~ /^debugTask.*$|^task.*$/",
"group": "inline@3"
},
{
@ -823,18 +847,19 @@
"mocha": "^8.3.1",
"prettier": "^2.2.1",
"sinon": "^9.2.4",
"snyk": "^1.695.0",
"snyk": "^1.697.0",
"string-argv": "^0.3.1",
"tree-kill": "^1.2.2",
"ts-loader": "^4.4.2",
"ts-protoc-gen": "^0.14.0",
"typescript": "^4.2.3",
"typescript": "4.2.3",
"vsce": "^1.88.0",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12"
},
"snyk": true,
"dependencies": {
"vscode-extension-telemetry-wrapper": "^0.9.0"
"vscode-extension-telemetry-wrapper": "^0.9.0",
"vscode-languageclient": "7.0.0"
}
}

Просмотреть файл

@ -26,6 +26,7 @@ import {
PINNED_TASKS_VIEW,
GRADLE_DAEMONS_VIEW,
RECENT_TASKS_VIEW,
GRADLE_DEFAULT_PROJECTS_VIEW,
} from './views/constants';
import { focusTaskInGradleTasksTree } from './views/viewUtil';
import { COMMAND_RENDER_TASK, COMMAND_REFRESH } from './commands';
@ -39,6 +40,8 @@ import { DependencyTreeItem } from './views/gradleTasks/DependencyTreeItem';
import { GRADLE_DEPENDENCY_REVEAL } from './views/gradleTasks/DependencyUtils';
import { GradleDependencyProvider } from './dependencies/GradleDependencyProvider';
import { getSpecificVersionStatus } from './views/gradleDaemons/util';
import { startLanguageServer } from './languageServer/languageServer';
import { DefaultProjectsTreeDataProvider } from './views/defaultProject/DefaultProjectsTreeDataProvider';
export class Extension {
private readonly client: GradleClient;
@ -62,11 +65,14 @@ export class Extension {
private readonly recentTasksTreeDataProvider: RecentTasksTreeDataProvider;
private readonly recentTasksTreeView: vscode.TreeView<vscode.TreeItem>;
private readonly gradleTasksTreeDataProvider: GradleTasksTreeDataProvider;
private readonly defaultProjectsTreeView: vscode.TreeView<vscode.TreeItem>;
private readonly defaultProjectsTreeDataProvider: DefaultProjectsTreeDataProvider;
private readonly api: Api;
private readonly commands: Commands;
private readonly _onDidTerminalOpen: vscode.EventEmitter<vscode.Terminal> = new vscode.EventEmitter<vscode.Terminal>();
private readonly onDidTerminalOpen: vscode.Event<vscode.Terminal> = this
._onDidTerminalOpen.event;
private readonly _onDidTerminalOpen: vscode.EventEmitter<vscode.Terminal> =
new vscode.EventEmitter<vscode.Terminal>();
private readonly onDidTerminalOpen: vscode.Event<vscode.Terminal> =
this._onDidTerminalOpen.event;
private recentTerminal: vscode.Terminal | undefined;
public constructor(private readonly context: vscode.ExtensionContext) {
@ -152,6 +158,19 @@ export class Extension {
treeDataProvider: this.recentTasksTreeDataProvider,
showCollapseAll: false,
});
this.defaultProjectsTreeDataProvider = new DefaultProjectsTreeDataProvider(
this.gradleTaskProvider,
this.rootProjectsStore,
this.client,
this.icons
);
this.defaultProjectsTreeView = vscode.window.createTreeView(
GRADLE_DEFAULT_PROJECTS_VIEW,
{
treeDataProvider: this.defaultProjectsTreeDataProvider,
showCollapseAll: false,
}
);
this.gradleTaskManager = new GradleTaskManager(context);
this.buildFileWatcher = new FileWatcher('**/*.{gradle,gradle.kts}');
@ -198,6 +217,7 @@ export class Extension {
);
void this.activate();
void startLanguageServer(this.context);
}
private storeSubscriptions(): void {
@ -215,7 +235,8 @@ export class Extension {
this.gradleDaemonsTreeView,
this.gradleTasksTreeView,
this.pinnedTasksTreeView,
this.recentTasksTreeView
this.recentTasksTreeView,
this.defaultProjectsTreeView
);
}
@ -229,6 +250,11 @@ export class Extension {
'gradle:activated',
activated
);
await vscode.commands.executeCommand(
'setContext',
'gradle:defaultView',
true
);
}
private registerCommands(): void {

Просмотреть файл

@ -46,6 +46,10 @@ import {
} from './CancellationKeys';
import { EventWaiter } from '../util/EventWaiter';
import { getGradleConfig, getConfigJavaDebug } from '../util/config';
import {
setDefault,
unsetDefault,
} from '../views/defaultProject/DefaultProjectUtils';
function logBuildEnvironment(environment: Environment): void {
const javaEnv = environment.getJavaEnvironment()!;
@ -59,11 +63,13 @@ function logBuildEnvironment(environment: Environment): void {
export class GradleClient implements vscode.Disposable {
private readonly connectDeadline = 30; // seconds
private grpcClient: GrpcClient | null = null;
private readonly _onDidConnect: vscode.EventEmitter<null> = new vscode.EventEmitter<null>();
private readonly _onDidConnectFail: vscode.EventEmitter<null> = new vscode.EventEmitter<null>();
private readonly _onDidConnect: vscode.EventEmitter<null> =
new vscode.EventEmitter<null>();
private readonly _onDidConnectFail: vscode.EventEmitter<null> =
new vscode.EventEmitter<null>();
public readonly onDidConnect: vscode.Event<null> = this._onDidConnect.event;
public readonly onDidConnectFail: vscode.Event<null> = this._onDidConnectFail
.event;
public readonly onDidConnectFail: vscode.Event<null> =
this._onDidConnectFail.event;
private readonly waitForConnect = new EventWaiter(this.onDidConnect).wait;
@ -189,6 +195,7 @@ export class GradleClient implements vscode.Disposable {
);
break;
case Output.OutputType.STDERR:
void setDefault();
stdErrLoggerStream.write(
getBuildReply.getOutput()!.getOutputBytes_asU8()
);
@ -199,6 +206,7 @@ export class GradleClient implements vscode.Disposable {
this.handleGetBuildCancelled(getBuildReply.getCancelled()!);
break;
case GetBuildReply.KindCase.GET_BUILD_RESULT:
void unsetDefault();
build = getBuildReply.getGetBuildResult()!.getBuild();
break;
case GetBuildReply.KindCase.ENVIRONMENT:
@ -625,9 +633,8 @@ export class GradleClient implements vscode.Disposable {
this.close();
this._onDidConnectFail.fire(null);
if (this.server.isReady()) {
const connectivityState = this.grpcClient!.getChannel().getConnectivityState(
true
);
const connectivityState =
this.grpcClient!.getChannel().getConnectivityState(true);
const enumKey = ConnectivityState[connectivityState];
logger.error('The client has state:', enumKey);
await this.showRestartMessage();

Просмотреть файл

@ -22,7 +22,8 @@ export class StopDaemonsCommand extends Command {
) {
return;
}
const gradleRootFolders = await this.rootProjectsStore.getProjectRootsWithUniqueVersions();
const gradleRootFolders =
await this.rootProjectsStore.getProjectRootsWithUniqueVersions();
const promises: Promise<StopDaemonsReply | void>[] = gradleRootFolders.map(
(rootProject) =>
this.client.stopDaemons(rootProject.getProjectUri().fsPath)

Просмотреть файл

@ -0,0 +1,138 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as net from 'net';
import * as path from 'path';
import * as vscode from 'vscode';
import {
DidChangeConfigurationNotification,
LanguageClientOptions,
} from 'vscode-languageclient';
import { LanguageClient, StreamInfo } from 'vscode-languageclient/node';
import {
getConfigGradleJavaHome,
getConfigJavaImportGradleHome,
getConfigJavaImportGradleUserHome,
getConfigJavaImportGradleVersion,
getConfigJavaImportGradleWrapperEnabled,
} from '../util/config';
const CHANNEL_NAME = 'Gradle Language Server';
export let isLanguageServerStarted = false;
export async function startLanguageServer(
context: vscode.ExtensionContext
): Promise<void> {
void vscode.window.withProgress(
{ location: vscode.ProgressLocation.Window },
// eslint-disable-next-line sonarjs/cognitive-complexity
(progress) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
return new Promise<void>(async (resolve, _reject) => {
progress.report({
message: 'Initializing Gradle Language Server',
});
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'gradle' }],
outputChannel: vscode.window.createOutputChannel(CHANNEL_NAME),
outputChannelName: CHANNEL_NAME,
initializationOptions: {
settings: getGradleSettings(),
},
};
let serverOptions;
if (process.env.VSCODE_DEBUG_LANGUAGE_SERVER === 'true') {
// debug mode
const port = process.env.VSCODE_GRADLE_PORT;
if (!port) {
void vscode.window.showErrorMessage(
'VSCODE_GRADLE_PORT is invalid, please check it in launch.json.'
);
return;
}
serverOptions = awaitServerConnection.bind(null, port);
} else {
// keep consistent with gRPC server
const javaHome = getConfigGradleJavaHome() || process.env.JAVA_HOME;
if (!javaHome) {
void vscode.window
.showErrorMessage(
'There is no valid JAVA_HOME setting to launch Gradle Language Server. Please check your "java.home" setting.',
'Open Settings'
)
.then((answer) => {
if (answer === 'Open Settings') {
void vscode.commands.executeCommand(
'workbench.action.openSettings',
'java.home'
);
}
});
return;
}
const args = [
'-jar',
path.resolve(
context.extensionPath,
'lib',
'gradle-language-server.jar'
),
];
serverOptions = {
command: path.join(javaHome, 'bin', 'java'),
args: args,
};
}
const languageClient = new LanguageClient(
'gradle',
'Gradle Language Server',
serverOptions,
clientOptions
);
languageClient.onReady().then(resolve, (e) => {
resolve();
isLanguageServerStarted = true;
void vscode.window.showErrorMessage(e);
});
const disposable = languageClient.start();
context.subscriptions.push(disposable);
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration((e) => {
if (e.affectsConfiguration('java.import.gradle')) {
languageClient.sendNotification(
DidChangeConfigurationNotification.type,
{ settings: getGradleSettings() }
);
}
})
);
});
}
);
}
async function awaitServerConnection(port: string): Promise<StreamInfo> {
const addr = parseInt(port);
return new Promise((resolve, reject) => {
const server = net.createServer((stream) => {
server.close();
resolve({ reader: stream, writer: stream });
});
server.on('error', reject);
server.listen(addr, () => {
server.removeListener('error', reject);
});
return server;
});
}
function getGradleSettings(): unknown {
return {
gradleHome: getConfigJavaImportGradleHome(),
gradleVersion: getConfigJavaImportGradleVersion(),
gradleWrapperEnabled: getConfigJavaImportGradleWrapperEnabled(),
gradleUserHome: getConfigJavaImportGradleUserHome(),
};
}

Просмотреть файл

@ -1,9 +1,10 @@
import * as vscode from 'vscode';
export class ProgressHandler {
private readonly _onDidProgressStart: vscode.EventEmitter<null> = new vscode.EventEmitter<null>();
public readonly onDidProgressStart: vscode.Event<null> = this
._onDidProgressStart.event;
private readonly _onDidProgressStart: vscode.EventEmitter<null> =
new vscode.EventEmitter<null>();
public readonly onDidProgressStart: vscode.Event<null> =
this._onDidProgressStart.event;
public constructor(
private readonly progress: vscode.Progress<{ message?: string }>,

Просмотреть файл

@ -15,8 +15,10 @@ export interface ServerOptions {
}
export class GradleServer implements vscode.Disposable {
private readonly _onDidStart: vscode.EventEmitter<null> = new vscode.EventEmitter<null>();
private readonly _onDidStop: vscode.EventEmitter<null> = new vscode.EventEmitter<null>();
private readonly _onDidStart: vscode.EventEmitter<null> =
new vscode.EventEmitter<null>();
private readonly _onDidStop: vscode.EventEmitter<null> =
new vscode.EventEmitter<null>();
private ready = false;
private port: number | undefined;
private restarting = false;

Просмотреть файл

@ -2,7 +2,8 @@ import * as vscode from 'vscode';
import { debounce } from '../util/decorators';
export abstract class EventedStore<V> implements vscode.Disposable {
private readonly _onDidChange: vscode.EventEmitter<V | null> = new vscode.EventEmitter<V>();
private readonly _onDidChange: vscode.EventEmitter<V | null> =
new vscode.EventEmitter<V>();
public readonly onDidChange: vscode.Event<V | null> = this._onDidChange.event;
@debounce(0)

Просмотреть файл

@ -50,11 +50,12 @@ export class RootProjectsStore extends StoreMap<string, RootProject> {
for (const workspaceFolder of workspaceFolders) {
const configNestedFolders = getNestedProjectsConfig(workspaceFolder);
const gradleProjectFoldersOutsideRoot = getGradleProjectFoldersOutsideRoot(
configNestedFolders,
gradleProjectFolders,
workspaceFolder
);
const gradleProjectFoldersOutsideRoot =
getGradleProjectFoldersOutsideRoot(
configNestedFolders,
gradleProjectFolders,
workspaceFolder
);
if (gradleProjectFolders.includes(workspaceFolder.uri.fsPath)) {
this.setRootProjectFolder(buildRootFolder(workspaceFolder.uri));
}

Просмотреть файл

@ -2,16 +2,19 @@ import * as vscode from 'vscode';
import { isGradleTask, getRunningGradleTasks } from './taskUtil';
export class GradleTaskManager implements vscode.Disposable {
private readonly _onDidStartTask: vscode.EventEmitter<vscode.Task> = new vscode.EventEmitter<vscode.Task>();
private readonly _onDidEndTask: vscode.EventEmitter<vscode.Task> = new vscode.EventEmitter<vscode.Task>();
private readonly _onDidEndAllTasks: vscode.EventEmitter<null> = new vscode.EventEmitter<null>();
private readonly _onDidStartTask: vscode.EventEmitter<vscode.Task> =
new vscode.EventEmitter<vscode.Task>();
private readonly _onDidEndTask: vscode.EventEmitter<vscode.Task> =
new vscode.EventEmitter<vscode.Task>();
private readonly _onDidEndAllTasks: vscode.EventEmitter<null> =
new vscode.EventEmitter<null>();
public readonly onDidStartTask: vscode.Event<vscode.Task> = this
._onDidStartTask.event;
public readonly onDidEndTask: vscode.Event<vscode.Task> = this._onDidEndTask
.event;
public readonly onDidEndAllTasks: vscode.Event<null> = this._onDidEndAllTasks
.event;
public readonly onDidStartTask: vscode.Event<vscode.Task> =
this._onDidStartTask.event;
public readonly onDidEndTask: vscode.Event<vscode.Task> =
this._onDidEndTask.event;
public readonly onDidEndAllTasks: vscode.Event<null> =
this._onDidEndAllTasks.event;
constructor(private readonly context: vscode.ExtensionContext) {
this.context.subscriptions.push(

Просмотреть файл

@ -10,25 +10,27 @@ import { getConfigJavaDebug } from '../util/config';
import { EventWaiter } from '../util/EventWaiter';
export class GradleTaskProvider
implements vscode.TaskProvider, vscode.Disposable {
implements vscode.TaskProvider, vscode.Disposable
{
private cachedTasks: vscode.Task[] = [];
private readonly _onDidLoadTasks: vscode.EventEmitter<
vscode.Task[]
> = new vscode.EventEmitter<vscode.Task[]>();
private readonly _onDidStartRefresh: vscode.EventEmitter<null> = new vscode.EventEmitter<null>();
private readonly _onDidStopRefresh: vscode.EventEmitter<null> = new vscode.EventEmitter<null>();
private readonly _onDidLoadTasks: vscode.EventEmitter<vscode.Task[]> =
new vscode.EventEmitter<vscode.Task[]>();
private readonly _onDidStartRefresh: vscode.EventEmitter<null> =
new vscode.EventEmitter<null>();
private readonly _onDidStopRefresh: vscode.EventEmitter<null> =
new vscode.EventEmitter<null>();
constructor(
private readonly rootProjectsStore: RootProjectsStore,
private readonly client: GradleClient
) {}
public readonly onDidLoadTasks: vscode.Event<vscode.Task[]> = this
._onDidLoadTasks.event;
public readonly onDidStartRefresh: vscode.Event<null> = this
._onDidStartRefresh.event;
public readonly onDidStopRefresh: vscode.Event<null> = this._onDidStopRefresh
.event;
public readonly onDidLoadTasks: vscode.Event<vscode.Task[]> =
this._onDidLoadTasks.event;
public readonly onDidStartRefresh: vscode.Event<null> =
this._onDidStartRefresh.event;
public readonly onDidStopRefresh: vscode.Event<null> =
this._onDidStopRefresh.event;
private loadTasksPromise?: Promise<vscode.Task[]>;
private readonly _waitForTasksLoad = new EventWaiter<vscode.Task[]>(

Просмотреть файл

@ -100,7 +100,8 @@ describe(getSuiteName('Gradle daemons'), () => {
});
it('should filter out projects with duplicate gradle versions', async () => {
const projects = await rootProjectsStore.getProjectRootsWithUniqueVersions();
const projects =
await rootProjectsStore.getProjectRootsWithUniqueVersions();
assert.strictEqual(
projects.length,
2,
@ -230,10 +231,9 @@ describe(getSuiteName('Gradle daemons'), () => {
mockReply.setMessage('Stopped');
mockClient.stopDaemon.resolves(mockReply);
const showWarningMessageStub = (sinon.stub(
vscode.window,
'showWarningMessage'
) as SinonStub).resolves('Yes');
const showWarningMessageStub = (
sinon.stub(vscode.window, 'showWarningMessage') as SinonStub
).resolves('Yes');
const mockDaemonInfoBusy = new DaemonInfo();
mockDaemonInfoBusy.setStatus(DaemonInfo.DaemonStatus.BUSY);
@ -280,10 +280,9 @@ describe(getSuiteName('Gradle daemons'), () => {
.withArgs(mockWorkspaceFolder2.uri.fsPath)
.resolves(mockReply2);
const showWarningMessageStub = (sinon.stub(
vscode.window,
'showWarningMessage'
) as SinonStub).resolves('Yes');
const showWarningMessageStub = (
sinon.stub(vscode.window, 'showWarningMessage') as SinonStub
).resolves('Yes');
await new StopDaemonsCommand(mockClient, rootProjectsStore).run();

Просмотреть файл

@ -336,7 +336,8 @@ describe(getSuiteName('Gradle tasks'), () => {
task,
},
]);
const gradleProjects = (await gradleTasksTreeDataProvider.getChildren()) as ProjectTreeItem[];
const gradleProjects =
(await gradleTasksTreeDataProvider.getChildren()) as ProjectTreeItem[];
removeCancellingTask(task);
const group = gradleProjects[0].groups[0];
const taskItem = group.tasks[0];
@ -383,7 +384,8 @@ describe(getSuiteName('Gradle tasks'), () => {
executeCommandStub.calledWith(COMMAND_RENDER_TASK, task),
'Task was not rendered'
);
const gradleProjects = (await gradleTasksTreeDataProvider.getChildren()) as ProjectTreeItem[];
const gradleProjects =
(await gradleTasksTreeDataProvider.getChildren()) as ProjectTreeItem[];
removeCancellingTask(task);
const group = gradleProjects[0].groups[0];
const taskItem = group.tasks[0];
@ -430,7 +432,8 @@ describe(getSuiteName('Gradle tasks'), () => {
});
it('should build root RootProject items at top level', async () => {
const rootProjectItems = await gradleTasksTreeDataProvider.getChildren();
const rootProjectItems =
await gradleTasksTreeDataProvider.getChildren();
assert.ok(rootProjectItems.length > 0, 'No root gradle projects found');
assert.strictEqual(
rootProjectItems.length,

Просмотреть файл

@ -348,10 +348,9 @@ describe(getSuiteName('Pinned tasks'), () => {
childrenBefore[1] instanceof PinnedTaskTreeItem,
'Pinned task is not PinnedTaskTreeItem'
);
const showWarningMessageStub = (sinon.stub(
vscode.window,
'showWarningMessage'
) as SinonStub).resolves('Yes');
const showWarningMessageStub = (
sinon.stub(vscode.window, 'showWarningMessage') as SinonStub
).resolves('Yes');
await new ClearAllPinnedTasksCommand(pinnedTasksStore).run();
assert.ok(
showWarningMessageStub.calledWith(
@ -379,7 +378,8 @@ describe(getSuiteName('Pinned tasks'), () => {
describe('With pinned tasks', () => {
it('should build a pinned task treeitem', async () => {
const workspaceTreeItems = await gradleTasksTreeDataProvider.getChildren();
const workspaceTreeItems =
await gradleTasksTreeDataProvider.getChildren();
assert.ok(workspaceTreeItems.length > 0, 'No gradle projects found');
const workspaceTreeItem = workspaceTreeItems[0] as RootProjectTreeItem;
assert.ok(
@ -404,7 +404,8 @@ describe(getSuiteName('Pinned tasks'), () => {
await new PinTaskCommand(pinnedTasksTreeDataProvider).run(taskItem);
const children = await pinnedTasksTreeDataProvider.getChildren();
assert.strictEqual(children.length, 1);
const pinnedTasksRootProjectTreeItem = children[0] as PinnedTasksRootProjectTreeItem;
const pinnedTasksRootProjectTreeItem =
children[0] as PinnedTasksRootProjectTreeItem;
assert.strictEqual(
pinnedTasksRootProjectTreeItem.collapsibleState,
vscode.TreeItemCollapsibleState.Expanded

Просмотреть файл

@ -171,7 +171,8 @@ describe(getSuiteName('Recent tasks'), () => {
path.join('resources', 'light', ICON_GRADLE_TASK)
);
const workspaceTreeItem = recentTaskTreeItem.parentTreeItem as RecentTasksRootProjectTreeItem;
const workspaceTreeItem =
recentTaskTreeItem.parentTreeItem as RecentTasksRootProjectTreeItem;
assert.ok(
workspaceTreeItem,
'parentTreeItem must reference a WorkSpace tree item'
@ -229,7 +230,8 @@ describe(getSuiteName('Recent tasks'), () => {
path.join('resources', 'light', ICON_GRADLE_TASK)
);
const workspaceTreeItem = recentTaskTreeItem.parentTreeItem as RecentTasksRootProjectTreeItem;
const workspaceTreeItem =
recentTaskTreeItem.parentTreeItem as RecentTasksRootProjectTreeItem;
assert.ok(
workspaceTreeItem instanceof RecentTasksRootProjectTreeItem,
'Tree item is not RecentTasksRootProjectTreeItem'
@ -247,10 +249,9 @@ describe(getSuiteName('Recent tasks'), () => {
'Task is not a RecentTaskTreeItem'
);
const showWarningMessageStub = (sinon.stub(
vscode.window,
'showWarningMessage'
) as SinonStub).resolves('Yes');
const showWarningMessageStub = (
sinon.stub(vscode.window, 'showWarningMessage') as SinonStub
).resolves('Yes');
await new ClearAllRecentTasksCommand(recentTasksStore).run();
@ -367,10 +368,9 @@ describe(getSuiteName('Recent tasks'), () => {
);
assert.strictEqual(recentTaskTreeItemBefore.description, '(2)');
const showWarningMessageStub = (sinon.stub(
vscode.window,
'showWarningMessage'
) as SinonStub).resolves('Yes');
const showWarningMessageStub = (
sinon.stub(vscode.window, 'showWarningMessage') as SinonStub
).resolves('Yes');
await new CloseAllTaskTerminalsCommand(taskTerminalsStore).run();

Просмотреть файл

@ -2,9 +2,10 @@ import * as vscode from 'vscode';
import * as minimatch from 'minimatch';
export class FileWatcher implements vscode.Disposable {
private readonly _onDidChange: vscode.EventEmitter<vscode.Uri> = new vscode.EventEmitter<vscode.Uri>();
public readonly onDidChange: vscode.Event<vscode.Uri> = this._onDidChange
.event;
private readonly _onDidChange: vscode.EventEmitter<vscode.Uri> =
new vscode.EventEmitter<vscode.Uri>();
public readonly onDidChange: vscode.Event<vscode.Uri> =
this._onDidChange.event;
private onDidSaveDisposable: vscode.Disposable;
private onDidDeleteDisposable: vscode.Disposable;
private onDidCreateDisposable: vscode.Disposable;

Просмотреть файл

@ -4,16 +4,14 @@ export const JAVA_LANGUAGE_EXTENSION_ID = 'redhat.java';
export const JAVA_DEBUGGER_EXTENSION_ID = 'vscjava.vscode-java-debug';
export function isJavaLanguageSupportExtensionActivated(): boolean {
const javaExt:
| vscode.Extension<unknown>
| undefined = getJavaLanguageSupportExtension();
const javaExt: vscode.Extension<unknown> | undefined =
getJavaLanguageSupportExtension();
return javaExt?.isActive || false;
}
export function isJavaDebuggerExtensionActivated(): boolean {
const javaExt:
| vscode.Extension<unknown>
| undefined = getJavaDebuggerExtension();
const javaExt: vscode.Extension<unknown> | undefined =
getJavaDebuggerExtension();
return javaExt?.isActive || false;
}

Просмотреть файл

@ -8,6 +8,7 @@ export const ICON_DAEMON_STOPPED = 'close.svg';
export const GRADLE_CONTAINER_VIEW = 'gradleContainerView';
export const GRADLE_TASKS_VIEW = 'gradleTasksView';
export const GRADLE_DEFAULT_PROJECTS_VIEW = 'gradleDefaultProjectsView';
export const GRADLE_DAEMONS_VIEW = 'gradleDaemonsView';
export const PINNED_TASKS_VIEW = 'pinnedTasksView';
export const RECENT_TASKS_VIEW = 'recentTasksView';

Просмотреть файл

@ -0,0 +1,142 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as path from 'path';
import * as vscode from 'vscode';
import { GradleClient } from '../../client';
import { isLanguageServerStarted } from '../../languageServer/languageServer';
import { RootProject } from '../../rootProject';
import { GradleTaskDefinition } from '../../tasks';
import { buildTaskId, createTaskFromDefinition } from '../../tasks/taskUtil';
import { DependencyConfigurationTreeItem } from '../gradleTasks/DependencyConfigurationTreeItem';
import { DependencyTreeItem } from '../gradleTasks/DependencyTreeItem';
import { HintItem } from '../gradleTasks/HintItem';
import { ProjectDependencyTreeItem } from '../gradleTasks/ProjectDependencyTreeItem';
import { generateDefaultTaskDefinitions } from './DefaultProjectUtils';
import { DefaultDependencyItem, DefaultTaskDefinition } from './types';
export class DefaultProjectProvider {
private static defaultTaskDefinitions: DefaultTaskDefinition[] =
generateDefaultTaskDefinitions();
constructor() {
this.defaultTasks = [];
this.defaultDependencies = new Map();
}
private defaultTasks: vscode.Task[];
private defaultDependencies: Map<string, vscode.TreeItem[]>;
public refresh(): void {
this.defaultTasks = [];
}
public async getDefaultTasks(
rootProjects: RootProject[],
client: GradleClient
): Promise<vscode.Task[]> {
if (this.defaultTasks.length) {
return this.defaultTasks;
}
const tasks = [];
for (const rootProject of rootProjects) {
for (const defaultTaskDefinition of DefaultProjectProvider.defaultTaskDefinitions) {
tasks.push(
createTaskFromDefinition(
this.generateDefaultTaskDefinition(
rootProject,
defaultTaskDefinition.name,
rootProject.getWorkspaceFolder().name,
path.join(rootProject.getProjectUri().fsPath, 'build.gradle'),
defaultTaskDefinition.description,
defaultTaskDefinition.group
),
rootProject,
client
)
);
}
}
this.defaultTasks = tasks;
return tasks;
}
private generateDefaultTaskDefinition(
rootProject: RootProject,
script: string,
projectName: string,
projectFile: string,
description: string,
group?: string
): Required<GradleTaskDefinition> {
return {
type: 'gradle',
id: buildTaskId(rootProject.getProjectUri().fsPath, script, projectName),
script,
description: description,
group: (group || 'other').toLowerCase(),
project: projectName,
buildFile: projectFile,
rootProject: projectName,
projectFolder: rootProject.getProjectUri().fsPath,
workspaceFolder: rootProject.getWorkspaceFolder().uri.fsPath,
args: '',
javaDebug: false,
};
}
public async getDefaultDependencyItems(
element: ProjectDependencyTreeItem
): Promise<vscode.TreeItem[]> {
const configurationMap: Map<string, DependencyConfigurationTreeItem> =
new Map();
const buildFileUri = vscode.Uri.file(
// Due to no project knowledge, we only get informations from project root's build.gradle
path.join(element.projectPath, 'build.gradle')
);
if (!isLanguageServerStarted) {
return [new HintItem('No dependencies')];
}
const dependencyItems = await vscode.commands.executeCommand<
DefaultDependencyItem[]
>('gradle.getDependencies', buildFileUri.toString());
if (!dependencyItems) {
return [new HintItem('No dependencies')];
}
for (const dependencyItem of dependencyItems) {
const configuration = dependencyItem.configuration;
if (!configurationMap.has(configuration)) {
const configItem: DependencyConfigurationTreeItem =
new DependencyConfigurationTreeItem(
dependencyItem.configuration,
vscode.TreeItemCollapsibleState.Collapsed,
element
);
configurationMap.set(configuration, configItem);
}
const configurationItem = configurationMap.get(configuration);
if (!configurationItem) {
continue;
}
const dependencyTreeItem: DependencyTreeItem = new DependencyTreeItem(
dependencyItem.name,
vscode.TreeItemCollapsibleState.None,
configurationItem
);
dependencyTreeItem.command = {
title: 'Show Dependency',
command: 'vscode.open',
arguments: [
buildFileUri,
<vscode.TextDocumentShowOptions>{ selection: dependencyItem.range },
],
};
configurationItem.getChildren().push(dependencyTreeItem);
}
this.defaultDependencies.set(
buildFileUri.toString(),
Array.from(configurationMap.values())
);
return this.defaultDependencies.get(buildFileUri.toString()) || [];
}
}

Просмотреть файл

@ -0,0 +1,111 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as vscode from 'vscode';
import { DefaultTaskDefinition } from './types';
export async function setDefault(): Promise<void> {
await vscode.commands.executeCommand(
'setContext',
'gradle:defaultView',
true
);
}
export async function unsetDefault(): Promise<void> {
await vscode.commands.executeCommand(
'setContext',
'gradle:defaultView',
false
);
}
export function generateDefaultTaskDefinitions(): DefaultTaskDefinition[] {
const result = [];
result.push(
getDefaultTaskDefinition(
'init',
'build setup',
'Initializes a new Gradle build.'
)
);
result.push(
getDefaultTaskDefinition(
'wrapper',
'build setup',
'Generates Gradle wrapper files.'
)
);
result.push(
getDefaultTaskDefinition(
'buildEnvironment',
'help',
'Displays all buildscript dependencies declared in root project.'
)
);
result.push(
getDefaultTaskDefinition(
'dependencies',
'help',
'Displays all dependencies declared in root project.'
)
);
result.push(
getDefaultTaskDefinition(
'dependencyInsight',
'help',
'Displays the insight into a specific dependency in root project.'
)
);
result.push(
getDefaultTaskDefinition('help', 'help', 'Displays a help message.')
);
result.push(
getDefaultTaskDefinition(
'javaToolchains',
'help',
'Displays the detected java toolchains.'
)
);
result.push(
getDefaultTaskDefinition(
'outgoingVariants',
'help',
'Displays the outgoing variants of root project.'
)
);
result.push(
getDefaultTaskDefinition(
'projects',
'help',
'Displays the sub-projects of root project.'
)
);
result.push(
getDefaultTaskDefinition(
'properties',
'help',
'Displays the properties of root project.'
)
);
result.push(
getDefaultTaskDefinition(
'tasks',
'help',
'Displays the tasks runnable from root project.'
)
);
return result;
}
export function getDefaultTaskDefinition(
name: string,
group: string,
description: string
): DefaultTaskDefinition {
return {
name: name,
group: group,
description: description,
};
}

Просмотреть файл

@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as vscode from 'vscode';
import {
GradleTasksTreeDataProvider,
GroupTreeItem,
ProjectTreeItem,
} from '..';
import { GradleClient } from '../../client';
import { RootProjectsStore } from '../../stores';
import { GradleTaskProvider } from '../../tasks';
import { DefaultProjectProvider } from './DefaultProjectProvider';
import { Icons } from '../../icons';
import { ProjectDependencyTreeItem } from '../gradleTasks/ProjectDependencyTreeItem';
import { ProjectTaskTreeItem } from '../gradleTasks/ProjectTaskTreeItem';
export class DefaultProjectsTreeDataProvider
implements vscode.TreeDataProvider<vscode.TreeItem>
{
private defaultProjectProvider: DefaultProjectProvider;
constructor(
private readonly gradleTaskProvider: GradleTaskProvider,
private readonly rootProjectStore: RootProjectsStore,
private readonly client: GradleClient,
private readonly icons: Icons
) {
this.defaultProjectProvider = new DefaultProjectProvider();
}
public getTreeItem(element: vscode.TreeItem): vscode.TreeItem {
return element;
}
public async getChildren(
element?: vscode.TreeItem
): Promise<vscode.TreeItem[]> {
if (!element) {
// async configuring
void this.gradleTaskProvider.loadTasks();
return GradleTasksTreeDataProvider.buildItemsTreeFromTasks(
await this.defaultProjectProvider.getDefaultTasks(
await this.rootProjectStore.getProjectRoots(),
this.client
),
this.rootProjectStore,
false,
this.icons
);
} else if (element instanceof ProjectTreeItem) {
return GradleTasksTreeDataProvider.getChildrenForProjectTreeItem(element);
} else if (element instanceof ProjectDependencyTreeItem) {
return this.defaultProjectProvider.getDefaultDependencyItems(element);
} else if (element instanceof ProjectTaskTreeItem) {
return element.getChildren() || [];
} else if (element instanceof GroupTreeItem) {
return element.tasks;
}
return [];
}
}

Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Range } from 'vscode-languageclient/node';
export interface DefaultDependencyItem {
name: string;
configuration: string;
range: Range;
}
export interface DefaultTaskDefinition {
name: string;
group: string;
description: string;
}

Просмотреть файл

@ -12,13 +12,15 @@ import { HintItem } from '../gradleTasks/HintItem';
import { getSpecificVersionStatus } from './util';
export class GradleDaemonsTreeDataProvider
implements vscode.TreeDataProvider<vscode.TreeItem> {
implements vscode.TreeDataProvider<vscode.TreeItem>
{
private cancelDeferred?: Deferred<vscode.TreeItem[]>;
private treeItems: vscode.TreeItem[] = [];
private specificVersion = false;
private readonly _onDidChangeTreeData: vscode.EventEmitter<vscode.TreeItem | null> = new vscode.EventEmitter<vscode.TreeItem | null>();
public readonly onDidChangeTreeData: vscode.Event<vscode.TreeItem | null> = this
._onDidChangeTreeData.event;
private readonly _onDidChangeTreeData: vscode.EventEmitter<vscode.TreeItem | null> =
new vscode.EventEmitter<vscode.TreeItem | null>();
public readonly onDidChangeTreeData: vscode.Event<vscode.TreeItem | null> =
this._onDidChangeTreeData.event;
constructor(
private readonly context: vscode.ExtensionContext,

Просмотреть файл

@ -4,7 +4,7 @@
import * as vscode from 'vscode';
export class DependencyConfigurationTreeItem extends vscode.TreeItem {
private children: vscode.TreeItem[] | undefined;
private children: vscode.TreeItem[];
constructor(
name: string,
collapsibleState: vscode.TreeItemCollapsibleState,
@ -13,13 +13,14 @@ export class DependencyConfigurationTreeItem extends vscode.TreeItem {
) {
super(name, collapsibleState);
this.iconPath = iconPath;
this.children = [];
}
public setChildren(children: vscode.TreeItem[]): void {
this.children = children;
}
public getChildren(): vscode.TreeItem[] | undefined {
public getChildren(): vscode.TreeItem[] {
return this.children;
}
}

Просмотреть файл

@ -38,11 +38,12 @@ function protocolItem2DependencyConfigurationTreeItem(
): DependencyConfigurationTreeItem | undefined {
const name = protocolItem.getName();
const storageMap = new Map();
const configurationItem: DependencyConfigurationTreeItem = new DependencyConfigurationTreeItem(
name,
vscode.TreeItemCollapsibleState.Collapsed,
parent
);
const configurationItem: DependencyConfigurationTreeItem =
new DependencyConfigurationTreeItem(
name,
vscode.TreeItemCollapsibleState.Collapsed,
parent
);
const children = protocolItem.getChildrenList();
const treeChildren = [];
for (const child of children) {

Просмотреть файл

@ -41,12 +41,13 @@ function resetCachedTreeItems(): void {
}
export class GradleTasksTreeDataProvider
implements vscode.TreeDataProvider<vscode.TreeItem> {
implements vscode.TreeDataProvider<vscode.TreeItem>
{
private collapsed = true;
private readonly _onDidChangeTreeData: vscode.EventEmitter<vscode.TreeItem | null> = new vscode.EventEmitter<vscode.TreeItem | null>();
public readonly onDidChangeTreeData: vscode.Event<vscode.TreeItem | null> = this
._onDidChangeTreeData.event;
private readonly _onDidChangeTreeData: vscode.EventEmitter<vscode.TreeItem | null> =
new vscode.EventEmitter<vscode.TreeItem | null>();
public readonly onDidChangeTreeData: vscode.Event<vscode.TreeItem | null> =
this._onDidChangeTreeData.event;
constructor(
private readonly context: vscode.ExtensionContext,
@ -81,7 +82,12 @@ export class GradleTasksTreeDataProvider
const tasks = await this.gradleTaskProvider.loadTasks();
return tasks.length === 0
? [new NoGradleTasksTreeItem()]
: this.buildItemsTreeFromTasks(tasks);
: GradleTasksTreeDataProvider.buildItemsTreeFromTasks(
tasks,
this.rootProjectStore,
this.collapsed,
this.icons
);
}
public refresh(treeItem: vscode.TreeItem | null = null): void {
@ -114,7 +120,7 @@ export class GradleTasksTreeDataProvider
return element.projects;
}
if (element instanceof ProjectTreeItem) {
return this.getChildrenForProjectTreeItem(element);
return GradleTasksTreeDataProvider.getChildrenForProjectTreeItem(element);
}
if (element instanceof GroupTreeItem) {
return element.tasks;
@ -136,12 +142,12 @@ export class GradleTasksTreeDataProvider
return element.getChildren() || [];
}
if (!element) {
return await this.buildTreeItems();
return this.buildTreeItems();
}
return [];
}
public async getChildrenForProjectTreeItem(
public static async getChildrenForProjectTreeItem(
element: ProjectTreeItem
): Promise<vscode.TreeItem[]> {
const projectTaskItem = new ProjectTaskTreeItem(
@ -155,26 +161,30 @@ export class GradleTasksTreeDataProvider
if (!resourceUri) {
return results;
}
const projectDependencyTreeItem: ProjectDependencyTreeItem = new ProjectDependencyTreeItem(
'Dependencies',
vscode.TreeItemCollapsibleState.Collapsed,
element,
path.dirname(resourceUri.fsPath),
typeof element.label === 'string' ? element.label : resourceUri.fsPath
);
const projectDependencyTreeItem: ProjectDependencyTreeItem =
new ProjectDependencyTreeItem(
'Dependencies',
vscode.TreeItemCollapsibleState.Collapsed,
element,
path.dirname(resourceUri.fsPath),
typeof element.label === 'string' ? element.label : resourceUri.fsPath
);
return [...results, projectDependencyTreeItem];
}
// eslint-disable-next-line sonarjs/cognitive-complexity
public buildItemsTreeFromTasks(
tasks: vscode.Task[]
public static buildItemsTreeFromTasks(
tasks: vscode.Task[],
rootProjectStore: RootProjectsStore,
collapsed: boolean,
icons: Icons
): RootProjectTreeItem[] | NoGradleTasksTreeItem[] {
let gradleProjectTreeItem = null;
tasks.forEach((task) => {
const definition = task.definition as GradleTaskDefinition;
if (isWorkspaceFolder(task.scope) && isGradleTask(task)) {
const rootProject = this.rootProjectStore.get(definition.projectFolder);
const rootProject = rootProjectStore.get(definition.projectFolder);
if (!rootProject) {
return;
}
@ -208,7 +218,7 @@ export class GradleTasksTreeDataProvider
);
let parentTreeItem: ProjectTreeItem | GroupTreeItem = projectTreeItem;
if (!this.collapsed) {
if (!collapsed) {
const groupId = definition.group + definition.project;
let groupTreeItem = groupTreeItemMap.get(groupId);
if (!groupTreeItem) {
@ -229,7 +239,7 @@ export class GradleTasksTreeDataProvider
taskName,
definition.description || taskName,
'',
this.icons,
icons,
rootProject.getJavaDebug()
);
taskTreeItem.setContext();

Просмотреть файл

@ -83,10 +83,12 @@ function buildGradleProjectTreeItem(
}
export class PinnedTasksTreeDataProvider
implements vscode.TreeDataProvider<vscode.TreeItem> {
private readonly _onDidChangeTreeData: vscode.EventEmitter<vscode.TreeItem | null> = new vscode.EventEmitter<vscode.TreeItem | null>();
public readonly onDidChangeTreeData: vscode.Event<vscode.TreeItem | null> = this
._onDidChangeTreeData.event;
implements vscode.TreeDataProvider<vscode.TreeItem>
{
private readonly _onDidChangeTreeData: vscode.EventEmitter<vscode.TreeItem | null> =
new vscode.EventEmitter<vscode.TreeItem | null>();
public readonly onDidChangeTreeData: vscode.Event<vscode.TreeItem | null> =
this._onDidChangeTreeData.event;
constructor(
private readonly pinnedTasksStore: PinnedTasksStore,

Просмотреть файл

@ -90,10 +90,12 @@ function buildGradleProjectTreeItem(
}
export class RecentTasksTreeDataProvider
implements vscode.TreeDataProvider<vscode.TreeItem> {
private readonly _onDidChangeTreeData: vscode.EventEmitter<vscode.TreeItem | null> = new vscode.EventEmitter<vscode.TreeItem | null>();
public readonly onDidChangeTreeData: vscode.Event<vscode.TreeItem | null> = this
._onDidChangeTreeData.event;
implements vscode.TreeDataProvider<vscode.TreeItem>
{
private readonly _onDidChangeTreeData: vscode.EventEmitter<vscode.TreeItem | null> =
new vscode.EventEmitter<vscode.TreeItem | null>();
public readonly onDidChangeTreeData: vscode.Event<vscode.TreeItem | null> =
this._onDidChangeTreeData.event;
constructor(
private readonly recentTasksStore: RecentTasksStore,

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,149 @@
THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
For vscode-gradle package
This extension uses Open Source components. You can find the source code of their
open source projects along with the license information below. We acknowledge and
are grateful to these developers for their contribution to open source.
1. fs-extra (https://github.com/jprichardson/node-fs-extra)
2. get-port (https://github.com/sindresorhus/get-port)
3. minimatch (https://github.com/isaacs/minimatch)
4. string-argv (https://github.com/mccormicka/string-argv)
5. tree-kill (https://github.com/pkrumins/node-tree-kill)
6. vscode-extension-telemetry-wrapper (https://github.com/Eskibear/vscode-extension-telemetry-wrapper)
fs-extra NOTICES BEGIN HERE
=============================
(The MIT License)
Copyright (c) 2011-2017 JP Richardson
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
END OF fs-extra NOTICES AND INFORMATION
==================================
get-port NOTICES BEGIN HERE
=============================
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
END OF get-port NOTICES AND INFORMATION
==================================
minimatch NOTICES BEGIN HERE
=============================
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
END OF minimatch NOTICES AND INFORMATION
=========================================
string-argv NOTICES BEGIN HERE
=============================
The MIT License (MIT)
Copyright 2014 Anthony McCormick
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
END OF string-argv NOTICES AND INFORMATION
=========================================
node-tree-kill NOTICES BEGIN HERE
=============================
MIT License
Copyright (c) 2018 Peter Krumins
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
END OF node-tree-kill NOTICES AND INFORMATION
=========================================
vscode-extension-telemetry-wrapper NOTICES BEGIN HERE
==========================================================
Copyright 2018 Yan Zhang
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
END OF vscode-extension-telemetry-wrapper NOTICES AND INFORMATION
==========================================================

Просмотреть файл

@ -0,0 +1,40 @@
plugins {
id "java"
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
repositories {
mavenCentral()
}
dependencies {
implementation "org.eclipse.lsp4j:org.eclipse.lsp4j:0.12.0"
implementation "org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.12.0"
implementation "org.codehaus.groovy:groovy-eclipse-batch:3.0.8-01"
implementation "com.google.code.gson:gson:2.8.7"
implementation "org.apache.bcel:bcel:6.5.0"
}
ext.mainClass = "com.microsoft.gradle.GradleLanguageServer"
task copyJar(type: Copy) {
from "build/libs/gradle-language-server.jar"
from configurations.runtimeClasspath
into "../extension/lib"
}
tasks.build.dependsOn tasks.copyJar
tasks.copyJar.dependsOn jar
jar {
manifest {
attributes(
"Main-Class": "com.microsoft.gradle.GradleLanguageServer",
"Class-Path": configurations.runtimeClasspath.collect { it.getName() }.join(' ')
)
}
}

Просмотреть файл

@ -0,0 +1,137 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle;
import java.io.IOException;
import java.net.Socket;
import java.net.URI;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.microsoft.gradle.semantictokens.TokenModifier;
import com.microsoft.gradle.semantictokens.TokenType;
import org.eclipse.lsp4j.CompletionOptions;
import org.eclipse.lsp4j.DocumentFilter;
import org.eclipse.lsp4j.ExecuteCommandOptions;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.SaveOptions;
import org.eclipse.lsp4j.SemanticTokensLegend;
import org.eclipse.lsp4j.SemanticTokensServerFull;
import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.TextDocumentSyncOptions;
import org.eclipse.lsp4j.WorkspaceFolder;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.eclipse.lsp4j.services.WorkspaceService;
public class GradleLanguageServer implements LanguageServer, LanguageClientAware {
private GradleServices gradleServices;
public static void main(String[] args) {
GradleLanguageServer server = new GradleLanguageServer();
try {
Launcher<LanguageClient> launcher;
String port = System.getenv("VSCODE_GRADLE_PORT");
if (port == null) {
// Launch Mode
launcher = Launcher.createLauncher(server, LanguageClient.class, System.in, System.out);
} else {
// Debug Mode
Socket socket = new Socket("localhost", Integer.parseInt(port));
launcher = Launcher.createLauncher(server, LanguageClient.class, socket.getInputStream(),
socket.getOutputStream());
}
server.connect(launcher.getRemoteProxy());
launcher.startListening();
} catch (IOException e) {
server.exit();
}
}
public GradleLanguageServer() {
this.gradleServices = new GradleServices();
}
@Override
public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
Map<?, ?> initOptions = new Gson().fromJson((JsonElement) params.getInitializationOptions(), Map.class);
// TODO: support multiple workspace folders
List<WorkspaceFolder> workspaceFolders = params.getWorkspaceFolders();
for (WorkspaceFolder folder : workspaceFolders) {
URI uri = URI.create(folder.getUri());
this.gradleServices.getLibraryResolver().setWorkspacePath(Paths.get(uri));
break;
}
Object settings = initOptions.get("settings");
this.gradleServices.applySetting(settings);
ServerCapabilities serverCapabilities = new ServerCapabilities();
SemanticTokensWithRegistrationOptions semanticOptions = new SemanticTokensWithRegistrationOptions();
semanticOptions.setFull(new SemanticTokensServerFull(false));
semanticOptions.setRange(false);
semanticOptions.setDocumentSelector(List.of(new DocumentFilter("gradle", "file", null)));
semanticOptions.setLegend(new SemanticTokensLegend(
Arrays.stream(TokenType.values()).map(TokenType::toString).collect(Collectors.toList()),
Arrays.stream(TokenModifier.values()).map(TokenModifier::toString).collect(Collectors.toList())));
serverCapabilities.setSemanticTokensProvider(semanticOptions);
serverCapabilities.setDocumentSymbolProvider(true);
TextDocumentSyncOptions textDocumentSyncOptions = new TextDocumentSyncOptions();
textDocumentSyncOptions.setOpenClose(Boolean.TRUE);
textDocumentSyncOptions.setSave(new SaveOptions(Boolean.TRUE));
textDocumentSyncOptions.setChange(TextDocumentSyncKind.Incremental);
serverCapabilities.setTextDocumentSync(textDocumentSyncOptions);
CompletionOptions completionOptions = new CompletionOptions(false, Arrays.asList(".", ":"));
serverCapabilities.setCompletionProvider(completionOptions);
serverCapabilities.setExecuteCommandProvider(new ExecuteCommandOptions(GradleServices.supportedCommands));
InitializeResult initializeResult = new InitializeResult(serverCapabilities);
return CompletableFuture.completedFuture(initializeResult);
}
@Override
public CompletableFuture<Object> shutdown() {
return CompletableFuture.completedFuture(new Object());
}
@Override
public void exit() {
System.exit(0);
}
@Override
public TextDocumentService getTextDocumentService() {
return this.gradleServices;
}
@Override
public WorkspaceService getWorkspaceService() {
return this.gradleServices;
}
@Override
public void connect(LanguageClient client) {
this.gradleServices.connect(client);
}
}

Просмотреть файл

@ -0,0 +1,335 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.microsoft.gradle.compile.CompletionVisitor;
import com.microsoft.gradle.compile.CompletionVisitor.DependencyItem;
import com.microsoft.gradle.compile.DocumentSymbolVisitor;
import com.microsoft.gradle.compile.GradleCompilationUnit;
import com.microsoft.gradle.compile.SemanticTokenVisitor;
import com.microsoft.gradle.handlers.CompletionHandler;
import com.microsoft.gradle.handlers.DefaultDependenciesHandler;
import com.microsoft.gradle.handlers.DefaultDependenciesHandler.DefaultDependencyItem;
import com.microsoft.gradle.handlers.DependencyCompletionHandler;
import com.microsoft.gradle.manager.GradleFilesManager;
import com.microsoft.gradle.resolver.GradleLibraryResolver;
import com.microsoft.gradle.semantictokens.SemanticToken;
import com.microsoft.gradle.utils.LSPUtils;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.ErrorCollector;
import org.codehaus.groovy.control.Phases;
import org.codehaus.groovy.control.messages.Message;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.codehaus.groovy.syntax.SyntaxException;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.eclipse.lsp4j.DidChangeConfigurationParams;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.DocumentSymbolParams;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SemanticTokens;
import org.eclipse.lsp4j.SemanticTokensParams;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.eclipse.lsp4j.services.WorkspaceService;
import org.eclipse.lsp4j.util.Ranges;
public class GradleServices implements TextDocumentService, WorkspaceService, LanguageClientAware {
public static final List<String> supportedCommands = List.of("gradle.getDependencies");
private LanguageClient client;
private GradleFilesManager gradleFilesManager;
private SemanticTokenVisitor semanticTokenVisitor;
private DocumentSymbolVisitor documentSymbolVisitor;
private CompletionVisitor completionVisitor;
private GradleLibraryResolver libraryResolver;
private DefaultDependenciesHandler defaultDependenciesHandler;
public GradleServices() {
this.gradleFilesManager = new GradleFilesManager();
this.semanticTokenVisitor = new SemanticTokenVisitor();
this.documentSymbolVisitor = new DocumentSymbolVisitor();
this.completionVisitor = new CompletionVisitor();
this.libraryResolver = new GradleLibraryResolver();
this.defaultDependenciesHandler = new DefaultDependenciesHandler();
}
public GradleLibraryResolver getLibraryResolver() {
return this.libraryResolver;
}
@Override
public void connect(LanguageClient client) {
this.client = client;
}
@Override
public void didOpen(DidOpenTextDocumentParams params) {
URI uri = URI.create(params.getTextDocument().getUri());
gradleFilesManager.didOpen(uri, params.getTextDocument().getText());
GradleCompilationUnit unit = this.gradleFilesManager.getCompilationUnit(uri, params.getTextDocument().getVersion());
compile(uri, unit);
}
@Override
public void didChange(DidChangeTextDocumentParams params) {
URI uri = URI.create(params.getTextDocument().getUri());
for (TextDocumentContentChangeEvent change : params.getContentChanges()) {
gradleFilesManager.didChange(uri, change);
}
GradleCompilationUnit unit = this.gradleFilesManager.getCompilationUnit(uri, params.getTextDocument().getVersion());
compile(uri, unit);
}
@Override
public void didClose(DidCloseTextDocumentParams params) {
URI uri = URI.create(params.getTextDocument().getUri());
gradleFilesManager.didClose(uri);
}
@Override
public void didSave(DidSaveTextDocumentParams params) {
// TODO
}
@Override
public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
// TODO
}
@Override
public void didChangeConfiguration(DidChangeConfigurationParams params) {
Map<?, ?> settings = new Gson().fromJson((JsonElement) params.getSettings(), Map.class);
this.applySetting(settings);
}
public void applySetting(Object settings) {
if (settings instanceof Map) {
this.getLibraryResolver().setGradleHome((String) ((Map<?, ?>) settings).get("gradleHome"));
this.getLibraryResolver().setGradleVersion((String) ((Map<?, ?>) settings).get("gradleVersion"));
this.getLibraryResolver().setGradleWrapperEnabled((Boolean) ((Map<?, ?>) settings).get("gradleWrapperEnabled"));
this.getLibraryResolver().setGradleUserHome((String) ((Map<?, ?>) settings).get("gradleUserHome"));
this.getLibraryResolver().resolve();
}
}
private void compile(URI uri, GradleCompilationUnit unit) {
if (unit == null) {
return;
}
Set<PublishDiagnosticsParams> diagnostics = new HashSet<>();
try {
unit.compile(Phases.CANONICALIZATION);
// Send empty diagnostic if there is no error
diagnostics.add(new PublishDiagnosticsParams(uri.toString(), Collections.emptyList()));
} catch (CompilationFailedException e) {
diagnostics = generateDiagnostics(unit.getErrorCollector());
}
for (PublishDiagnosticsParams diagnostic : diagnostics) {
client.publishDiagnostics(diagnostic);
}
}
private Set<PublishDiagnosticsParams> generateDiagnostics(ErrorCollector collector) {
// URI, List<Diagnostic>
Map<String, List<Diagnostic>> diagnosticsStorage = new HashMap<>();
for (Message error : collector.getErrors()) {
if (error instanceof SyntaxErrorMessage) {
SyntaxException exp = ((SyntaxErrorMessage) error).getCause();
Range range = LSPUtils.toRange(exp);
Diagnostic diagnostic = new Diagnostic();
diagnostic.setRange(range);
diagnostic.setSeverity(DiagnosticSeverity.Error);
diagnostic.setMessage(exp.getMessage());
diagnostic.setSource("Gradle");
if (diagnosticsStorage.containsKey(exp.getSourceLocator())) {
diagnosticsStorage.get(exp.getSourceLocator()).add(diagnostic);
} else {
List<Diagnostic> diagnostics = new ArrayList<>();
diagnostics.add(diagnostic);
diagnosticsStorage.put(exp.getSourceLocator(), diagnostics);
}
}
}
Set<PublishDiagnosticsParams> diagnosticsParams = new HashSet<>();
for (Map.Entry<String, List<Diagnostic>> entry : diagnosticsStorage.entrySet()) {
diagnosticsParams.add(new PublishDiagnosticsParams(entry.getKey(), entry.getValue()));
}
return diagnosticsParams;
}
@Override
public CompletableFuture<SemanticTokens> semanticTokensFull(SemanticTokensParams params) {
URI uri = URI.create(params.getTextDocument().getUri());
GradleCompilationUnit unit = this.gradleFilesManager.getCompilationUnit(uri);
if (unit == null) {
return CompletableFuture.completedFuture(new SemanticTokens(Collections.emptyList()));
}
this.semanticTokenVisitor.visitCompilationUnit(uri, unit);
List<SemanticToken> semanticTokens = this.semanticTokenVisitor.getSemanticTokens(uri);
if (semanticTokens == null) {
return CompletableFuture.completedFuture(new SemanticTokens(Collections.emptyList()));
}
return CompletableFuture.completedFuture(new SemanticTokens(SemanticToken.encodedTokens(semanticTokens)));
}
@Override
public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> documentSymbol(
DocumentSymbolParams params) {
URI uri = URI.create(params.getTextDocument().getUri());
GradleCompilationUnit unit = this.gradleFilesManager.getCompilationUnit(uri);
if (unit == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
this.documentSymbolVisitor.visitCompilationUnit(uri, unit);
List<DocumentSymbol> documentSymbols = this.documentSymbolVisitor.getDocumentSymbols(uri);
if (documentSymbols == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
List<Either<SymbolInformation, DocumentSymbol>> result = new ArrayList<>();
for (DocumentSymbol symbol : documentSymbols) {
result.add(Either.forRight(symbol));
}
return CompletableFuture.completedFuture(result);
}
@Override
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams params) {
URI uri = URI.create(params.getTextDocument().getUri());
GradleCompilationUnit unit = this.gradleFilesManager.getCompilationUnit(uri);
if (unit == null) {
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
this.completionVisitor.visitCompilationUnit(uri, unit);
List<DependencyItem> dependencies = this.completionVisitor.getDependencies(uri);
if (dependencies == null) {
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
for (DependencyItem dependency : dependencies) {
if (Ranges.containsPosition(dependency.getRange(), params.getPosition())) {
DependencyCompletionHandler handler = new DependencyCompletionHandler();
return CompletableFuture
.completedFuture(Either.forLeft(handler.getDependencyCompletionItems(dependency, params.getPosition())));
}
}
// should return empty if in constants
List<Expression> constants = this.completionVisitor.getConstants(uri);
for (Expression constant : constants) {
Range range = LSPUtils.toRange(constant);
if (Ranges.containsPosition(range, params.getPosition())) {
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
}
Set<MethodCallExpression> methodCalls = this.completionVisitor.getMethodCalls(uri);
MethodCallExpression containingCall = null;
for (MethodCallExpression call : methodCalls) {
Expression expression = call.getArguments();
Range range = LSPUtils.toRange(expression);
if (Ranges.containsPosition(range, params.getPosition()) && (containingCall == null || Ranges
.containsRange(LSPUtils.toRange(containingCall.getArguments()), LSPUtils.toRange(call.getArguments())))) {
// find inner containing call
containingCall = call;
}
}
boolean javaPluginsIncluded = this.libraryResolver.isJavaPluginsIncluded(this.completionVisitor.getPlugins(uri));
CompletionHandler handler = new CompletionHandler();
// check again
if (containingCall == null && isGradleRoot(uri, params.getPosition())) {
return CompletableFuture.completedFuture(Either
.forLeft(handler.getCompletionItems(null, Paths.get(uri).getFileName().toString(), this.libraryResolver, javaPluginsIncluded)));
}
return CompletableFuture.completedFuture(Either.forLeft(
handler.getCompletionItems(containingCall, Paths.get(uri).getFileName().toString(), this.libraryResolver, javaPluginsIncluded)));
}
@Override
public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
String command = params.getCommand();
if (command.equals("gradle.getDependencies")) {
List<Object> arguments = params.getArguments();
if (arguments.isEmpty()) {
return CompletableFuture.completedFuture(null);
}
String uriString = new Gson().fromJson((JsonElement) arguments.get(0), String.class);
URI uri = URI.create(uriString);
if (this.gradleFilesManager.getCompilationUnit(uri) == null) {
try {
Path uriPath = Paths.get(uri);
String content = Files.asCharSource(uriPath.toFile(), Charsets.UTF_8).read();
DidOpenTextDocumentParams openDocumentParams = new DidOpenTextDocumentParams(new TextDocumentItem(uriString, "gradle", 1, content));
this.didOpen(openDocumentParams);
DocumentSymbolParams documentSymbolParams = new DocumentSymbolParams(new TextDocumentIdentifier(uriString));
this.documentSymbol(documentSymbolParams);
} catch (IOException e) {
return CompletableFuture.completedFuture(null);
}
}
List<DocumentSymbol> dependencies = this.documentSymbolVisitor.getDependencies(uri);
if (dependencies == null) {
return CompletableFuture.completedFuture(null);
}
List<DefaultDependencyItem> result = defaultDependenciesHandler.getDefaultDependencies(dependencies);
return CompletableFuture.completedFuture(result);
}
return CompletableFuture.completedFuture(null);
}
private boolean isGradleRoot(URI uri, Position position) {
List<Statement> statements = this.completionVisitor.getStatements(uri);
for (Statement statement : statements) {
Range range = LSPUtils.toRange(statement);
if (Ranges.containsPosition(range, position)) {
return false;
}
}
return true;
}
}

Просмотреть файл

@ -0,0 +1,251 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.compile;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.microsoft.gradle.utils.LSPUtils;
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.GStringExpression;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.NamedArgumentListExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.control.SourceUnit;
import org.eclipse.lsp4j.Range;
public class CompletionVisitor extends ClassCodeVisitorSupport {
public class DependencyItem {
private String text;
private Range range;
public DependencyItem(String text, Range range) {
this.text = text;
this.range = range;
}
public String getText() {
return this.text;
}
public Range getRange() {
return this.range;
}
}
private URI currentUri;
private Map<URI, List<DependencyItem>> dependencies = new HashMap<>();
private Map<URI, Set<MethodCallExpression>> methodCalls = new HashMap<>();
private Map<URI, List<Statement>> statements = new HashMap<>();
private Map<URI, List<Expression>> constants = new HashMap<>();
private Map<URI, Set<String>> plugins = new HashMap<>();
public List<DependencyItem> getDependencies(URI uri) {
return this.dependencies.get(uri);
}
public Set<MethodCallExpression> getMethodCalls(URI uri) {
return this.methodCalls.get(uri);
}
public List<Statement> getStatements(URI uri) {
return this.statements.get(uri);
}
public List<Expression> getConstants(URI uri) {
return this.constants.get(uri);
}
public Set<String> getPlugins(URI uri) {
return this.plugins.get(uri);
}
public void visitCompilationUnit(URI uri, GradleCompilationUnit compilationUnit) {
this.currentUri = uri;
compilationUnit.iterator().forEachRemaining(unit -> visitSourceUnit(unit));
}
public void visitSourceUnit(SourceUnit unit) {
ModuleNode moduleNode = unit.getAST();
if (moduleNode != null) {
this.dependencies.put(this.currentUri, new ArrayList<>());
this.methodCalls.put(this.currentUri, new HashSet<>());
this.statements.put(this.currentUri, new ArrayList<>());
this.constants.put(this.currentUri, new ArrayList<>());
this.plugins.put(this.currentUri, new HashSet<>());
visitModule(moduleNode);
}
}
public void visitModule(ModuleNode node) {
BlockStatement blockStatement = node.getStatementBlock();
this.statements.put(currentUri, blockStatement.getStatements());
node.getClasses().forEach(classNode -> {
super.visitClass(classNode);
});
}
@Override
public void visitMethodCallExpression(MethodCallExpression node) {
this.methodCalls.get(this.currentUri).add(node);
if (node.getMethodAsString().equals("dependencies")) {
this.dependencies.get(this.currentUri).addAll(getDependencies(node));
} else if (node.getMethodAsString().equals("plugins")) {
// match plugins { id: ${id} }
List<String> plugins = getPluginFromPlugins(node);
this.plugins.get(this.currentUri).addAll(plugins);
} else if (node.getMethodAsString().equals("apply")) {
// match apply plugins: '${id}'
String plugin = getPluginFromApply(node);
if (plugin != null) {
this.plugins.get(this.currentUri).add(plugin);
}
}
super.visitMethodCallExpression(node);
}
private List<DependencyItem> getDependencies(MethodCallExpression expression) {
Expression argument = expression.getArguments();
if (argument instanceof ArgumentListExpression) {
return getDependencies((ArgumentListExpression) argument);
}
return Collections.emptyList();
}
private List<DependencyItem> getDependencies(ArgumentListExpression argumentListExpression) {
List<Expression> expressions = argumentListExpression.getExpressions();
List<DependencyItem> symbols = new ArrayList<>();
for (Expression expression : expressions) {
if (expression instanceof ClosureExpression) {
symbols.addAll(getDependencies((ClosureExpression) expression));
} else if (expression instanceof GStringExpression || expression instanceof ConstantExpression) {
// GStringExp: implementation "org.gradle:gradle-tooling-api:${gradleToolingApi}"
// ConstantExp: implementation "org.gradle:gradle-tooling-api:6.8.0"
symbols.add(new DependencyItem(expression.getText(), LSPUtils.toDependencyRange(expression)));
} else if (expression instanceof MethodCallExpression) {
symbols.addAll(getDependencies((MethodCallExpression) expression));
}
}
return symbols;
}
private List<DependencyItem> getDependencies(ClosureExpression expression) {
Statement code = expression.getCode();
if (code instanceof BlockStatement) {
return getDependencies((BlockStatement) code);
}
return Collections.emptyList();
}
private List<DependencyItem> getDependencies(BlockStatement blockStatement) {
List<Statement> statements = blockStatement.getStatements();
List<DependencyItem> results = new ArrayList<>();
for (Statement statement : statements) {
if (statement instanceof ExpressionStatement) {
results.addAll(getDependencies((ExpressionStatement) statement));
}
}
return results;
}
private List<DependencyItem> getDependencies(ExpressionStatement expressionStatement) {
Expression expression = expressionStatement.getExpression();
if (expression instanceof MethodCallExpression) {
return getDependencies((MethodCallExpression) expression);
}
return Collections.emptyList();
}
private String getPluginFromApply(MethodCallExpression node) {
Expression argument = node.getArguments();
if (argument instanceof TupleExpression) {
List<Expression> expressions = ((TupleExpression)argument).getExpressions();
for (Expression expression : expressions) {
if (expression instanceof NamedArgumentListExpression) {
List<MapEntryExpression> mapEntryExpressions = ((NamedArgumentListExpression)expression).getMapEntryExpressions();
for (MapEntryExpression mapEntryExp: mapEntryExpressions) {
Expression keyExpression = mapEntryExp.getKeyExpression();
if (keyExpression instanceof ConstantExpression && keyExpression.getText().equals("plugin")) {
return mapEntryExp.getValueExpression().getText();
}
}
}
}
}
return null;
}
private List<String> getPluginFromPlugins(MethodCallExpression node) {
Expression objectExpression = node.getObjectExpression();
if (objectExpression instanceof MethodCallExpression) {
return getPluginFromPlugins((MethodCallExpression)objectExpression);
}
List<String> results = new ArrayList<>();
Expression argument = node.getArguments();
if (argument instanceof ArgumentListExpression) {
List<Expression> expressions = ((ArgumentListExpression)argument).getExpressions();
for (Expression expression : expressions) {
if (expression instanceof ConstantExpression && node.getMethodAsString().equals("id")) {
results.add(expression.getText());
} else if (expression instanceof ClosureExpression) {
Statement code = ((ClosureExpression)expression).getCode();
if (code instanceof BlockStatement) {
results.addAll(getPluginFromPlugins((BlockStatement) code));
}
}
}
}
return results;
}
private List<String> getPluginFromPlugins(BlockStatement code) {
List<String> results = new ArrayList<>();
List<Statement> statements = code.getStatements();
for (Statement statement : statements) {
if (statement instanceof ExpressionStatement) {
Expression expression = ((ExpressionStatement)statement).getExpression();
if (expression instanceof MethodCallExpression) {
results.addAll(getPluginFromPlugins((MethodCallExpression) expression));
}
}
}
return results;
}
@Override
public void visitConstantExpression(ConstantExpression expression) {
this.constants.get(currentUri).add(expression);
super.visitConstantExpression(expression);
}
@Override
public void visitGStringExpression(GStringExpression expression) {
this.constants.get(currentUri).add(expression);
super.visitGStringExpression(expression);
}
}

Просмотреть файл

@ -0,0 +1,289 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.compile;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.microsoft.gradle.utils.LSPUtils;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.GStringExpression;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.NamedArgumentListExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.control.SourceUnit;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.SymbolKind;
public class DocumentSymbolVisitor {
private URI currentUri;
private Map<URI, List<DocumentSymbol>> documentSymbols = new HashMap<>();
private Map<URI, List<DocumentSymbol>> dependencies = new HashMap<>();
public List<DocumentSymbol> getDocumentSymbols(URI uri) {
return this.documentSymbols.get(uri);
}
public List<DocumentSymbol> getDependencies(URI uri) {
return this.dependencies.get(uri);
}
public void visitCompilationUnit(URI uri, GradleCompilationUnit compilationUnit) {
this.currentUri = uri;
compilationUnit.iterator().forEachRemaining(unit -> visitSourceUnit(uri, unit));
}
public void visitSourceUnit(URI uri, SourceUnit unit) {
ModuleNode moduleNode = unit.getAST();
if (moduleNode != null) {
this.documentSymbols.put(uri, new ArrayList<>());
this.dependencies.put(uri, new ArrayList<>());
visitModule(moduleNode);
}
}
public void visitModule(ModuleNode node) {
BlockStatement blockStatement = node.getStatementBlock();
List<Statement> statements = blockStatement.getStatements();
for (Statement statement : statements) {
if (statement instanceof ExpressionStatement) {
DocumentSymbol symbol = getDocumentSymbol((ExpressionStatement) statement);
if (symbol != null) {
this.documentSymbols.get(this.currentUri).add(symbol);
}
}
}
}
public DocumentSymbol getDocumentSymbol(ExpressionStatement statement) {
Expression expression = statement.getExpression();
DocumentSymbol symbol = null;
if (expression instanceof MethodCallExpression) {
symbol = getDocumentSymbol((MethodCallExpression) expression);
} else if (expression instanceof BinaryExpression) {
symbol = getDocumentSymbol((BinaryExpression) expression);
}
if (symbol == null || symbol.getName() == null) {
return null;
}
return symbol;
}
private DocumentSymbol getDocumentSymbol(BinaryExpression expression) {
Expression left = expression.getLeftExpression();
Expression right = expression.getRightExpression();
DocumentSymbol symbol = new DocumentSymbol();
symbol.setName(left.getText());
if (right instanceof ConstantExpression) {
symbol.setDetail(right.getText());
}
symbol.setKind(SymbolKind.Property);
symbol.setRange(LSPUtils.toRange(expression));
symbol.setSelectionRange(LSPUtils.toRange(expression));
return symbol;
}
private DocumentSymbol getDocumentSymbol(MethodCallExpression expression) {
DocumentSymbol symbol = new DocumentSymbol();
symbol.setKind(SymbolKind.Function);
String name = getSymbolName(expression);
if (name == null) {
return null;
}
symbol.setName(name);
String detail = getSymbolDetail(expression);
if (detail != null) {
symbol.setDetail(detail);
}
symbol.setSelectionRange(LSPUtils.toRange(expression));
symbol.setRange(LSPUtils.toRange(expression));
if (expression.getMethodAsString().equals("dependencies")) {
List<DocumentSymbol> dependencySymbols = getDependencies(expression);
symbol.setChildren(dependencySymbols);
this.dependencies.get(currentUri).addAll(dependencySymbols);
}
return symbol;
}
private String getSymbolName(MethodCallExpression expression) {
Expression objectExpression = expression.getObjectExpression();
if (objectExpression instanceof VariableExpression) {
StringBuilder builder = new StringBuilder();
String objectText = objectExpression.getText();
if (!objectText.equals("this")) {
// variable "this" should be ignored
builder.append(objectText);
builder.append(".");
}
builder.append(expression.getMethodAsString());
Expression arguments = expression.getArguments();
if (arguments instanceof ArgumentListExpression) {
List<Expression> expressions = ((ArgumentListExpression) arguments).getExpressions();
for (Expression exp : expressions) {
if (exp instanceof MethodCallExpression) {
// for case: task taskName(Closure), we show "task taskName" in outline
builder.append(" ");
builder.append(getSymbolName((MethodCallExpression) exp));
}
}
}
return builder.toString();
} else if (objectExpression instanceof PropertyExpression) {
// for case: a.b.c.d("string"), we show "a.b.c.d" in outline
StringBuilder builder = new StringBuilder();
builder.append(getSymbolName((PropertyExpression) objectExpression));
builder.append(".");
builder.append(expression.getMethodAsString());
return builder.toString();
}
return null;
}
private String getSymbolName(PropertyExpression expression) {
Expression objectExpression = expression.getObjectExpression();
Expression property = expression.getProperty();
StringBuilder builder = new StringBuilder();
if (objectExpression instanceof PropertyExpression) {
builder.append(getSymbolName((PropertyExpression) objectExpression));
} else if (objectExpression instanceof VariableExpression) {
builder.append(objectExpression.getText());
}
if (property instanceof ConstantExpression) {
builder.append(".");
builder.append(property.getText());
}
return builder.toString();
}
private String getSymbolDetail(MethodCallExpression expression) {
Expression argument = expression.getArguments();
if (argument instanceof ArgumentListExpression) {
List<Expression> arguments = ((ArgumentListExpression) argument).getExpressions();
if (!arguments.isEmpty() && arguments.get(0) instanceof ConstantExpression) {
// if first arg is constantExpression, show it as detail
return arguments.get(0).getText();
}
return null;
} else if (argument instanceof TupleExpression) {
// if argument is tupleExpression, show first argument as detail
List<Expression> arguments = ((TupleExpression) argument).getExpressions();
if (!arguments.isEmpty() && arguments.get(0) instanceof NamedArgumentListExpression) {
NamedArgumentListExpression namedArgumentListExpression = (NamedArgumentListExpression) arguments.get(0);
List<MapEntryExpression> mapEntryExpressions = namedArgumentListExpression.getMapEntryExpressions();
if (!mapEntryExpressions.isEmpty()) {
MapEntryExpression firstExpression = mapEntryExpressions.get(0);
if (firstExpression.getValueExpression() instanceof ConstantExpression) {
StringBuilder detail = new StringBuilder();
detail.append(firstExpression.getKeyExpression().getText());
detail.append(": ");
detail.append(firstExpression.getValueExpression().getText());
return detail.toString();
}
}
return null;
}
}
return null;
}
private List<DocumentSymbol> getDependencies(MethodCallExpression expression) {
Expression argument = expression.getArguments();
if (expression.getMethodAsString().equals("dependencies")) {
return getDependencies((ArgumentListExpression) argument);
}
List<DocumentSymbol> results = new ArrayList<>();
DocumentSymbol symbol = new DocumentSymbol();
String name = expression.getMethodAsString();
symbol.setName(name);
String detail = getDetail(expression);
if (detail != null) {
symbol.setDetail(detail);
}
symbol.setKind(SymbolKind.Constant);
symbol.setRange(LSPUtils.toRange(expression));
symbol.setSelectionRange(LSPUtils.toRange(expression));
results.add(symbol);
return results;
}
private List<DocumentSymbol> getDependencies(ArgumentListExpression argumentListExpression) {
List<Expression> expressions = argumentListExpression.getExpressions();
List<DocumentSymbol> symbols = new ArrayList<>();
for (Expression expression : expressions) {
if (expression instanceof ClosureExpression) {
symbols.addAll(getDependencies((ClosureExpression) expression));
} else if (expression instanceof MethodCallExpression) {
symbols.addAll(getDependencies((MethodCallExpression) expression));
}
}
return symbols;
}
private List<DocumentSymbol> getDependencies(ClosureExpression expression) {
Statement code = expression.getCode();
if (code instanceof BlockStatement) {
return getDependencies((BlockStatement) code);
}
return Collections.emptyList();
}
private List<DocumentSymbol> getDependencies(BlockStatement blockStatement) {
List<Statement> statements = blockStatement.getStatements();
List<DocumentSymbol> symbols = new ArrayList<>();
for (Statement statement : statements) {
if (statement instanceof ExpressionStatement) {
symbols.addAll(getDependencies((ExpressionStatement) statement));
}
}
return symbols;
}
private List<DocumentSymbol> getDependencies(ExpressionStatement expressionStatement) {
Expression expression = expressionStatement.getExpression();
List<DocumentSymbol> symbols = new ArrayList<>();
if (expression instanceof MethodCallExpression) {
symbols.addAll(getDependencies((MethodCallExpression) expression));
}
return symbols;
}
private String getDetail(MethodCallExpression node) {
Expression arguments = node.getArguments();
if (arguments instanceof ArgumentListExpression) {
List<Expression> expressions = ((ArgumentListExpression) arguments).getExpressions();
for (Expression expression : expressions) {
if (expression instanceof MethodCallExpression) {
return getDetail((MethodCallExpression) expression);
} else if (expression instanceof GStringExpression || expression instanceof ConstantExpression) {
return expression.getText();
}
}
}
return null;
}
}

Просмотреть файл

@ -0,0 +1,32 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.compile;
import java.security.CodeSource;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;
import groovy.lang.GroovyClassLoader;
public class GradleCompilationUnit extends CompilationUnit {
private Integer version;
public GradleCompilationUnit(CompilerConfiguration configuration, CodeSource codeSource, GroovyClassLoader loader, Integer version) {
super(configuration, codeSource, loader);
this.version = version;
}
public Integer getVersion() {
return this.version;
}
}

Просмотреть файл

@ -0,0 +1,101 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.compile;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.microsoft.gradle.semantictokens.SemanticToken;
import com.microsoft.gradle.semantictokens.TokenModifier;
import com.microsoft.gradle.semantictokens.TokenType;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.control.SourceUnit;
public class SemanticTokenVisitor extends ClassCodeVisitorSupport {
private URI currentUri;
private Map<URI, List<SemanticToken>> tokens = new HashMap<>();
public List<SemanticToken> getSemanticTokens(URI uri) {
return this.tokens.get(uri);
}
private void addToken(int line, int column, int length, TokenType tokenType, int modifiers) {
if (length > 0) {
tokens.get(currentUri).add(new SemanticToken(line, column, length, tokenType, modifiers));
}
}
private void addToken(ASTNode node, TokenType tokenType, int modifiers) {
addToken(node.getLineNumber(), node.getColumnNumber(), node.getLength(), tokenType, modifiers);
}
private void addToken(ASTNode node, TokenType tokenType) {
addToken(node.getLineNumber(), node.getColumnNumber(), node.getLength(), tokenType, 0);
}
public void visitCompilationUnit(URI uri, GradleCompilationUnit compilationUnit) {
this.currentUri = uri;
compilationUnit.iterator().forEachRemaining(unit -> visitSourceUnit(uri, unit));
}
public void visitSourceUnit(URI uri, SourceUnit unit) {
ModuleNode moduleNode = unit.getAST();
if (moduleNode != null) {
this.tokens.put(uri, new ArrayList<>());
visitModule(moduleNode);
}
}
public void visitModule(ModuleNode node) {
node.getClasses().forEach(classNode -> {
super.visitClass(classNode);
});
}
@Override
public void visitMethodCallExpression(MethodCallExpression node) {
if (TokenModifier.isDefaultLibrary(node.getMethod().getText())) {
addToken(node.getMethod(), TokenType.FUNCTION, TokenModifier.DEFAULT_LIBRARY.bitmask);
} else {
addToken(node.getMethod(), TokenType.FUNCTION);
}
super.visitMethodCallExpression(node);
}
@Override
public void visitMapEntryExpression(MapEntryExpression node) {
addToken(node.getKeyExpression(), TokenType.PARAMETER);
super.visitMapEntryExpression(node);
}
@Override
public void visitVariableExpression(VariableExpression node) {
addToken(node, TokenType.VARIABLE);
super.visitVariableExpression(node);
}
@Override
public void visitPropertyExpression(PropertyExpression node) {
addToken(node.getProperty(), TokenType.PROPERTY);
super.visitPropertyExpression(node);
}
}

Просмотреть файл

@ -0,0 +1,61 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.delegate;
import java.util.HashMap;
import java.util.Map;
public class GradleDelegate {
private static String PROJECT = "org.gradle.api.Project";
private static String SETTINGS = "org.gradle.api.initialization.Settings";
private static Map<String, String> delegateMap;
static {
delegateMap = new HashMap<>();
// plugins
delegateMap.put("application", "org.gradle.api.plugins.ApplicationPluginConvention");
delegateMap.put("base", "org.gradle.api.plugins.BasePluginConvention");
delegateMap.put("java", "org.gradle.api.plugins.JavaPluginConvention");
delegateMap.put("war", "org.gradle.api.plugins.WarPluginConvention");
// basic closures
delegateMap.put("plugins", "org.gradle.plugin.use.PluginDependenciesSpec");
delegateMap.put("configurations", "org.gradle.api.artifacts.Configuration");
delegateMap.put("dependencySubstitution", "org.gradle.api.artifacts.DependencySubstitutions");
delegateMap.put("resolutionStrategy", "org.gradle.api.artifacts.ResolutionStrategy");
delegateMap.put("artifacts", "org.gradle.api.artifacts.dsl.ArtifactHandler");
delegateMap.put("components", "org.gradle.api.artifacts.dsl.ComponentMetadataHandler");
delegateMap.put("modules", "org.gradle.api.artifacts.dsl.ComponentModuleMetadataHandler");
delegateMap.put("dependencies", "org.gradle.api.artifacts.dsl.DependencyHandler");
delegateMap.put("repositories", "org.gradle.api.artifacts.dsl.RepositoryHandler");
delegateMap.put("publishing", "org.gradle.api.publish.PublishingExtension");
delegateMap.put("publications", "org.gradle.api.publish.PublicationContainer");
delegateMap.put("sourceSets", "org.gradle.api.tasks.SourceSet");
delegateMap.put("distributions", "org.gradle.api.distribution.Distribution");
delegateMap.put("fileTree", "org.gradle.api.file.ConfigurableFileTree");
delegateMap.put("copySpec", "org.gradle.api.file.CopySpec");
delegateMap.put("exec", "org.gradle.process.ExecSpec");
delegateMap.put("files", "org.gradle.api.file.ConfigurableFileCollection");
delegateMap.put("task", "org.gradle.api.Task");
}
public static Map<String, String> getDelegateMap() {
return delegateMap;
}
public static String getDefault() {
return PROJECT;
}
public static String getSettings() {
return SETTINGS;
}
}

Просмотреть файл

@ -0,0 +1,150 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.handlers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.microsoft.gradle.delegate.GradleDelegate;
import com.microsoft.gradle.resolver.GradleLibraryResolver;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.Type;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.InsertTextFormat;
public class CompletionHandler {
private static String BUILD_GRADLE = "build.gradle";
private static String SETTING_GRADLE = "settings.gradle";
private static String DEPENDENCYHANDLER_CLASS = "org.gradle.api.artifacts.dsl.DependencyHandler";
public List<CompletionItem> getCompletionItems(MethodCallExpression containingCall, String fileName, GradleLibraryResolver resolver, boolean javaPluginsIncluded) {
String delegateClassName = null;
if (containingCall == null) {
if (fileName.equals(BUILD_GRADLE)) {
delegateClassName = GradleDelegate.getDefault();
} else if (fileName.equals(SETTING_GRADLE)) {
delegateClassName = GradleDelegate.getSettings();
}
} else {
delegateClassName = GradleDelegate.getDelegateMap().get(containingCall.getMethodAsString());
}
if (delegateClassName == null) {
return Collections.emptyList();
}
JavaClass delegateClass = resolver.getGradleLibraries().get(delegateClassName);
if (delegateClass == null) {
return Collections.emptyList();
}
return getCompletionItemsFromClass(delegateClass, resolver, javaPluginsIncluded, new HashSet<>());
}
private List<CompletionItem> getCompletionItemsFromClass(JavaClass javaClass, GradleLibraryResolver resolver, boolean javaPluginsIncluded, Set<String> resultSet) {
if (javaClass == null) {
return Collections.emptyList();
}
List<CompletionItem> results = new ArrayList<>();
for (String superInterface : javaClass.getInterfaceNames()) {
if (resolver.getGradleLibraries().containsKey(superInterface)) {
results.addAll(getCompletionItemsFromClass(resolver.getGradleLibraries().get(superInterface), resolver, javaPluginsIncluded, resultSet));
}
}
String superClass = javaClass.getSuperclassName();
if (resolver.getGradleLibraries().containsKey(superClass)) {
results.addAll(getCompletionItemsFromClass(resolver.getGradleLibraries().get(superClass), resolver, javaPluginsIncluded, resultSet));
}
List<String> methodNames = new ArrayList<>();
Method[] methods = javaClass.getMethods();
for (Method method : methods) {
StringBuilder labelBuilder = new StringBuilder();
String methodName = method.getName();
// When parsing a abstract class, we'll get a "<init>" method which can't be called directly,
// So we filter it here.
if (methodName.equals("<init>")) {
continue;
}
methodNames.add(methodName);
labelBuilder.append(methodName);
labelBuilder.append("(");
for (Type type : method.getArgumentTypes()) {
if (type instanceof ObjectType) {
String[] classNameSplits = ((ObjectType) type).getClassName().split("\\.");
String className = classNameSplits[classNameSplits.length - 1];
String variableName = className.substring(0, 1).toLowerCase();
labelBuilder.append(className);
labelBuilder.append(" ");
labelBuilder.append(variableName);
labelBuilder.append(",");
}
}
if (labelBuilder.charAt(labelBuilder.length() - 1) == ',') {
labelBuilder.deleteCharAt(labelBuilder.length() - 1);
}
labelBuilder.append(")");
String label = labelBuilder.toString();
CompletionItem item = new CompletionItem(label);
item.setKind(CompletionItemKind.Function);
item.setInsertTextFormat(InsertTextFormat.Snippet);
StringBuilder builder = new StringBuilder();
builder.append(methodName);
if (label.endsWith("(Closure c)")) {
// for single closure, we offer curly brackets
builder.append(" {$0}");
} else {
builder.append("($0)");
}
item.setInsertText(builder.toString());
if (resultSet.add(label)) {
results.add(item);
}
}
for (String methodName : methodNames) {
if (methodName.startsWith("set") && methodName.length() > 3) {
// for cases like setVersion() and getVersion(),
// we offer version as a property
String getMethod = "get" + methodName.substring(3);
if (methodNames.contains(getMethod)) {
String property = methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
CompletionItem item = new CompletionItem(property);
item.setKind(CompletionItemKind.Property);
if (resultSet.add(property)) {
results.add(item);
}
}
}
}
if (javaPluginsIncluded && javaClass.getClassName().equals(DEPENDENCYHANDLER_CLASS)) {
// for dependency {}, we offer java configurations if there is any applied java plugin
for (String plugin : resolver.getJavaConfigurations()) {
StringBuilder builder = new StringBuilder();
builder.append(plugin);
builder.append("(Object... o)");
StringBuilder insertBuilder = new StringBuilder();
insertBuilder.append(plugin);
insertBuilder.append("($0)");
CompletionItem item = new CompletionItem(builder.toString());
item.setKind(CompletionItemKind.Function);
item.setInsertTextFormat(InsertTextFormat.Snippet);
item.setInsertText(insertBuilder.toString());
results.add(item);
}
}
return results;
}
}

Просмотреть файл

@ -0,0 +1,45 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.handlers;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.Range;
public class DefaultDependenciesHandler {
public class DefaultDependencyItem {
private String name;
private String configuration;
private Range range;
public DefaultDependencyItem(String name, String configuration, Range range) {
this.name = name;
this.configuration = configuration;
this.range = range;
}
}
public List<DefaultDependencyItem> getDefaultDependencies(List<DocumentSymbol> symbols) {
List<DefaultDependencyItem> dependencies = new ArrayList<>();
for (DocumentSymbol symbol : symbols) {
String configuration = symbol.getName();
String id = symbol.getDetail();
Range range = symbol.getRange();
DefaultDependencyItem item = new DefaultDependencyItem(id, configuration, range);
dependencies.add(item);
}
return dependencies;
}
}

Просмотреть файл

@ -0,0 +1,136 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.handlers;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.gradle.compile.CompletionVisitor.DependencyItem;
import com.microsoft.gradle.utils.LSPUtils;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
public class DependencyCompletionHandler {
private DependencyItem dependency;
private Position position;
private static String URL_BASIC_SEARCH = "https://search.maven.org/solrsearch/select?q=";
private enum DependencyCompletionKind {
ID, VERSION
}
public List<CompletionItem> getDependencyCompletionItems(DependencyItem dependency, Position position) {
this.dependency = dependency;
this.position = position;
String validText = LSPUtils.getStringBeforePosition(dependency.getText(), dependency.getRange(), position);
String[] validTexts = validText.split(":", -1);
switch (validTexts.length) {
case 1:
return getDependenciesForInCompleteGroup(validTexts[0]);
case 2:
return getDependenciesForInCompleteArtifact(validTexts[0]);
case 3:
return getDependenciesForVersion(validTexts[0], validTexts[1]);
default:
return Collections.emptyList();
}
}
private List<CompletionItem> getDependenciesForInCompleteGroup(String group) {
if (group.length() < 3) {
return Collections.emptyList();
}
StringBuilder builder = new StringBuilder();
builder.append(URL_BASIC_SEARCH);
builder.append(group);
// limit the number of result to 50
builder.append("&rows=50&wt=json");
return getDependenciesFromRestAPI(builder.toString(), DependencyCompletionKind.ID);
}
private List<CompletionItem> getDependenciesForInCompleteArtifact(String group) {
if (group.length() < 3) {
return Collections.emptyList();
}
StringBuilder builder = new StringBuilder();
builder.append(URL_BASIC_SEARCH);
builder.append("g:%22");
builder.append(group);
// limit the number of result to 50
builder.append("%22&rows=50&wt=json");
return getDependenciesFromRestAPI(builder.toString(), DependencyCompletionKind.ID);
}
private List<CompletionItem> getDependenciesForVersion(String group, String artifact) {
if (group.length() < 3 || artifact.length() < 3) {
return Collections.emptyList();
}
StringBuilder builder = new StringBuilder();
builder.append(URL_BASIC_SEARCH);
builder.append("g:%22");
builder.append(group);
builder.append("%22+AND+a:%22");
builder.append(artifact);
// limit the number of result to 50
builder.append("%22&core=gav&rows=50&wt=json");
return getDependenciesFromRestAPI(builder.toString(), DependencyCompletionKind.VERSION);
}
private List<CompletionItem> getDependenciesFromRestAPI(String url, DependencyCompletionKind kind) {
try (InputStreamReader reader = new InputStreamReader(new URL(url).openStream())) {
JsonObject jsonResult = new Gson().fromJson(reader, JsonObject.class);
JsonObject response = jsonResult.getAsJsonObject("response");
JsonArray docs = response.getAsJsonArray("docs");
List<CompletionItem> completions = new ArrayList<>();
for (int i = 0; i < docs.size(); i++) {
JsonElement element = docs.get(i);
if (element instanceof JsonObject) {
CompletionItem completionItem = new CompletionItem();
JsonElement labelContent = ((JsonObject) element).get("id");
String label = labelContent.getAsJsonPrimitive().getAsString();
TextEdit textEdit = new TextEdit(new Range(this.dependency.getRange().getStart(), this.position), label);
completionItem.setTextEdit(Either.forLeft(textEdit));
if (kind == DependencyCompletionKind.ID) {
completionItem.setLabel(label);
completionItem.setTextEdit(Either.forLeft(textEdit));
completionItem.setKind(CompletionItemKind.Module);
completionItem.setDetail("mavenCentral");
} else {
String version = ((JsonObject) element).get("v").getAsJsonPrimitive().getAsString();
completionItem.setLabel(version);
completionItem.setFilterText(label + ":" + version);
completionItem.setKind(CompletionItemKind.Constant);
completionItem.setDetail("version");
}
// Currently we have no more than 50 results, so the padding values should have at least 3 digits.
completionItem.setSortText(String.format("%03d", i));
completions.add(completionItem);
}
}
return completions;
} catch (Exception e) {
// TODO
}
return Collections.emptyList();
}
}

Просмотреть файл

@ -0,0 +1,119 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.manager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import com.microsoft.gradle.compile.GradleCompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.io.StringReaderSource;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import groovy.lang.GroovyClassLoader;
public class GradleFilesManager {
private Map<URI, String> openFiles = new HashMap<>();
private Map<URI, GradleCompilationUnit> unitStorage = new HashMap<>();
public void didOpen(URI uri, String content) {
openFiles.put(uri, content);
}
public void didChange(URI uri, TextDocumentContentChangeEvent change) {
String oldText = openFiles.get(uri);
Range range = change.getRange();
if (range == null) {
openFiles.put(uri, change.getText());
} else {
int offsetStart = getOffset(oldText, change.getRange().getStart());
int offsetEnd = getOffset(oldText, change.getRange().getEnd());
StringBuilder builder = new StringBuilder();
builder.append(oldText.substring(0, offsetStart));
builder.append(change.getText());
builder.append(oldText.substring(offsetEnd));
openFiles.put(uri, builder.toString());
}
}
public void didClose(URI uri) {
openFiles.remove(uri);
}
public String getContents(URI uri) {
if (openFiles.containsKey(uri)) {
return openFiles.get(uri);
}
return null;
}
public int getOffset(String string, Position position) {
int line = position.getLine();
int character = position.getCharacter();
int currentIndex = 0;
if (line > 0) {
BufferedReader reader = new BufferedReader(new StringReader(string));
try {
int readLines = 0;
while (true) {
char currentChar = (char) reader.read();
if (currentChar == -1) {
return -1;
}
currentIndex++;
if (currentChar == '\n') {
readLines++;
if (readLines == line) {
break;
}
}
}
reader.close();
} catch (IOException e) {
return -1;
}
}
return currentIndex + character;
}
public GradleCompilationUnit getCompilationUnit(URI uri, Integer version) {
if (this.unitStorage.containsKey(uri) && this.unitStorage.get(uri).getVersion().equals(version)) {
return this.unitStorage.get(uri);
}
CompilerConfiguration config = new CompilerConfiguration();
GroovyClassLoader classLoader = new GroovyClassLoader(ClassLoader.getSystemClassLoader().getParent(), config, true);
GradleCompilationUnit unit = new GradleCompilationUnit(config, null, classLoader, version);
SourceUnit sourceUnit = new SourceUnit(uri.toString(),
new StringReaderSource(getContents(uri), unit.getConfiguration()),
unit.getConfiguration(), unit.getClassLoader(), unit.getErrorCollector());
unit.addSource(sourceUnit);
this.unitStorage.put(uri, unit);
return unit;
}
public GradleCompilationUnit getCompilationUnit(URI uri) {
// if there is no version info provided, we return the newest version
// when the previous cu exists, otherwise return null
if (this.unitStorage.containsKey(uri)) {
return this.unitStorage.get(uri);
}
return null;
}
}

Просмотреть файл

@ -0,0 +1,240 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.resolver;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
public class GradleLibraryResolver {
private static String JAVA_PLUGIN = "org.gradle.api.plugins.JavaPlugin";
private Map<String, JavaClass> gradleLibraries = new HashMap<>();
private Set<String> javaConfigurations = new HashSet<>();
private Set<String> javaPlugins = new HashSet<>();
private String gradleHome;
private String gradleVersion;
private boolean gradleWrapperEnabled;
private String gradleUserHome;
private Path workspacePath;
public GradleLibraryResolver() {
this.javaPlugins.addAll(Arrays.asList("java", "application", "groovy", "java-library", "war"));
}
public void setGradleHome(String gradleHome) {
this.gradleHome = gradleHome;
}
public void setGradleVersion(String gradleVersion) {
this.gradleVersion = gradleVersion;
}
public void setGradleWrapperEnabled(boolean gradleWrapperEnabled) {
this.gradleWrapperEnabled = gradleWrapperEnabled;
}
public void setGradleUserHome(String gradleUserHome) {
this.gradleUserHome = gradleUserHome;
}
public void setWorkspacePath(Path workspacePath) {
this.workspacePath = workspacePath;
}
public Map<String, JavaClass> getGradleLibraries() {
return this.gradleLibraries;
}
public Set<String> getJavaConfigurations() {
return this.javaConfigurations;
}
public void resolve() {
Path gradleUserHomePath = (this.gradleUserHome == null) ? Path.of(System.getProperty("user.home"), ".gradle")
: Path.of(this.gradleUserHome);
File libFile = null;
if (this.gradleWrapperEnabled) {
libFile = findLibWithWrapper(gradleUserHomePath);
} else if (this.gradleVersion != null) {
libFile = findLibWithDist(gradleUserHomePath, "gradle-" + this.gradleVersion);
} else if (this.gradleHome != null) {
Path libPath = Path.of(this.gradleHome).resolve("lib");
libFile = findLibFile(libPath.toFile());
} else {
return;
}
if (libFile == null || !libFile.exists()) {
return;
}
try {
JarFile libJar = new JarFile(libFile);
getGradleLibraries(libFile.toPath(), libJar);
File pluginLibFile = findPluginLibFile(libFile.toPath().getParent().resolve(Path.of("plugins")).toFile());
if (pluginLibFile == null) {
return;
}
JarFile pluginLibJar = new JarFile(pluginLibFile);
getGradleLibraries(pluginLibFile.toPath(), pluginLibJar);
resolveJavaConfigurations();
} catch (Exception e) {
// Do Nothing
}
}
private File findLibWithWrapper(Path gradleUserHomePath) {
if (this.workspacePath == null) {
return null;
}
Path propertiesRelativePath = Path.of("gradle", "wrapper", "gradle-wrapper.properties");
Path propertiesPath = this.workspacePath.resolve(propertiesRelativePath);
File propertiesFile = propertiesPath.toFile();
if (!propertiesFile.exists()) {
return null;
}
try (FileInputStream stream = new FileInputStream(propertiesFile)) {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String content = null;
while ((content = reader.readLine()) != null) {
if (content.startsWith("distributionUrl")) {
Pattern p = Pattern.compile("(gradle-(?s)(.*))-bin");
Matcher matcher = p.matcher(content);
if (matcher.find()) {
String gradleDist = matcher.group(1);
return findLibWithDist(gradleUserHomePath, gradleDist);
}
}
}
} catch (IOException e) {
// Do Nothing
}
return null;
}
private File findLibWithDist(Path gradleUserHomePath, String gradleDist) {
Path distPath = gradleUserHomePath.resolve(Path.of("wrapper", "dists"));
File distFolder = searchInFolder(gradleDist, distPath.toFile());
if (distFolder != null && distFolder.exists()) {
Path libPath = distFolder.toPath().resolve("lib");
return findLibFile(libPath.toFile());
}
return null;
}
private File searchInFolder(String gradleDist, File folder) {
for (File file : folder.listFiles()) {
if (file.isDirectory()) {
if (file.getName().equals(gradleDist)) {
return file;
} else {
File searchResult = searchInFolder(gradleDist, file);
if (searchResult != null) {
return searchResult;
}
}
}
}
return null;
}
private File findLibFile(File folder) {
for (File file : folder.listFiles()) {
String name = file.getName();
if (name.startsWith("gradle-core-api") && name.endsWith(".jar")) {
return file;
}
}
// For Gradle version under 5.6, the name of library file is like gradle-core-${version}.jar
for (File file : folder.listFiles()) {
String name = file.getName();
if (name.startsWith("gradle-core") && name.endsWith(".jar")) {
return file;
}
}
return null;
}
private File findPluginLibFile(File folder) {
for (File file : folder.listFiles()) {
String name = file.getName();
if (name.startsWith("gradle-plugins") && name.endsWith(".jar")) {
return file;
}
}
return null;
}
private void getGradleLibraries(Path jarPath, JarFile jarFile) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (!entry.getName().endsWith(".class")) {
continue;
}
ClassParser parser = new ClassParser(jarPath.toString(), entry.getName());
try {
JavaClass javaClass = parser.parse();
String className = javaClass.getClassName();
this.gradleLibraries.put(className, javaClass);
} catch (IOException | ClassFormatException e) {
// Do Nothing
}
}
}
private void resolveJavaConfigurations() {
JavaClass javaPluginClass = this.gradleLibraries.get(GradleLibraryResolver.JAVA_PLUGIN);
if (javaPluginClass == null) {
return;
}
for (Field field : javaPluginClass.getFields()) {
if (field.getName().endsWith("CONFIGURATION_NAME")) {
this.javaConfigurations.add(removeQuotes(field.getConstantValue().toString()));
}
}
}
private static String removeQuotes(String original) {
// for those fields parsed from class files, we get ""values"", so we remove the starting and ending quotes here
if (original.length() < 3) {
return original;
}
return original.substring(1, original.length() - 1);
}
public boolean isJavaPluginsIncluded(Set<String> plugins) {
for (String plugin : plugins) {
if (this.javaPlugins.contains(plugin)) {
return true;
}
}
return false;
}
}

Просмотреть файл

@ -0,0 +1,95 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.semantictokens;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class SemanticToken {
private final TokenType tokenType;
private final int tokenModifiers;
private final int line;
private final int column;
private final int length;
public SemanticToken(int line, int column, int length, TokenType tokenType, int tokenModifiers) {
this.line = line;
this.column = column;
this.length = length;
this.tokenType = tokenType;
this.tokenModifiers = tokenModifiers;
}
public TokenType getTokenType() {
return tokenType;
}
public int getTokenModifiers() {
return tokenModifiers;
}
public int getLine() {
return line;
}
public int getColumn() {
return column;
}
public int getLength() {
return length;
}
// Note: similar logics as JDT.LS, but in groovy AST ranges start from 1
public static List<Integer> encodedTokens(List<SemanticToken> tokens) {
tokens.sort(new Comparator<SemanticToken>() {
@Override
public int compare(final SemanticToken a, final SemanticToken b) {
int lineResult = Integer.valueOf(a.getLine()).compareTo(Integer.valueOf(b.getLine()));
if (lineResult == 0) {
return Integer.valueOf(a.getColumn()).compareTo(Integer.valueOf(b.getColumn()));
}
return lineResult;
}
});
int numTokens = tokens.size();
List<Integer> data = new ArrayList<>(numTokens * 5);
int currentLine = 0;
int currentColumn = 0;
for (int i = 0; i < numTokens; i++) {
SemanticToken token = tokens.get(i);
int line = token.getLine() - 1;
int column = token.getColumn() - 1;
if (line < 0 || column < 0) {
continue;
}
int deltaLine = line - currentLine;
if (deltaLine != 0) {
currentLine = line;
currentColumn = 0;
}
int deltaColumn = column - currentColumn;
currentColumn = column;
// Disallow duplicate/conflict token (if exists)
if (deltaLine != 0 || deltaColumn != 0 || i == 0) {
int tokenTypeIndex = token.getTokenType().ordinal();
int tokenModifiers = token.getTokenModifiers();
data.add(deltaLine);
data.add(deltaColumn);
data.add(token.getLength());
data.add(tokenTypeIndex);
data.add(tokenModifiers);
}
}
return data;
}
}

Просмотреть файл

@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.semantictokens;
import java.util.List;
import org.eclipse.lsp4j.SemanticTokenModifiers;
public enum TokenModifier {
DEFAULT_LIBRARY(SemanticTokenModifiers.DefaultLibrary);
private String genericName;
// See https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html
private static List<String> defaultLibrary = List.of("afterEvaluate", "allprojects", "ant", "apply", "artifacts",
"beforeEvaluate", "buildscript", "configurations", "configure", "copy", "copySpec", "dependencies", "javaexec",
"repositories", "subprojects", "task");
public final int bitmask = 1 << ordinal();
TokenModifier(String genericName) {
this.genericName = genericName;
}
@Override
public String toString() {
return genericName;
}
public static boolean isDefaultLibrary(String method) {
if (TokenModifier.defaultLibrary.contains(method)) {
return true;
}
return false;
}
}

Просмотреть файл

@ -0,0 +1,31 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.semantictokens;
import org.eclipse.lsp4j.SemanticTokenTypes;
public enum TokenType {
FUNCTION(SemanticTokenTypes.Function), PROPERTY(SemanticTokenTypes.Property), VARIABLE(SemanticTokenTypes.Variable),
PARAMETER(SemanticTokenTypes.Parameter);
private String genericName;
TokenType(String genericName) {
this.genericName = genericName;
}
@Override
public String toString() {
return genericName;
}
}

Просмотреть файл

@ -0,0 +1,52 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.utils;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.syntax.SyntaxException;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
public class LSPUtils {
public static Range toRange(SyntaxException exp) {
// LSP Range start from 0, while groovy classes start from 1
return new Range(new Position(exp.getStartLine() - 1, exp.getStartColumn() - 1),
new Position(exp.getEndLine() - 1, exp.getEndColumn() - 1));
}
public static Range toRange(Expression expression) {
// LSP Range start from 0, while groovy expressions start from 1
return new Range(new Position(expression.getLineNumber() - 1, expression.getColumnNumber() - 1),
new Position(expression.getLastLineNumber() - 1, expression.getLastColumnNumber() - 1));
}
public static Range toRange(Statement statement) {
// LSP Range start from 0, while groovy expressions start from 1
return new Range(new Position(statement.getLineNumber() - 1, statement.getColumnNumber() - 1),
new Position(statement.getLastLineNumber() - 1, statement.getLastColumnNumber() - 1));
}
public static Range toDependencyRange(Expression expression) {
// For dependency, the string includes open/close quotes should be excluded
return new Range(new Position(expression.getLineNumber() - 1, expression.getColumnNumber()),
new Position(expression.getLastLineNumber() - 1, expression.getLastColumnNumber() - 2));
}
public static String getStringBeforePosition(String text, Range range, Position position) {
Position start = range.getStart();
// Since it's used for extract dependency info, we doesn't support multiple line
if (start.getLine() != position.getLine()) {
return text;
}
return text.substring(0, position.getCharacter() - start.getCharacter());
}
}

Просмотреть файл

@ -4,3 +4,4 @@ include('extension')
include('npm-package')
include('gradle-plugin-api')
include('gradle-plugin')
include('gradle-language-server')