Skip to content

Commit

Permalink
added ExtractType and ExtractInterface refactoring via rename lib
Browse files Browse the repository at this point in the history
added autodiscovery of classpath - deprecating haxe.renameSourceFolders setting for Haxe 4
refactored rename cache
fixed repeated rename operations breaking rename cache
fixed rename / refactor operations not working on unsaved files
  • Loading branch information
AlexHaxe committed Nov 7, 2024
1 parent 900c057 commit 9a93c9b
Show file tree
Hide file tree
Showing 13 changed files with 682 additions and 480 deletions.
4 changes: 2 additions & 2 deletions .haxerc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"version": "b537e99",
"version": "21e2c18",
"resolveLibs": "scoped"
}
}
6 changes: 3 additions & 3 deletions haxe_libraries/rename.hxml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# @install: lix --silent download "haxelib:/rename#2.3.0" into rename/2.3.0/haxelib
-cp ${HAXE_LIBCACHE}/rename/2.3.0/haxelib/src
-D rename=2.3.0
# @install: lix --silent download "gh://github.com/HaxeCheckstyle/haxe-rename#811d14596e7d287d371b056ab0267b9a5d5117f4" into rename/2.3.1/github/811d14596e7d287d371b056ab0267b9a5d5117f4
-cp ${HAXE_LIBCACHE}/rename/2.3.1/github/811d14596e7d287d371b056ab0267b9a5d5117f4/src
-D rename=2.3.1
12 changes: 11 additions & 1 deletion src/haxeLanguageServer/Context.hx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ import haxeLanguageServer.features.haxe.GotoDefinitionFeature;
import haxeLanguageServer.features.haxe.GotoImplementationFeature;
import haxeLanguageServer.features.haxe.GotoTypeDefinitionFeature;
import haxeLanguageServer.features.haxe.InlayHintFeature;
import haxeLanguageServer.features.haxe.RefactorFeature;
import haxeLanguageServer.features.haxe.RenameFeature;
import haxeLanguageServer.features.haxe.SignatureHelpFeature;
import haxeLanguageServer.features.haxe.WorkspaceSymbolsFeature;
import haxeLanguageServer.features.haxe.codeAction.CodeActionFeature;
import haxeLanguageServer.features.haxe.documentSymbols.DocumentSymbolsFeature;
import haxeLanguageServer.features.haxe.foldingRange.FoldingRangeFeature;
import haxeLanguageServer.features.haxe.refactoring.RefactorCache;
import haxeLanguageServer.server.DisplayResult;
import haxeLanguageServer.server.HaxeServer;
import haxeLanguageServer.server.ServerRecording;
Expand Down Expand Up @@ -64,6 +66,7 @@ class Context {
@:nullSafety(Off) public var findReferences(default, null):FindReferencesFeature;
@:nullSafety(Off) public var determinePackage(default, null):DeterminePackageFeature;
@:nullSafety(Off) public var diagnostics(default, null):DiagnosticsFeature;
@:nullSafety(Off) public var refactorCache(default, null):RefactorCache;
public var experimental(default, null):Null<ExperimentalCapabilities>;

var activeEditor:Null<DocumentUri>;
Expand Down Expand Up @@ -376,7 +379,8 @@ class Context {
new GotoTypeDefinitionFeature(this);
findReferences = new FindReferencesFeature(this);
determinePackage = new DeterminePackageFeature(this);
new RenameFeature(this);
refactorCache = new RefactorCache(this);
new RenameFeature(this, refactorCache);
diagnostics = new DiagnosticsFeature(this);
new CodeActionFeature(this);
new CodeLensFeature(this);
Expand All @@ -390,6 +394,7 @@ class Context {
} else {
haxeServer.restart(reason, function() {
onServerStarted();
refactorCache.initClassPaths();
if (activeEditor != null) {
publishDiagnostics(activeEditor);
}
Expand All @@ -416,6 +421,7 @@ class Context {
serverRecording.onDidChangeTextDocument(event);
invalidateFile(uri);
documents.onDidChangeTextDocument(event);
refactorCache.invalidateFile(uri.toFsPath().toString());
}
}

Expand Down Expand Up @@ -445,6 +451,7 @@ class Context {
case Deleted:
diagnostics.clearDiagnostics(change.uri);
invalidateFile(change.uri);
refactorCache.invalidateFile(change.uri.toFsPath().toString());
case _:
}
}
Expand All @@ -466,6 +473,9 @@ class Context {
}

function onDidChangeActiveTextEditor(params:{uri:DocumentUri}) {
if (!params.uri.isFile() || !params.uri.isHaxeFile()) {
return;
}
activeEditor = params.uri;
final document = documents.getHaxe(params.uri);
if (document == null) {
Expand Down
2 changes: 1 addition & 1 deletion src/haxeLanguageServer/features/haxe/InlayHintFeature.hx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class InlayHintFeature {
if (root == null) {
return reject.noFittingDocument(uri);
}
#if debug
#if debug_inlayhints
trace('[inlayHints] requesting inlay hints for $fileName lines ${params.range.start.line}-${params.range.end.line}');
#end
removeCancelledRequests();
Expand Down
147 changes: 147 additions & 0 deletions src/haxeLanguageServer/features/haxe/RefactorFeature.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package haxeLanguageServer.features.haxe;

import haxeLanguageServer.features.haxe.codeAction.CodeActionFeature.CodeActionContributor;
import haxeLanguageServer.features.haxe.codeAction.CodeActionFeature.CodeActionResolveType;
import haxeLanguageServer.features.haxe.refactoring.EditList;
import haxeLanguageServer.features.haxe.refactoring.RefactorCache;
import js.lib.Promise;
import jsonrpc.ResponseError;
import languageServerProtocol.Types.CodeAction;
import languageServerProtocol.Types.CodeActionKind;
import languageServerProtocol.Types.WorkspaceEdit;
import refactor.RefactorResult;
import refactor.Refactoring;
import refactor.refactor.RefactorType;

class RefactorFeature implements CodeActionContributor {
final context:Context;
final refactorCache:RefactorCache;

public function new(context:Context) {
this.context = context;
this.refactorCache = context.refactorCache;
}

public function createCodeActions(params:CodeActionParams):Array<CodeAction> {
var actions:Array<CodeAction> = [];
if (params.context.only != null) {
if (params.context.only.contains(Refactor)) {}
if (params.context.only.contains(RefactorExtract)) {
actions = actions.concat(extractRefactors(params));
}
if (params.context.only.contains(RefactorRewrite)) {}
// if (params.context.only.contains(RefactorMove)) {} // upcoming LSP 3.18.0
if (params.context.only.contains(RefactorInline)) {}
} else {
actions = actions.concat(extractRefactors(params));
}

return actions;
}

function extractRefactors(params:CodeActionParams):Array<CodeAction> {
var actions:Array<CodeAction> = [];
final canRefactorContext = refactorCache.makeCanRefactorContext(context.documents.getHaxe(params.textDocument.uri), params.range);
if (canRefactorContext == null) {
return actions;
}
refactorCache.updateSingleFileCache(canRefactorContext.what.fileName);
var refactorRunners:Array<Null<RefactorInfo>> = [getRefactorInfo(ExtractType), getRefactorInfo(ExtractInterface)];
for (refactor in refactorRunners) {
if (refactor == null) {
continue;
}
switch (Refactoring.canRefactor(refactor.refactorType, canRefactorContext)) {
case Unsupported:
case Supported(title):
actions.push(makeEmptyCodeAction(title, refactor.codeActionKind, params, refactor.type));
}
}

return actions;
}

function getRefactorInfo(type:CodeActionResolveType):Null<RefactorInfo> {
switch (type) {
case MissingArg | ChangeFinalToVar | AddTypeHint:
return null;
case ExtractType:
return {
refactorType: RefactorExtractType,
type: type,
codeActionKind: RefactorExtract,
title: "extractType",
prefix: "[ExtractType]"
}
case ExtractInterface:
return {
refactorType: RefactorExtractInterface,
type: type,
codeActionKind: RefactorExtract,
title: "extractInterface",
prefix: "[ExtractInterface]"
}
}
}

function makeEmptyCodeAction(title:String, kind:CodeActionKind, params:CodeActionParams, type:CodeActionResolveType):CodeAction {
return {
title: title,
kind: kind,
data: {params: params, type: type}
}
}

public function createCodeActionEdits(context:Context, type:CodeActionResolveType, action:CodeAction, params:CodeActionParams):Promise<WorkspaceEdit> {
var endProgress = context.startProgress("Performing Refactor Operation…");

var actions:Array<CodeAction> = [];
final editList:EditList = new EditList();
final refactorContext = refactorCache.makeRefactorContext(context.documents.getHaxe(params.textDocument.uri), params.range, editList);
if (refactorContext == null) {
return Promise.reject("failed to make refactor context");
}
var info = getRefactorInfo(type);
if (info == null) {
return Promise.reject("failed to make refactor context");
}
final onResolve:(?result:Null<Dynamic>, ?debugInfo:Null<String>) -> Void = context.startTimer("refactor/" + info.title);
refactorCache.updateFileCache();
return Refactoring.doRefactor(info.refactorType, refactorContext).then((result:RefactorResult) -> {
var promise = switch (result) {
case NoChange:
trace(info.prefix + " no change");
Promise.reject(ResponseError.internalError("no change"));
case NotFound:
var msg = 'could not find identifier at "${refactorContext.what.fileName}@${refactorContext.what.posStart}-${refactorContext.what.posEnd}"';
trace('${info.prefix} $msg');
Promise.reject(ResponseError.internalError(msg));
case Unsupported(name):
trace('${info.prefix} refactoring not supported for "$name"');
Promise.reject(ResponseError.internalError('refactoring not supported for "$name"'));
case DryRun:
trace(info.prefix + " dry run");
Promise.reject(ResponseError.internalError("dry run"));
case Done:
var edit:WorkspaceEdit = {documentChanges: editList.documentChanges};
Promise.resolve(edit);
}
endProgress();
onResolve(null, editList.documentChanges.length + " changes");
return promise;
}).catchError((msg) -> {
trace('${info.prefix} error: $msg');
endProgress();
onResolve(null, "error");
Promise.reject(ResponseError.internalError('$msg'));
});
}
}

typedef RefactorInfo = {
var refactorType:RefactorType;
var type:CodeActionResolveType;
var codeActionKind:CodeActionKind;
var title:String;
var prefix:String;
}
Loading

0 comments on commit 9a93c9b

Please sign in to comment.