diff --git a/editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.java b/editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.java
index 8261fb02b..554c2ec4a 100644
--- a/editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.java
+++ b/editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.java
@@ -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;
diff --git a/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/helpers.kt b/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/helpers.kt
index 406b2a2e6..398b40b97 100644
--- a/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/helpers.kt
+++ b/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/helpers.kt
@@ -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)
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/IJavaEditHandler.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/AdvancedJavaEditHandler.kt
similarity index 86%
rename from lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/IJavaEditHandler.kt
rename to lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/AdvancedJavaEditHandler.kt
index ed0d8199d..f067ea64a 100644
--- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/IJavaEditHandler.kt
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/AdvancedJavaEditHandler.kt
@@ -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)
}
/**
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/BaseJavaEditHandler.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/BaseJavaEditHandler.kt
new file mode 100644
index 000000000..71db6f6b0
--- /dev/null
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/BaseJavaEditHandler.kt
@@ -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 .
+ */
+
+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)
+ }
+}
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/ClassImportEditHandler.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/ClassImportEditHandler.kt
index b9665e9ea..1f73882dc 100644
--- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/ClassImportEditHandler.kt
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/ClassImportEditHandler.kt
@@ -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, file: Path) : IJavaEditHandler(file) {
+class ClassImportEditHandler(val imports: Set, file: Path) : AdvancedJavaEditHandler(file) {
override fun performEdits(
compiler: JavaCompilerService,
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/MultipleClassImportEditHandler.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/MultipleClassImportEditHandler.kt
index 90c3e118a..02da367fe 100644
--- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/MultipleClassImportEditHandler.kt
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/MultipleClassImportEditHandler.kt
@@ -34,7 +34,7 @@ class MultipleClassImportEditHandler(
private val classes: Set,
private val imported: Set,
file: Path
-) : IJavaEditHandler(file) {
+) : AdvancedJavaEditHandler(file) {
private val log = ILogger.newInstance(javaClass.simpleName)
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/models/JavaCompletionItem.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/models/JavaCompletionItem.kt
new file mode 100644
index 000000000..af0ea8520
--- /dev/null
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/models/JavaCompletionItem.kt
@@ -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 .
+ */
+
+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?,
+ 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
+ )
+}
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IJavaCompletionProvider.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IJavaCompletionProvider.kt
index 6e09f5cbe..ca993648c 100644
--- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IJavaCompletionProvider.kt
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IJavaCompletionProvider.kt
@@ -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
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/ScopeCompletionProvider.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/ScopeCompletionProvider.kt
index 77c0d189d..cb473db2f 100644
--- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/ScopeCompletionProvider.kt
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/ScopeCompletionProvider.kt
@@ -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
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/TopLevelSnippetsProvider.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/TopLevelSnippetsProvider.kt
index 9bdac931a..99788f55f 100644
--- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/TopLevelSnippetsProvider.kt
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/TopLevelSnippetsProvider.kt
@@ -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
diff --git a/lsp/models/src/main/java/com/itsaky/androidide/lsp/edits/DefaultEditHandler.kt b/lsp/models/src/main/java/com/itsaky/androidide/lsp/edits/DefaultEditHandler.kt
index 1023056a7..83558884a 100644
--- a/lsp/models/src/main/java/com/itsaky/androidide/lsp/edits/DefaultEditHandler.kt
+++ b/lsp/models/src/main/java/com/itsaky/androidide/lsp/edits/DefaultEditHandler.kt
@@ -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)
diff --git a/lsp/models/src/main/java/com/itsaky/androidide/lsp/models/Completions.kt b/lsp/models/src/main/java/com/itsaky/androidide/lsp/models/Completions.kt
index 538b6d0b4..7975752b8 100644
--- a/lsp/models/src/main/java/com/itsaky/androidide/lsp/models/Completions.kt
+++ b/lsp/models/src/main/java/com/itsaky/androidide/lsp/models/Completions.kt
@@ -124,7 +124,8 @@ open class CompletionItem(
var kind: CompletionItemKind,
var matchLevel: MatchLevel,
var additionalTextEdits: List?,
- var data: ICompletionData?
+ var data: ICompletionData?,
+ var editHandler: IEditHandler = DefaultEditHandler()
) :
io.github.rosemoe.sora.lang.completion.CompletionItem(label, detail), Comparable {
@@ -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) {
diff --git a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/edits/AttrValueEditHandler.kt b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/edits/AttrValueEditHandler.kt
index 7ec7b7777..f0dae9283 100644
--- a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/edits/AttrValueEditHandler.kt
+++ b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/edits/AttrValueEditHandler.kt
@@ -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('@', '.', ':', '/')
}
-}
\ No newline at end of file
+}
diff --git a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/edits/QualifiedValueEditHandler.kt b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/edits/QualifiedValueEditHandler.kt
index 2f2e34fe2..1d9ecc8fa 100644
--- a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/edits/QualifiedValueEditHandler.kt
+++ b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/edits/QualifiedValueEditHandler.kt
@@ -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
}