Skip to content

Commit

Permalink
Language Server fixes and improvements (#235)
Browse files Browse the repository at this point in the history
* lots of changes & fixes

* moving classes & stuff

* custom uri decoder/encoder

* dont insert . after properties

* better sorting of methods and properties

* classes inherit banned methods

* Inline icons for Object mappers in vsc (#238)

* prototype of inline icons for ObjectMapper

* inline icons for ObjectMapper

* fix icon uri encoding

* fix icon position

* fix some issues

* add setting to disable icons

* blacklist some mod support methods

* fix weird lsp error

* fix script error

* fix suggesting private methods, fix signatures, fix not suggesting overloads

* replace todo with comment

* show last array param as varargs

* only suggest public fields

* add tooltips for inline icons

* ban packages from groovy and lsp

* fix class and member suggestions

* better completion for keywords and methods

* dont insert tab location on varargs

* insert imports after varargs

* blacklist some mixin methods

* mark texture binder as experimental
  • Loading branch information
brachy84 authored Oct 4, 2024
1 parent 2de5103 commit be47fbd
Show file tree
Hide file tree
Showing 64 changed files with 2,542 additions and 1,459 deletions.
192 changes: 96 additions & 96 deletions editors/vscode/package-lock.json

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion editors/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
"default": 25564,
"title": "Language server port",
"description": "Port to connect to GroovyScript mod"
},
"groovyscript.enableIcons": {
"type": "boolean",
"default": true,
"title": "Enable Inline Icons",
"description": "Enables preview icons of some global methods like item()"
}
}
},
Expand All @@ -62,6 +68,6 @@
"devDependencies": {
"@types/node": "^20.11.17",
"@types/vscode": "^1.81.0",
"esbuild": "^0.20.0"
"esbuild": "0.20.2"
}
}
91 changes: 91 additions & 0 deletions editors/vscode/src/features/TextureDecoration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { CancellationToken, Disposable, ProviderResult, TextDocument, Range as VRange } from 'vscode';
import { ClientCapabilities, DocumentColorOptions, DocumentSelector, ensure, FeatureClient, MessageDirection, PartialResultParams, ProtocolRequestType, RequestHandler, ServerCapabilities, StaticRegistrationOptions, TextDocumentIdentifier, TextDocumentLanguageFeature, TextDocumentRegistrationOptions, WorkDoneProgressOptions, WorkDoneProgressParams } from 'vscode-languageclient';
import { registerTextureDecorationProvider } from '../languageProviders/TextureDecorationLanguageProvider';

export interface TextureDecorationParams extends WorkDoneProgressParams, PartialResultParams {
/**
* The text document.
*/
textDocument: TextDocumentIdentifier;
}

export interface TextureDecorationInformation {
range: VRange;
textureUri: string;
tooltips: string[];
}

export interface TextureDecorationOptions extends WorkDoneProgressOptions {
}

export interface TextureDecorationRegistrationOptions extends TextDocumentRegistrationOptions, StaticRegistrationOptions, DocumentColorOptions {
}

export interface ProvideTextureDecorationsSignature {
(document: TextDocument, token: CancellationToken): ProviderResult<TextureDecorationInformation[]>;
}

export interface TextureDecorationProvider {
provideTextureDecoration(document: TextDocument, token: CancellationToken): ProviderResult<TextureDecorationInformation[]>;
}

export interface TextureDecorationMiddleware {
provideTextureDecorations?: (this: void, document: TextDocument, token: CancellationToken, next: ProvideTextureDecorationsSignature) => ProviderResult<TextureDecorationInformation[]>;
}

export namespace TextureDecorationRequest {
export const method: 'groovyScript/textureDecoration' = 'groovyScript/textureDecoration';
export const messageDirection = MessageDirection.clientToServer;
export const type = new ProtocolRequestType<TextureDecorationParams, TextureDecorationInformation[], TextureDecorationInformation[], void, TextureDecorationRegistrationOptions>(method);
export type HandlerSignature = RequestHandler<TextureDecorationParams, TextureDecorationInformation[], void>;
}

