fix(lsp/java): signature help is not shown sometimes

This commit is contained in:
Akash Yadav
2023-03-11 23:17:25 +05:30
parent 5affaccc1c
commit 1bc3fc7bc1
14 changed files with 170 additions and 34 deletions
@@ -235,7 +235,7 @@ public class IDEEditor extends CodeEditor implements IEditor, ILspEditor {
languageServer.signatureHelp(
new SignatureHelpParams(
getFile().toPath(),
new Position(getCursor().getLeftLine(), getCursor().getLeftColumn()))));
getCursorLSPPosition())));
future.whenComplete(
(help, error) -> {
@@ -743,8 +743,10 @@ public class IDEEditor extends CodeEditor implements IEditor, ILspEditor {
* @param event The content change event.
*/
private void checkForSignatureHelp(@NonNull ContentChangeEvent event) {
final var changeLength = event.getChangedText().length();
if (event.getAction() != ContentChangeEvent.ACTION_INSERT
|| event.getChangedText().length() != 1) {
|| changeLength > 0
&& changeLength <= 2) { // changeLength will be 2 as '(' and ')' are inserted at the same time
return;
}
@@ -880,7 +882,7 @@ public class IDEEditor extends CodeEditor implements IEditor, ILspEditor {
@Override
@SuppressWarnings("unused")
public void executeCommand(Command command) {
public void executeCommand(@Nullable Command command) {
if (command == null) {
LOG.warn("Cannot execute command in editor. Command is null.");
return;
@@ -18,5 +18,12 @@
package com.itsaky.androidide.lsp.api
import com.itsaky.androidide.lsp.models.SnippetDescription
import io.github.rosemoe.sora.lang.completion.snippet.CodeSnippet
fun describeSnippet(prefix: String) = SnippetDescription(prefix.length)
@JvmOverloads
fun describeSnippet(
prefix: String,
deleteSelected: Boolean = true,
snippet: CodeSnippet? = null,
allowCommandExecution: Boolean = false
) = SnippetDescription(prefix.length, deleteSelected, snippet, allowCommandExecution)
@@ -17,7 +17,6 @@
package com.itsaky.androidide.lsp.java.edits
import com.itsaky.androidide.lsp.edits.IEditHandler
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
import com.itsaky.androidide.lsp.models.CompletionItem
@@ -31,13 +30,22 @@ import java.nio.file.Path
*
* @author Akash Yadav
*/
abstract class IJavaEditHandler(protected val file: Path) : IEditHandler {
abstract class AdvancedJavaEditHandler(protected val file: Path) : BaseJavaEditHandler() {
override fun performEdits(item: CompletionItem, editor: CodeEditor, text: Content, line: Int, column: Int, index: Int) {
override fun performEdits(
item: CompletionItem,
editor: CodeEditor,
text: Content,
line: Int,
column: Int,
index: Int
) {
val compiler = JavaCompilerProvider.get(ProjectManager.findModuleForFile(file) ?: return)
if (compiler != null) {
performEdits(compiler, editor, item)
}
executeCommand(editor, item.command)
}
/**
@@ -0,0 +1,40 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.edits
import com.itsaky.androidide.editor.api.ILspEditor
import com.itsaky.androidide.lsp.edits.DefaultEditHandler
import com.itsaky.androidide.lsp.models.Command
import io.github.rosemoe.sora.widget.CodeEditor
/**
* Implementation of [DefaultEditHandler] which avoids reflection in
* [DefaultEditHandler.executeCommand].
*
* @author Akash Yadav
*/
open class BaseJavaEditHandler : DefaultEditHandler() {
override fun executeCommand(editor: CodeEditor, command: Command?) {
if (editor is ILspEditor) {
editor.executeCommand(command)
return
}
super.executeCommand(editor, command)
}
}
@@ -32,7 +32,7 @@ import java.nio.file.Path
* @param file The file in which this edit will be performed.
* @author Akash Yadav
*/
class ClassImportEditHandler(val imports: Set<String>, file: Path) : IJavaEditHandler(file) {
class ClassImportEditHandler(val imports: Set<String>, file: Path) : AdvancedJavaEditHandler(file) {
override fun performEdits(
compiler: JavaCompilerService,
@@ -34,7 +34,7 @@ class MultipleClassImportEditHandler(
private val classes: Set<String>,
private val imported: Set<String>,
file: Path
) : IJavaEditHandler(file) {
) : AdvancedJavaEditHandler(file) {
private val log = ILogger.newInstance(javaClass.simpleName)
@@ -0,0 +1,77 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.models
import com.itsaky.androidide.lsp.edits.IEditHandler
import com.itsaky.androidide.lsp.java.edits.BaseJavaEditHandler
import com.itsaky.androidide.lsp.models.Command
import com.itsaky.androidide.lsp.models.CompletionItem
import com.itsaky.androidide.lsp.models.CompletionItemKind
import com.itsaky.androidide.lsp.models.ICompletionData
import com.itsaky.androidide.lsp.models.InsertTextFormat
import com.itsaky.androidide.lsp.models.MatchLevel
import com.itsaky.androidide.lsp.models.TextEdit
/**
* Completion item model for java completion items.
*
* @author Akash Yadav
*/
class JavaCompletionItem(
label: String,
detail: String,
insertText: String?,
insertTextFormat: InsertTextFormat?,
sortText: String?,
command: Command?,
kind: CompletionItemKind,
matchLevel: MatchLevel,
additionalTextEdits: List<TextEdit>?,
data: ICompletionData?,
// Override the default edit handler
editHandler: IEditHandler = BaseJavaEditHandler()
) :
CompletionItem(
label,
detail,
insertText,
insertTextFormat,
sortText,
command,
kind,
matchLevel,
additionalTextEdits,
data,
editHandler
) {
constructor() :
this(
"", // label
"", // detail
null, // insertText
null, // insertTextFormat
null, // sortText
null, // command
CompletionItemKind.NONE, // kind
MatchLevel.NO_MATCH, // match level
ArrayList(), // additionalEdits
null // data
)
}
@@ -22,6 +22,7 @@ import com.itsaky.androidide.lsp.api.describeSnippet
import com.itsaky.androidide.lsp.java.compiler.CompileTask
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
import com.itsaky.androidide.lsp.java.edits.ClassImportEditHandler
import com.itsaky.androidide.lsp.java.models.JavaCompletionItem
import com.itsaky.androidide.lsp.java.providers.BaseJavaServiceProvider
import com.itsaky.androidide.lsp.java.utils.EditHelper
import com.itsaky.androidide.lsp.models.ClassCompletionData
@@ -147,7 +148,7 @@ abstract class IJavaCompletionProvider(
): CompletionItem {
abortIfCancelled()
abortCompletionIfCancelled()
val item = CompletionItem()
val item = JavaCompletionItem()
item.setLabel(keyword)
item.kind = KEYWORD
item.detail = "keyword"
@@ -166,7 +167,7 @@ abstract class IJavaCompletionProvider(
abortIfCancelled()
abortCompletionIfCancelled()
val first = overloads[0]
val item = CompletionItem()
val item = JavaCompletionItem()
item.setLabel(first.simpleName.toString())
item.kind = CompletionItemKind.METHOD
item.detail = printMethodDetail(first)
@@ -186,7 +187,7 @@ abstract class IJavaCompletionProvider(
item.command = Command("Trigger Parameter Hints", Command.TRIGGER_PARAMETER_HINTS)
}
item.insertTextFormat = SNIPPET // Snippet
item.snippetDescription = describeSnippet(partial)
item.snippetDescription = describeSnippet(prefix = partial, allowCommandExecution = true)
}
return item
}
@@ -217,7 +218,7 @@ abstract class IJavaCompletionProvider(
abortIfCancelled()
abortCompletionIfCancelled()
val item = CompletionItem()
val item = JavaCompletionItem()
item.setLabel(element.simpleName.toString())
item.kind = kind(element)
item.detail = element.toString()
@@ -247,7 +248,7 @@ abstract class IJavaCompletionProvider(
): CompletionItem {
abortIfCancelled()
abortCompletionIfCancelled()
val item = CompletionItem()
val item = JavaCompletionItem()
item.setLabel(simpleName(className).toString())
item.kind = CompletionItemKind.CLASS
item.detail = packageName(className).toString()
@@ -280,7 +281,7 @@ abstract class IJavaCompletionProvider(
if (packageName == name) {
packageName = " "
}
return CompletionItem().apply {
return JavaCompletionItem().apply {
setLabel(simpleName)
this.detail = packageName
this.insertText = simpleName
@@ -22,6 +22,7 @@ import com.itsaky.androidide.lsp.api.describeSnippet
import com.itsaky.androidide.lsp.java.compiler.CompileTask
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
import com.itsaky.androidide.lsp.java.edits.MultipleClassImportEditHandler
import com.itsaky.androidide.lsp.java.models.JavaCompletionItem
import com.itsaky.androidide.lsp.java.utils.EditHelper
import com.itsaky.androidide.lsp.java.utils.EditHelper.repeatSpaces
import com.itsaky.androidide.lsp.java.utils.JavaPoetUtils.Companion.buildMethod
@@ -172,7 +173,7 @@ class ScopeCompletionProvider(
abortIfCancelled()
abortCompletionIfCancelled()
val item = CompletionItem()
val item = JavaCompletionItem()
item.setLabel(methodSpec.name)
item.kind = com.itsaky.androidide.lsp.models.CompletionItemKind.METHOD
item.detail = method.returnType.toString() + " " + method
@@ -20,7 +20,7 @@ package com.itsaky.androidide.lsp.java.providers.completion
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.lsp.api.ICompletionCancelChecker
import com.itsaky.androidide.lsp.api.describeSnippet
import com.itsaky.androidide.lsp.java.parser.ParseTask
import com.itsaky.androidide.lsp.java.models.JavaCompletionItem
import com.itsaky.androidide.lsp.models.CompletionItem
import com.itsaky.androidide.lsp.models.CompletionItemKind.SNIPPET
import com.itsaky.androidide.lsp.models.CompletionResult
@@ -83,7 +83,7 @@ class TopLevelSnippetsProvider {
): CompletionItem {
abortIfCancelled()
abortCompletionIfCancelled()
val item = CompletionItem()
val item = JavaCompletionItem()
item.setLabel(label)
item.kind = SNIPPET
item.insertText = snippet
@@ -53,7 +53,7 @@ open class DefaultEditHandler : IEditHandler {
performEditsInternal(item, editor, text, line, column, index)
}
private fun performEditsInternal(
protected open fun performEditsInternal(
item: CompletionItem,
editor: CodeEditor,
text: Content,
@@ -89,23 +89,27 @@ open class DefaultEditHandler : IEditHandler {
column: Int,
index: Int
) {
item.snippetDescription!!
val snippetDescription = item.snippetDescription!!
val snippet = CodeSnippetParser.parse(item.insertText)
val prefixLength = item.snippetDescription!!.selectedLength
val prefixLength = snippetDescription.selectedLength
val selectedText = text.subSequence(index - prefixLength, index).toString()
var actionIndex = index
if (item.snippetDescription!!.deleteSelected) {
if (snippetDescription.deleteSelected) {
text.delete(index - prefixLength, index)
actionIndex -= prefixLength
}
editor.snippetController.startSnippet(actionIndex, snippet, selectedText)
if (snippetDescription.allowCommandExecution) {
executeCommand(editor, item.command)
}
}
protected open fun executeCommand(editor: CodeEditor, command: Command?) {
if (command == null) {
return
}
try {
val klass = editor::class.java
val method = klass.getMethod("executeCommand", Command::class.java)
@@ -124,7 +124,8 @@ open class CompletionItem(
var kind: CompletionItemKind,
var matchLevel: MatchLevel,
var additionalTextEdits: List<TextEdit>?,
var data: ICompletionData?
var data: ICompletionData?,
var editHandler: IEditHandler = DefaultEditHandler()
) :
io.github.rosemoe.sora.lang.completion.CompletionItem(label, detail), Comparable<CompletionItem> {
@@ -147,7 +148,6 @@ open class CompletionItem(
}
var insertTextFormat: InsertTextFormat = insertTextFormat ?: PLAIN_TEXT
var editHandler: IEditHandler = DefaultEditHandler()
var additionalEditHandler: IEditHandler? = null
var snippetDescription: SnippetDescription? = null
var overrideTypeText: String? = null
@@ -268,7 +268,8 @@ data class SnippetDescription
constructor(
val selectedLength: Int,
val deleteSelected: Boolean = true,
val snippet: CodeSnippet? = null
val snippet: CodeSnippet? = null,
val allowCommandExecution: Boolean = false
)
data class Command(var title: String, var command: String) {
@@ -17,16 +17,14 @@
package com.itsaky.androidide.lsp.xml.edits
import com.itsaky.androidide.lsp.edits.DefaultEditHandler
/**
* Handles insertion of attribute values.
*
* @author Akash Yadav
*/
class AttrValueEditHandler : QualifiedValueEditHandler() {
override fun isPartialPart(c: Char): Boolean {
return super.isPartialPart(c) || c in charArrayOf('@', '.', ':', '/')
}
}
}
@@ -18,10 +18,7 @@
package com.itsaky.androidide.lsp.xml.edits
import com.itsaky.androidide.lsp.edits.DefaultEditHandler
import com.itsaky.androidide.lsp.edits.IEditHandler
import com.itsaky.androidide.lsp.models.CompletionItem
import io.github.rosemoe.sora.text.Content
import io.github.rosemoe.sora.widget.CodeEditor
/**
* Handles edits for attribute values with qualified binary names. The default implementation in
@@ -30,7 +27,7 @@ import io.github.rosemoe.sora.widget.CodeEditor
* @author Akash Yadav
*/
open class QualifiedValueEditHandler : DefaultEditHandler() {
override fun isPartialPart(c: Char): Boolean {
return super.isPartialPart(c) || c == '.' // Tags can contain '.' as well
}