mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
feat: read snippets from JSON files stored in assets
This commit is contained in:
+9
-9
@@ -15,18 +15,18 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.providers.snippet
|
||||
package com.itsaky.androidide.lsp.snippets
|
||||
|
||||
/**
|
||||
* Java snippet.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
data class JavaSnippet(
|
||||
val prefix: String,
|
||||
val description: String,
|
||||
val content: Array<String>
|
||||
) {
|
||||
data class DefaultSnippet(
|
||||
override val prefix: String,
|
||||
override val description: String,
|
||||
override val body: Array<String>
|
||||
) : ISnippet {
|
||||
|
||||
constructor(
|
||||
prefix: String,
|
||||
@@ -36,11 +36,11 @@ data class JavaSnippet(
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is JavaSnippet) return false
|
||||
if (other !is DefaultSnippet) return false
|
||||
|
||||
if (description != other.description) return false
|
||||
if (prefix != other.prefix) return false
|
||||
if (!content.contentEquals(other.content)) return false
|
||||
if (!body.contentEquals(other.body)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -48,7 +48,7 @@ data class JavaSnippet(
|
||||
override fun hashCode(): Int {
|
||||
var result = prefix.hashCode()
|
||||
result = 31 * result + description.hashCode()
|
||||
result = 31 * result + content.contentHashCode()
|
||||
result = 31 * result + body.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.snippets
|
||||
|
||||
/**
|
||||
* A snippet item.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface ISnippet {
|
||||
|
||||
/** The prefix for the snippet. */
|
||||
val prefix: String
|
||||
|
||||
/** A short description about the snippet. */
|
||||
val description: String
|
||||
|
||||
/**
|
||||
* The snippet body. Each element in this array represents a line of code. The lines are joined
|
||||
* and indented before inserting the text.
|
||||
*/
|
||||
val body: Array<String>
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.snippets
|
||||
|
||||
/**
|
||||
* Marker interface for snippet scopes.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface ISnippetScope {
|
||||
|
||||
/**
|
||||
* The filename for the snippet. This is used to resolve the snippet file stored at location
|
||||
* `'data/editor/<lang>/snippets.<filename>.json'`
|
||||
*/
|
||||
val filename: String
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.snippets
|
||||
|
||||
import com.google.gson.JsonParseException
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.itsaky.androidide.app.BaseApplication
|
||||
import com.itsaky.androidide.tasks.executeAsyncProvideError
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Parser for parsing snippets from assets.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
object SnippetParser {
|
||||
|
||||
private val log = ILogger.newInstance("SnippetParser")
|
||||
|
||||
fun <S : ISnippetScope> parse(
|
||||
lang: String,
|
||||
scopes: Array<S>,
|
||||
snippetFactory: (String, String, List<String>) -> ISnippet = { prefix, desc, body ->
|
||||
DefaultSnippet(prefix, desc, body.toTypedArray())
|
||||
}
|
||||
): Map<S, List<ISnippet>> {
|
||||
return ConcurrentHashMap<S, List<ISnippet>>().apply {
|
||||
for (scope in scopes) {
|
||||
this[scope] =
|
||||
mutableListOf<ISnippet>().apply {
|
||||
readSnippets(lang, scope.filename, snippetFactory, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readSnippets(
|
||||
lang: String,
|
||||
type: String,
|
||||
snippetFactory: (String, String, List<String>) -> ISnippet,
|
||||
snippets: MutableList<ISnippet>
|
||||
) {
|
||||
executeAsyncProvideError({
|
||||
val content =
|
||||
try {
|
||||
BaseApplication.getBaseInstance()
|
||||
.assets
|
||||
.open("data/editor/${lang}/snippets.${type}.json")
|
||||
.reader()
|
||||
} catch (e: IOException) {
|
||||
// snippet file probably does not exist
|
||||
return@executeAsyncProvideError
|
||||
}
|
||||
|
||||
JsonReader(content).use {
|
||||
it.beginObject()
|
||||
while (it.hasNext()) {
|
||||
val prefix = it.nextName()
|
||||
readSnippet(prefix, it, snippetFactory, snippets)
|
||||
}
|
||||
it.endObject()
|
||||
}
|
||||
}) { result, err ->
|
||||
if (result == null || err != null) {
|
||||
log.error("Failed to load '${type}' snippets", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readSnippet(
|
||||
prefix: String,
|
||||
reader: JsonReader,
|
||||
snippetFactory: (String, String, List<String>) -> ISnippet,
|
||||
snippets: MutableList<ISnippet>
|
||||
) {
|
||||
reader.beginObject()
|
||||
var desc: String? = null
|
||||
val body = mutableListOf<String>()
|
||||
while (reader.hasNext()) {
|
||||
val n = reader.nextName()
|
||||
if (n != "desc" && n != "body") {
|
||||
throw JsonParseException("'desc' or 'body' was expected, but found '${n}'")
|
||||
}
|
||||
|
||||
if (n == "desc") {
|
||||
desc = reader.nextString()
|
||||
continue
|
||||
}
|
||||
|
||||
if (n == "body") {
|
||||
reader.beginArray()
|
||||
while (reader.hasNext()) {
|
||||
body.add(reader.nextString())
|
||||
}
|
||||
reader.endArray()
|
||||
}
|
||||
}
|
||||
|
||||
checkNotNull(desc) { "DefaultSnippet description not defined for '${prefix}'" }
|
||||
check(body.isNotEmpty()) { "DefaultSnippet body not defined for '${prefix}'" }
|
||||
|
||||
snippets.add(snippetFactory(prefix, desc, body))
|
||||
|
||||
reader.endObject()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"for": {
|
||||
"desc": "Indexed for loop",
|
||||
"body": [
|
||||
"for(int ${1:i} = 0; $1 < ${2:count}; ++$1) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"foreach": {
|
||||
"desc": "For-each loop",
|
||||
"body": [
|
||||
"for(${1:element} : ${2:iterable}) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"forr": {
|
||||
"desc": "Reverse-indexed for loop",
|
||||
"body": [
|
||||
"for(int ${1:i} = ${2:count} - 1; $1 >= 0; --$1) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"while": {
|
||||
"desc": "While loop",
|
||||
"body": [
|
||||
"while(${1:condition}) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"dowhile": {
|
||||
"desc": "Do-While loop",
|
||||
"body": [
|
||||
"do {",
|
||||
"\t$0",
|
||||
"} while(${1:condition});"
|
||||
]
|
||||
},
|
||||
"if": {
|
||||
"desc": "If statement",
|
||||
"body": [
|
||||
"if(${1:condition}) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"ifelse": {
|
||||
"desc": "If-else statement",
|
||||
"body": [
|
||||
"if(${1:condition}) {",
|
||||
"\t$0",
|
||||
"} else {",
|
||||
"\t",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"ifnull": {
|
||||
"desc": "If-null",
|
||||
"body": [
|
||||
"if(${1:object} == null) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"ifnotnull": {
|
||||
"desc": "If-not-null",
|
||||
"body": [
|
||||
"if(${1:object} != null) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import com.itsaky.androidide.lsp.java.providers.JavaDiagnosticProvider;
|
||||
import com.itsaky.androidide.lsp.java.providers.JavaSelectionProvider;
|
||||
import com.itsaky.androidide.lsp.java.providers.ReferenceProvider;
|
||||
import com.itsaky.androidide.lsp.java.providers.SignatureProvider;
|
||||
import com.itsaky.androidide.lsp.java.providers.snippet.JavaSnippetRepository;
|
||||
import com.itsaky.androidide.lsp.java.utils.AnalyzeTimer;
|
||||
import com.itsaky.androidide.lsp.java.utils.CancelChecker;
|
||||
import com.itsaky.androidide.lsp.models.CodeFormatResult;
|
||||
@@ -99,6 +100,8 @@ public class JavaLanguageServer implements ILanguageServer {
|
||||
if (!EventBus.getDefault().isRegistered(this)) {
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
JavaSnippetRepository.INSTANCE.init();
|
||||
}
|
||||
|
||||
public IServerSettings getSettings() {
|
||||
|
||||
+5
-5
@@ -24,7 +24,6 @@ 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.providers.snippet.JavaSnippet
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper
|
||||
import com.itsaky.androidide.lsp.models.ClassCompletionData
|
||||
import com.itsaky.androidide.lsp.models.Command
|
||||
@@ -43,9 +42,11 @@ import com.itsaky.androidide.lsp.models.ICompletionData
|
||||
import com.itsaky.androidide.lsp.models.InsertTextFormat.SNIPPET
|
||||
import com.itsaky.androidide.lsp.models.MatchLevel
|
||||
import com.itsaky.androidide.lsp.models.MethodCompletionData
|
||||
import com.itsaky.androidide.lsp.snippets.ISnippet
|
||||
import com.itsaky.androidide.preferences.utils.indentationString
|
||||
import com.itsaky.androidide.progress.ProgressManager.Companion.abortIfCancelled
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.nio.file.Path
|
||||
import jdkx.lang.model.element.Element
|
||||
import jdkx.lang.model.element.ElementKind.ANNOTATION_TYPE
|
||||
import jdkx.lang.model.element.ElementKind.CLASS
|
||||
@@ -69,7 +70,6 @@ import jdkx.lang.model.element.TypeElement
|
||||
import jdkx.lang.model.element.VariableElement
|
||||
import openjdk.source.tree.Tree
|
||||
import openjdk.source.util.TreePath
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Completion provider for Java source code.
|
||||
@@ -188,7 +188,7 @@ abstract class IJavaCompletionProvider(
|
||||
item.insertText = first.simpleName.toString() + "($0)"
|
||||
item.command = Command("Trigger Parameter Hints", Command.TRIGGER_PARAMETER_HINTS)
|
||||
}
|
||||
item.insertTextFormat = SNIPPET // Snippet
|
||||
item.insertTextFormat = SNIPPET // DefaultSnippet
|
||||
item.snippetDescription = describeSnippet(prefix = partial, allowCommandExecution = true)
|
||||
}
|
||||
return item
|
||||
@@ -294,7 +294,7 @@ abstract class IJavaCompletionProvider(
|
||||
}
|
||||
|
||||
protected open fun snippetItem(
|
||||
snippet: JavaSnippet,
|
||||
snippet: ISnippet,
|
||||
matchLevel: MatchLevel,
|
||||
partial: String,
|
||||
indent: Int
|
||||
@@ -310,7 +310,7 @@ abstract class IJavaCompletionProvider(
|
||||
val indentation = indentationString(indent)
|
||||
this.insertTextFormat = SNIPPET
|
||||
this.insertText =
|
||||
snippet.content.joinToString(separator = "\n").also {
|
||||
snippet.body.joinToString(separator = "\n").also {
|
||||
it.replace("\t", indentationString).replace("\n", "\n${indentation}")
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -20,12 +20,13 @@ package com.itsaky.androidide.lsp.java.providers.completion
|
||||
import com.itsaky.androidide.lsp.api.IServerSettings
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
|
||||
import com.itsaky.androidide.lsp.java.providers.snippet.JavaSnippet
|
||||
import com.itsaky.androidide.lsp.snippets.DefaultSnippet
|
||||
import com.itsaky.androidide.lsp.java.providers.snippet.JavaSnippetRepository
|
||||
import com.itsaky.androidide.lsp.java.providers.snippet.JavaSnippetScope
|
||||
import com.itsaky.androidide.lsp.models.CompletionItem
|
||||
import com.itsaky.androidide.lsp.models.CompletionResult
|
||||
import com.itsaky.androidide.lsp.models.MatchLevel
|
||||
import com.itsaky.androidide.lsp.snippets.ISnippet
|
||||
import com.itsaky.androidide.preferences.internal.tabSize
|
||||
import io.github.rosemoe.sora.text.TextUtils
|
||||
import java.nio.file.Path
|
||||
@@ -99,7 +100,7 @@ class SnippetCompletionProvider(
|
||||
): CompletionResult {
|
||||
val items = mutableListOf<CompletionItem>()
|
||||
val snippets =
|
||||
mutableListOf<JavaSnippet>().apply {
|
||||
mutableListOf<ISnippet>().apply {
|
||||
JavaSnippetRepository.snippets[JavaSnippetScope.LOCAL]?.let { addAll(it) }
|
||||
JavaSnippetRepository.snippets[JavaSnippetScope.GLOBAL]?.let { addAll(it) }
|
||||
}
|
||||
|
||||
+7
-31
@@ -17,6 +17,9 @@
|
||||
|
||||
package com.itsaky.androidide.lsp.java.providers.snippet
|
||||
|
||||
import com.itsaky.androidide.lsp.snippets.ISnippet
|
||||
import com.itsaky.androidide.lsp.snippets.SnippetParser
|
||||
|
||||
/**
|
||||
* Repository to store various snippets for Java.
|
||||
*
|
||||
@@ -24,37 +27,10 @@ package com.itsaky.androidide.lsp.java.providers.snippet
|
||||
*/
|
||||
object JavaSnippetRepository {
|
||||
|
||||
val snippets: Map<JavaSnippetScope, List<JavaSnippet>> by lazy { createSnippets() }
|
||||
lateinit var snippets: Map<JavaSnippetScope, List<ISnippet>>
|
||||
private set
|
||||
|
||||
private fun createSnippets(): Map<JavaSnippetScope, List<JavaSnippet>> {
|
||||
return mutableMapOf<JavaSnippetScope, List<JavaSnippet>>().apply {
|
||||
this[JavaSnippetScope.GLOBAL] = createGlobalSnippets()
|
||||
this[JavaSnippetScope.TOP_LEVEL] = createTopLevelSnippets()
|
||||
this[JavaSnippetScope.MEMBER] = createMemberSnippets()
|
||||
this[JavaSnippetScope.LOCAL] = createLocalSnippets()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createGlobalSnippets(): List<JavaSnippet> {
|
||||
return listOf()
|
||||
}
|
||||
|
||||
private fun createTopLevelSnippets(): List<JavaSnippet> {
|
||||
return listOf()
|
||||
}
|
||||
|
||||
private fun createMemberSnippets(): List<JavaSnippet> {
|
||||
return listOf()
|
||||
}
|
||||
|
||||
private fun createLocalSnippets(): List<JavaSnippet> {
|
||||
return listOf(
|
||||
JavaSnippet("for", "Indexed for loop") {
|
||||
arrayOf("for (int \${1:i} = 0; \$1 < \${2:count}; ++\$1) {", "\t\$0", "}")
|
||||
},
|
||||
JavaSnippet("forr", "Reverse-indexed for loop") {
|
||||
arrayOf("for (int \${1:i} = \${2:count} - 1; \$1 >= 0; --\$1) {", "\t\$0", "}")
|
||||
}
|
||||
)
|
||||
fun init() {
|
||||
this.snippets = SnippetParser.parse("java", JavaSnippetScope.values())
|
||||
}
|
||||
}
|
||||
|
||||
+7
-5
@@ -17,25 +17,27 @@
|
||||
|
||||
package com.itsaky.androidide.lsp.java.providers.snippet
|
||||
|
||||
import com.itsaky.androidide.lsp.snippets.ISnippetScope
|
||||
|
||||
/**
|
||||
* Scope for [JavaSnippet].
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
enum class JavaSnippetScope {
|
||||
enum class JavaSnippetScope(override val filename: String) : ISnippetScope {
|
||||
|
||||
/**
|
||||
* Snippets that can be used at the top level. This includes snippes such as class, interface,
|
||||
* enum templates.
|
||||
*/
|
||||
TOP_LEVEL,
|
||||
TOP_LEVEL("top-level"),
|
||||
|
||||
/** Snippets that can be used at the member level i.e. inside a class tree. */
|
||||
MEMBER,
|
||||
MEMBER("member"),
|
||||
|
||||
/** Snippets that can be used at a local level. E.g. inside a method or constructor. */
|
||||
LOCAL,
|
||||
LOCAL("local"),
|
||||
|
||||
/** Snippets that can be used anywhere in the code, irrespective of the current scope. */
|
||||
GLOBAL
|
||||
GLOBAL("global")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user