export class TextureDecorationFeature extends TextDocumentLanguageFeature<boolean | TextureDecorationOptions, TextureDecorationRegistrationOptions, TextureDecorationProvider, TextureDecorationMiddleware> {
constructor(client: FeatureClient<TextureDecorationMiddleware>) {
super(client, TextureDecorationRequest.type);
}
fillClientCapabilities(capabilities: ClientCapabilities): void {
ensure(ensure(capabilities, 'experimental')!, 'textureDecorationProvider')!.dynamicRegistration = true;
}
initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {
const [id, options] = this.getRegistration(documentSelector, capabilities.experimental.textureDecorationProvider);
if (!id || !options) {
return;
}
this.register({ id: id, registerOptions: options });
}
protected registerLanguageProvider(options: TextureDecorationRegistrationOptions, id: string): [Disposable, TextureDecorationProvider] {
const selector = options.documentSelector!;

const provider: TextureDecorationProvider = {
provideTextureDecoration: (document, token) => {
const client = this._client;
const provideTextureDecorations: ProvideTextureDecorationsSignature = (document, token) => {
const requestParams: TextureDecorationParams = {
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
};

return client.sendRequest(TextureDecorationRequest.type, requestParams, token).then((result) => {
if (token.isCancellationRequested) {
return null;
}
return result.map<TextureDecorationInformation>(decoration => ({
range: decoration.range,
textureUri: client.protocol2CodeConverter.asUri(decoration.textureUri).toString(true),
tooltips: decoration.tooltips
}));
}, (error) => {
return client.handleFailedRequest(TextureDecorationRequest.type, token, error, null);
});
};
const middleware = client.middleware;
return middleware.provideTextureDecorations
? middleware.provideTextureDecorations(document, token, provideTextureDecorations)
: provideTextureDecorations(document, token);
},
};

return [registerTextureDecorationProvider(this._client.protocol2CodeConverter.asDocumentSelector(selector), provider), provider];
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as vscode from "vscode";
import { DocumentSelector, Disposable, window as vWindow, workspace as vWorkspace, CancellationTokenSource, TextEditor, languages, DecorationOptions, Uri } from "vscode";
import { TextureDecorationInformation, TextureDecorationProvider } from "../features/TextureDecoration";

export function registerTextureDecorationProvider(selector: DocumentSelector, provider: TextureDecorationProvider): Disposable {
let cancellationSource = new CancellationTokenSource();

function cancel() {
cancellationSource.cancel();
cancellationSource.dispose();
cancellationSource = new CancellationTokenSource();
}

async function doDecorate(editor: TextEditor): Promise<void> {
const configuration = vscode.workspace.getConfiguration("groovyscript");
if (configuration.get<boolean>("enableIcons", true)) {
const result = await provider.provideTextureDecoration(editor.document, cancellationSource.token);
if (result) {
decorate(editor, result);
}
return;
}
removeDecoration(editor)
return;
}

const editorChangedHandler = async (editor: TextEditor | undefined): Promise<void> => {
if (editor && languages.match(selector, editor.document)) {
cancel();
await doDecorate(editor)
}
};
const changedActiveTextEditor = vWindow.onDidChangeActiveTextEditor(editorChangedHandler);

editorChangedHandler(vWindow.activeTextEditor);

const changedDocumentText = vWorkspace.onDidChangeTextDocument(async event => {
if (vWindow.activeTextEditor?.document === event.document && languages.match(selector, event.document)) {
cancel();
await doDecorate(vWindow.activeTextEditor)
}
})

return new Disposable(() => {
changedActiveTextEditor.dispose();
changedDocumentText.dispose();
});
}

function removeDecoration(textEditor: TextEditor) {
textEditor.setDecorations(decorationStyle, [])
}

function decorate(textEditor: TextEditor, decorations: TextureDecorationInformation[]) {
textEditor.setDecorations(decorationStyle, decorations.map<DecorationOptions>(decoration => ({
range: decoration.range,
hoverMessage: decoration.tooltips,
renderOptions: {
before: {
contentIconPath: Uri.parse(decoration.textureUri, true),
}
}
})))
}

const decorationStyle = vWindow.createTextEditorDecorationType({})
10 changes: 9 additions & 1 deletion editors/vscode/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as net from "net";
import * as lc from "vscode-languageclient/node";
import * as vscode from "vscode";
import { extensionStatusBar } from "./gui/extensionStatusBarProvider";
import { TextureDecorationFeature, TextureDecorationMiddleware } from "./features/TextureDecoration";
import { FeatureClient } from "vscode-languageclient/node";

let client: lc.LanguageClient;
let outputChannel = vscode.window.createOutputChannel("GroovyScript Language Server");
Expand Down Expand Up @@ -37,7 +39,9 @@ async function startClient() {
traceOutputChannel,
};

client = new lc.LanguageClient("groovyscript", "GroovyScript", serverOptions, clientOptions)
client = new lc.LanguageClient("groovyscript", "GroovyScript", serverOptions, clientOptions);

registerFeatures();

try {
await client.start();
Expand All @@ -51,6 +55,10 @@ async function startClient() {
outputChannel.appendLine("Connected to GroovyScript Language Server");
}

function registerFeatures() {
client.registerFeature(new TextureDecorationFeature(<FeatureClient<TextureDecorationMiddleware>>client));
}

async function stopClient() {
if (!client || !client.isRunning()) return;
try {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/cleanroommc/groovyscript/GroovyScript.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import com.cleanroommc.groovyscript.sandbox.*;
import com.cleanroommc.groovyscript.sandbox.mapper.GroovyDeobfMapper;
import com.cleanroommc.groovyscript.sandbox.meta.GrSMetaClassCreationHandle;
import com.cleanroommc.groovyscript.server.GroovyScriptLanguageServer;
import com.cleanroommc.groovyscript.server.GroovyScriptLanguageServerImpl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import groovy.lang.GroovySystem;
Expand Down Expand Up @@ -328,7 +328,7 @@ public static void postScriptRunResult(ICommandSender sender, boolean onlyLogFai

public static boolean runLanguageServer() {
if (languageServerThread != null) return false;
languageServerThread = new Thread(() -> GroovyScriptLanguageServer.listen(getSandbox().getScriptRoot()));
languageServerThread = new Thread(() -> GroovyScriptLanguageServerImpl.listen(getSandbox().getScriptRoot()));
languageServerThread.start();
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ public static Collection<GroovyContainer<? extends GroovyPropertyContainer>> get
return Collections.unmodifiableList(containerList);
}

private ModSupport() {
}
private ModSupport() {}

@GroovyBlacklist
@ApiStatus.Internal
public void setup(ASMDataTable dataTable) {
for (ASMDataTable.ASMData data : dataTable.getAll(GroovyPlugin.class.getName().replace('.', '/'))) {
Expand All @@ -155,6 +155,7 @@ public void setup(ASMDataTable dataTable) {
}
}

@GroovyBlacklist
private void registerContainer(GroovyPlugin container) {
if (container instanceof GroovyContainer) {
GroovyScript.LOGGER.error("GroovyPlugin must not extend {}", GroovyContainer.class.getSimpleName());
Expand Down Expand Up @@ -185,6 +186,7 @@ private void registerContainer(GroovyPlugin container) {
externalPluginClasses.add(container.getClass());
}

@GroovyBlacklist
void registerContainer(GroovyContainer<?> container) {
if (containerList.contains(container) || containers.containsKey(container.getModId())) {
throw new IllegalStateException("Container already present!");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cleanroommc.groovyscript.compat.vanilla;

import com.cleanroommc.groovyscript.api.GroovyBlacklist;
import com.cleanroommc.groovyscript.api.IIngredient;
import com.cleanroommc.groovyscript.api.INBTResourceStack;
import com.cleanroommc.groovyscript.api.INbtIngredient;
Expand Down Expand Up @@ -35,15 +36,18 @@ static ItemStackMixinExpansion of(ItemStack stack) {

void grs$setTransformer(ItemStackTransformer transformer);

@GroovyBlacklist
void grs$setNbtMatcher(Predicate<NBTTagCompound> matcher);

@GroovyBlacklist
void grs$setMatcher(Predicate<ItemStack> matcher);

@Nullable
String grs$getMark();

void grs$setMark(String mark);

@GroovyBlacklist
default boolean grs$isEmpty() {
return grs$getItemStack().isEmpty();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cleanroommc.groovyscript.core.mixin;

import com.cleanroommc.groovyscript.api.GroovyBlacklist;
import com.cleanroommc.groovyscript.compat.vanilla.ItemStackMixinExpansion;
import com.cleanroommc.groovyscript.compat.vanilla.ItemStackTransformer;
import net.minecraft.item.ItemStack;
Expand All @@ -22,53 +23,62 @@ public abstract class ItemStackMixin implements ItemStackMixinExpansion {
@Unique
protected String groovyScript$mark;

@GroovyBlacklist
@Override
public ItemStack grs$getItemStack() {
return (ItemStack) (Object) this;
}

@GroovyBlacklist
@Override
public ItemStackTransformer grs$getTransformer() {
return groovyScript$transformer;
}

@GroovyBlacklist
@Override
public Predicate<ItemStack> grs$getMatcher() {
return groovyScript$matchCondition;
}

@GroovyBlacklist
@Override
public Predicate<NBTTagCompound> grs$getNbtMatcher() {
return groovyScript$nbtMatcher;
}

@GroovyBlacklist
@Override
public void grs$setTransformer(ItemStackTransformer transformer) {
if (grs$getItemStack() != ItemStack.EMPTY) {
this.groovyScript$transformer = transformer;
}
}

@GroovyBlacklist
@Override
public void grs$setMatcher(Predicate<ItemStack> matcher) {
if (grs$getItemStack() != ItemStack.EMPTY) {
this.groovyScript$matchCondition = matcher;
}
}

@GroovyBlacklist
@Override
public void grs$setNbtMatcher(Predicate<NBTTagCompound> nbtMatcher) {
if (grs$getItemStack() != ItemStack.EMPTY) {
this.groovyScript$nbtMatcher = nbtMatcher;
}
}

@GroovyBlacklist
@Nullable
@Override
public String grs$getMark() {
return groovyScript$mark;
}

@GroovyBlacklist
@Override
public void grs$setMark(String mark) {
this.groovyScript$mark = mark;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ public abstract class ModuleNodeMixin {
public void init(SourceUnit context, CallbackInfo ci) {
// auto set package name
String script = context.getName();
if (!RunConfig.isGroovyFile(script)) {
String rel;
if (!RunConfig.isGroovyFile(script) || (rel = FileUtil.relativizeNullable(GroovyScript.getScriptPath(), script)) == null) {
// probably not a script file
// can happen with traits
return;
}
String rel = FileUtil.relativize(GroovyScript.getScriptPath(), script);
int i = rel.lastIndexOf(File.separatorChar);
if (i >= 0) {
// inject correct package declaration into script
Expand All @@ -43,7 +43,8 @@ public void init(SourceUnit context, CallbackInfo ci) {
@Inject(method = "setPackage", at = @At("HEAD"), cancellable = true)
public void setPackage(PackageNode packageNode, CallbackInfo ci) {
if (this.packageNode == null || this.context == null) return;
if (!RunConfig.isGroovyFile(this.context.getName())) {
String rel;
if (!RunConfig.isGroovyFile(this.context.getName()) || (rel = FileUtil.relativizeNullable(GroovyScript.getScriptPath(), this.context.getName())) == null) {
// probably not a script file
// can happen with traits
return;
Expand All @@ -52,7 +53,6 @@ public void setPackage(PackageNode packageNode, CallbackInfo ci) {
String cur = this.packageNode.getName();
String newName = packageNode.getName();
if (!cur.equals(newName)) {
String rel = FileUtil.relativize(GroovyScript.getScriptPath(), this.context.getName());
GroovyLog.get().error("Expected package {} but got {} in script {}", cur, newName, rel);
}
if (this.packageNode.getAnnotations() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static void init() {

@Nullable
public static Object getGameObject(String name, String mainArg, Object... args) {
return ObjectMapperManager.getGameObject(name, mainArg, args);
return ObjectMapperManager.getGameObject(false, name, mainArg, args);
}

public static boolean hasGameObjectHandler(String key) {
Expand Down
Loading

0 comments on commit be47fbd

Please sign in to comment.