From 10e5551ecaa35eaaf1402a681b1291fec375d9d4 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Mon, 9 May 2022 15:15:49 +0530 Subject: [PATCH] Added utility method to find the module of a file --- .../sheets/AttrValueEditorSheet.java | 2 +- .../language/CommonCompletionProvider.java | 2 + .../androidide/projects/ProjectManager.kt | 34 +++++ .../test/test-home/.androidide/init/model.jar | Bin 83899 -> 83949 bytes .../tooling/api/model/IdeAndroidModule.kt | 2 +- lsp/api/build.gradle | 17 ++- .../java/com/itsaky/lsp/models/Completions.kt | 2 + .../lsp/xml/providers/CompletionProvider.java | 4 +- .../xml/providers/XmlCompletionProvider.kt | 25 ++++ .../com/itsaky/lsp/xml/utils/XmlUtils.java | 56 ------- .../java/com/itsaky/lsp/xml/utils/XmlUtils.kt | 138 ++++++++++++++++++ .../main/java/com/itsaky/xml/INamespace.java | 28 +++- .../java/com/itsaky/xml/impl/UiNamespace.java | 40 +++-- .../com/itsaky/inflater/impl/BaseView.java | 8 +- 14 files changed, 271 insertions(+), 87 deletions(-) delete mode 100644 lsp/xml/src/main/java/com/itsaky/lsp/xml/utils/XmlUtils.java create mode 100644 lsp/xml/src/main/java/com/itsaky/lsp/xml/utils/XmlUtils.kt diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrValueEditorSheet.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrValueEditorSheet.java index fecaec1e5..5a7a3990a 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrValueEditorSheet.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrValueEditorSheet.java @@ -116,7 +116,7 @@ public class AttrValueEditorSheet extends BottomSheetDialogFragment final var namespace = this.attribute.getNamespace(); final var formatText = Attr.createFormatText(attribute.getFormat()); this.binding.attributeName.setText( - String.format("%s:%s", namespace.getName(), attribute.getAttributeName())); + String.format("%s:%s", namespace.getPrefix (), attribute.getAttributeName())); this.binding.attributeFormat.setText(formatText); setupTabs(formatText); diff --git a/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.java b/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.java index e74f91357..102262fef 100644 --- a/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.java +++ b/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.java @@ -19,6 +19,7 @@ package com.itsaky.androidide.language; import androidx.annotation.NonNull; +import com.itsaky.androidide.projects.ProjectManager; import com.itsaky.androidide.utils.ILogger; import com.itsaky.lsp.api.ICompletionProvider; import com.itsaky.lsp.api.ILanguageServer; @@ -123,6 +124,7 @@ public class CommonCompletionProvider { file); params.setContent(content); params.setPrefix(prefix); + params.setModule(ProjectManager.INSTANCE.findModuleForFile (file.toFile ())); final var result = completer.complete(params); Collections.sort(result.getItems()); return result; diff --git a/app/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt b/app/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt index 8c88fed5a..47db018c1 100644 --- a/app/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt +++ b/app/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt @@ -245,4 +245,38 @@ object ProjectManager { android.variants.first { it.name == "debug" }.mainArtifact.generatedSourceFolders) return sources } + + /** + * Finds the module in which the given file exists. + * + * @return The module in which the given file exists or `null` if the module cannot be found. + */ + fun findModuleForFile(file: File): IdeModule? { + if (rootProject == null) { + log.warn( + "No root project instance is set. Is the project initialization process finished?") + return null + } + + val path = file.canonicalPath + val modules = rootProject!!.getModules() + + var longestPath = "" + var longestPathModule: IdeModule? = null + for (module in modules) { + val modulePath = module.projectDir!!.canonicalPath + if (path.startsWith(modulePath)) { + if (longestPath.length < modulePath.length && module is IdeModule) { + longestPathModule = module + longestPath = modulePath + } + } + } + + if (longestPath.isEmpty()) { + return null + } + + return longestPathModule + } } diff --git a/build-tools/tooling-api-impl/src/test/test-home/.androidide/init/model.jar b/build-tools/tooling-api-impl/src/test/test-home/.androidide/init/model.jar index ca64b7b65ed66dee95d1b132b2ab29113f9093d0..349de83a2b8cfd2e60bba042f02e509f00a070e8 100644 GIT binary patch delta 7151 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 NfQM4S19(1`{{cUTW&Z#G delta 7051 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 M2{?=&xPOiR1rhHW*8l(j diff --git a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeAndroidModule.kt b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeAndroidModule.kt index 650d7d1b4..51a0bb596 100644 --- a/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeAndroidModule.kt +++ b/build-tools/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeAndroidModule.kt @@ -203,7 +203,7 @@ class IdeAndroidModule( shouldLookupPackage = false } - val packageAttr = manifest.attributes.getNamedItem("package")] + val packageAttr = manifest.attributes.getNamedItem("package") this.packageName = packageAttr.nodeValue this.shouldLookupPackage = false } diff --git a/lsp/api/build.gradle b/lsp/api/build.gradle index 52d4c2b4e..7d70359ab 100644 --- a/lsp/api/build.gradle +++ b/lsp/api/build.gradle @@ -20,16 +20,16 @@ apply plugin: 'kotlin-android' android { compileSdkVersion rootProject.ext.compileSdk buildToolsVersion rootProject.ext.buildTools - + defaultConfig { minSdkVersion rootProject.ext.minSdk targetSdkVersion rootProject.ext.targetSdk } - + buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { @@ -42,11 +42,14 @@ dependencies { implementation common.editor implementation "androidx.core:core-ktx:1.7.0" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - - compileOnly project(':actions') - compileOnly project(':common') + + api project (':build-tools:tooling-api-model') + + compileOnly project (':xml-inflater') + compileOnly project (':actions') + compileOnly project (':common') } repositories { - mavenCentral() + mavenCentral () } \ No newline at end of file diff --git a/lsp/api/src/main/java/com/itsaky/lsp/models/Completions.kt b/lsp/api/src/main/java/com/itsaky/lsp/models/Completions.kt index 75c3b54ee..c4caf8693 100644 --- a/lsp/api/src/main/java/com/itsaky/lsp/models/Completions.kt +++ b/lsp/api/src/main/java/com/itsaky/lsp/models/Completions.kt @@ -17,6 +17,7 @@ package com.itsaky.lsp.models +import com.itsaky.androidide.tooling.api.model.IdeModule import com.itsaky.androidide.utils.ILogger import io.github.rosemoe.sora.text.Content import io.github.rosemoe.sora.widget.CodeEditor @@ -25,6 +26,7 @@ import java.nio.file.Path data class CompletionParams(var position: Position, var file: Path) { var content: CharSequence? = null var prefix: String? = null + var module: IdeModule? = null fun requirePrefix(): String { if (prefix == null) { diff --git a/lsp/xml/src/main/java/com/itsaky/lsp/xml/providers/CompletionProvider.java b/lsp/xml/src/main/java/com/itsaky/lsp/xml/providers/CompletionProvider.java index 2b46a19d3..e7291d0f9 100644 --- a/lsp/xml/src/main/java/com/itsaky/lsp/xml/providers/CompletionProvider.java +++ b/lsp/xml/src/main/java/com/itsaky/lsp/xml/providers/CompletionProvider.java @@ -248,13 +248,13 @@ public class CompletionProvider extends AbstractServiceProvider implements IComp @NonNull private String createAttributeInsertText(@NonNull Attr attr) { StringBuilder xml = new StringBuilder(); - xml.append(attr.namespace.getName()); + xml.append(attr.namespace.getPrefix ()); xml.append(":"); xml.append(attr.name); xml.append("="); xml.append("\""); - if (attr.namespace.getName().equals("android") && attr.name.equals("id")) { + if (attr.namespace.getPrefix ().equals("android") && attr.name.equals("id")) { xml.append("@+id/"); } diff --git a/lsp/xml/src/main/java/com/itsaky/lsp/xml/providers/XmlCompletionProvider.kt b/lsp/xml/src/main/java/com/itsaky/lsp/xml/providers/XmlCompletionProvider.kt index c1b84ce58..9fabce458 100644 --- a/lsp/xml/src/main/java/com/itsaky/lsp/xml/providers/XmlCompletionProvider.kt +++ b/lsp/xml/src/main/java/com/itsaky/lsp/xml/providers/XmlCompletionProvider.kt @@ -17,10 +17,16 @@ package com.itsaky.lsp.xml.providers +import com.itsaky.androidide.tooling.api.model.IdeAndroidModule import com.itsaky.androidide.utils.ILogger import com.itsaky.lsp.api.ICompletionProvider import com.itsaky.lsp.models.CompletionParams import com.itsaky.lsp.models.CompletionResult +import com.itsaky.lsp.xml.utils.XmlUtils +import com.itsaky.lsp.xml.utils.XmlUtils.NodeType.UNKNOWN +import com.itsaky.xml.INamespace +import org.eclipse.lemminx.dom.DOMParser +import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager /** * Completion provider for XMl files. @@ -34,6 +40,25 @@ class XmlCompletionProvider : ICompletionProvider { override fun complete(params: CompletionParams): CompletionResult { return try { + if (params.module == null || params.module !is IdeAndroidModule) { + log.warn("Cannot provide completions for file:", params.file) + log.warn("Module provide in params is either null or is not an Android module") + return CompletionResult() + } + + val contents = params.requireContents().toString() + val namespace = + INamespace.forPackageName((params.module as IdeAndroidModule).packageName) + val document = + DOMParser.getInstance() + .parse(contents, namespace.uri, URIResolverExtensionManager()) + val type = XmlUtils.getNodeType(document, params.position.requireIndex()) + + if (type == UNKNOWN) { + log.warn("Unknown node type. CompletionParams:", params) + return CompletionResult() + } + CompletionResult() } catch (error: Throwable) { log.error("An error occurred while computing XML completions", error) diff --git a/lsp/xml/src/main/java/com/itsaky/lsp/xml/utils/XmlUtils.java b/lsp/xml/src/main/java/com/itsaky/lsp/xml/utils/XmlUtils.java deleted file mode 100644 index 900b92756..000000000 --- a/lsp/xml/src/main/java/com/itsaky/lsp/xml/utils/XmlUtils.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.lsp.xml.utils; - -import androidx.annotation.NonNull; - -import com.itsaky.androidide.utils.CharSequenceReader; -import com.itsaky.androidide.utils.ILogger; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -/** - * @author Akash Yadav - */ -public class XmlUtils { - - private static final XmlPullParserFactory parserFactory; - private static final ILogger LOG = ILogger.newInstance("XmlUtils"); - - static { - try { - parserFactory = XmlPullParserFactory.newInstance(); - } catch (XmlPullParserException e) { - LOG.error("Unable to create pull parser factory"); - throw new RuntimeException(e); - } - } - - public static XmlPullParser newParser() throws XmlPullParserException { - return parserFactory.newPullParser(); - } - - @NonNull - public static XmlPullParser newParser(CharSequence contents) throws XmlPullParserException { - final var parser = parserFactory.newPullParser(); - parser.setInput(new CharSequenceReader(contents)); - return parser; - } -} diff --git a/lsp/xml/src/main/java/com/itsaky/lsp/xml/utils/XmlUtils.kt b/lsp/xml/src/main/java/com/itsaky/lsp/xml/utils/XmlUtils.kt new file mode 100644 index 000000000..7dcfa451e --- /dev/null +++ b/lsp/xml/src/main/java/com/itsaky/lsp/xml/utils/XmlUtils.kt @@ -0,0 +1,138 @@ +/* + * 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.lsp.xml.utils + +import com.itsaky.androidide.lexers.xml.XMLLexer +import com.itsaky.androidide.utils.CharSequenceReader +import com.itsaky.androidide.utils.ILogger +import com.itsaky.lsp.xml.utils.XmlUtils.NodeType.ATTRIBUTE +import com.itsaky.lsp.xml.utils.XmlUtils.NodeType.ATTRIBUTE_VALUE +import com.itsaky.lsp.xml.utils.XmlUtils.NodeType.TAG +import org.antlr.v4.runtime.CharStreams +import org.antlr.v4.runtime.Token +import org.eclipse.lemminx.dom.DOMDocument +import org.eclipse.lemminx.dom.DOMElement +import org.eclipse.lemminx.dom.DOMNode +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserException +import org.xmlpull.v1.XmlPullParserFactory + +/** @author Akash Yadav */ +object XmlUtils { + private val LOG = ILogger.newInstance("XmlUtils") + private val parserFactory: XmlPullParserFactory = + try { + XmlPullParserFactory.newInstance() + } catch (e: XmlPullParserException) { + LOG.error("Unable to create pull parser factory") + throw RuntimeException(e) + } + + @Throws(XmlPullParserException::class) + fun newParser(): XmlPullParser { + return parserFactory.newPullParser() + } + + @Throws(XmlPullParserException::class) + fun newParser(contents: CharSequence?): XmlPullParser { + val parser = parserFactory.newPullParser() + parser.setInput(CharSequenceReader(contents)) + return parser + } + + fun isTag(node: DOMNode, index: Int): Boolean { + var name = node.nodeName + if (name == null) { + name = "" + } + return node.start < index && index <= node.start + name.length + 1 + } + + fun isEndTag(node: DOMNode?, index: Int): Boolean { + if (node !is DOMElement) { + return false + } + val endOpenOffset = node.endTagOpenOffset + return if (endOpenOffset == -1) { + false + } else index >= endOpenOffset + } + + fun isInAttributeValue(contents: String?, index: Int): Boolean { + val lexer = XMLLexer(CharStreams.fromString(contents)) + var token: Token + while (lexer.nextToken().also { token = it } != null) { + val start = token.startIndex + val end = token.stopIndex + if (token.type == Token.EOF) { + break + } + + if (index in start..end) { + return token.type == XMLLexer.STRING + } + if (end > index) { + break + } + } + return false + } + + fun getPrefix(parsed: DOMDocument, index: Long, type: NodeType?): String? { + val text = parsed.text + when (type) { + TAG -> { + val nodeAt = parsed.findNodeAt(index.toInt()) ?: return null + return text.substring(nodeAt.start, index.toInt()) + } + ATTRIBUTE -> { + val attr = parsed.findAttrAt(index.toInt()) ?: return null + return text.substring(attr.start, index.toInt()) + } + ATTRIBUTE_VALUE -> { + val attrAt = parsed.findAttrAt(index.toInt()) ?: return null + var prefix = text.substring(attrAt.nodeAttrValue.start + 1, index.toInt()) + if (prefix.contains("|")) { + prefix = prefix.substring(prefix.lastIndexOf('|') + 1) + } + return prefix + } + else -> return "" + } + } + + fun getNodeType(parsed: DOMDocument, cursor: Int): NodeType { + val nodeAt = parsed.findNodeAt(cursor) ?: return NodeType.UNKNOWN + + if (isTag(nodeAt, cursor) || isEndTag(nodeAt, cursor)) { + return TAG + } + + return if (isInAttributeValue(parsed.textDocument.text, cursor)) { + ATTRIBUTE_VALUE + } else { + ATTRIBUTE + } + } + + enum class NodeType { + UNKNOWN, + TAG, + ATTRIBUTE, + ATTRIBUTE_VALUE + } +} diff --git a/sdk-info/src/main/java/com/itsaky/xml/INamespace.java b/sdk-info/src/main/java/com/itsaky/xml/INamespace.java index 11f9eb3eb..afaf8af21 100644 --- a/sdk-info/src/main/java/com/itsaky/xml/INamespace.java +++ b/sdk-info/src/main/java/com/itsaky/xml/INamespace.java @@ -31,11 +31,35 @@ import org.jetbrains.annotations.Contract; */ public interface INamespace extends Parcelable { + String URI_PREFIX = "http://schemas.android.com/apk/res/"; + /** Used to represent the namespace declarator (xmlns) (don't know what else to call it). */ INamespace DECLARATOR = new UiNamespace("xmlns", ""); /** The android namespace. */ - INamespace ANDROID = new UiNamespace("android", "http://schemas.android.com/apk/res/android"); + INamespace ANDROID = + new UiNamespace("android", "android", "http://schemas.android.com/apk/res/android"); + + /** The 'res-auto' namespace. */ + INamespace RES_AUTO = new UiNamespace("", null, "http://schemas.android.com/apk/res-auto"); + + /** The tools namespace. */ + INamespace TOOLS = new UiNamespace("tools", null, "http://schemas.android.com/tools"); + + static INamespace forPackageName(@NonNull String packageName) { + if (ANDROID.getPackageName().equals(packageName)) { + return ANDROID; + } + + return new UiNamespace("", packageName, INamespace.URI_PREFIX + packageName); + } + + /** + * Get the package name of this namespace (as used by aapt). + * + * @return The package name. + */ + String getPackageName(); @NonNull @Contract(value = "_ -> new", pure = true) @@ -48,7 +72,7 @@ public interface INamespace extends Parcelable { * * @return The name of the namespace. */ - String getName(); + String getPrefix(); /** * Get the uri of this namespace. diff --git a/sdk-info/src/main/java/com/itsaky/xml/impl/UiNamespace.java b/sdk-info/src/main/java/com/itsaky/xml/impl/UiNamespace.java index 5cedc4404..8627fade6 100644 --- a/sdk-info/src/main/java/com/itsaky/xml/impl/UiNamespace.java +++ b/sdk-info/src/main/java/com/itsaky/xml/impl/UiNamespace.java @@ -31,9 +31,6 @@ import java.util.Objects; */ public class UiNamespace implements INamespace { - private final String name; - private final String uri; - public static final Creator CREATOR = new Creator<>() { @NonNull @@ -50,21 +47,29 @@ public class UiNamespace implements INamespace { return new UiNamespace[0]; } }; + private final String prefix; + private final String packageName; + private final String uri; - public UiNamespace(String name, String uri) { - this.name = name; + public UiNamespace(String prefix, String uri) { + this(prefix, null, uri); + } + + public UiNamespace(String prefix, String packageName, String uri) { + this.prefix = prefix; + this.packageName = packageName; this.uri = uri; } private UiNamespace(@NonNull Parcel in) { - this.name = in.readString(); + this.prefix = in.readString(); + this.packageName = in.readString(); this.uri = in.readString(); } - @NonNull @Override - public String toString() { - return "UiNamespace{" + "name='" + name + '\'' + ", uri='" + uri + '\'' + '}'; + public int hashCode() { + return Objects.hash(getUri()); } @Override @@ -84,14 +89,20 @@ public class UiNamespace implements INamespace { return Objects.equals(getUri(), that.getUri()); } + @NonNull @Override - public int hashCode() { - return Objects.hash(getUri()); + public String toString() { + return "UiNamespace{" + "name='" + prefix + '\'' + ", uri='" + uri + '\'' + '}'; } @Override - public String getName() { - return name; + public String getPrefix() { + return prefix; + } + + @Override + public String getPackageName() { + return packageName; } @Override @@ -106,7 +117,8 @@ public class UiNamespace implements INamespace { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeString(name); + dest.writeString(prefix); + dest.writeString(packageName); dest.writeString(uri); } } diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/impl/BaseView.java b/xml-inflater/src/main/java/com/itsaky/inflater/impl/BaseView.java index 70749cf7f..7fc669759 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/impl/BaseView.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/impl/BaseView.java @@ -43,7 +43,7 @@ public abstract class BaseView implements IView { public static final String XML_HEADER = ""; protected final Set namespaces = - new TreeSet<>(Comparator.comparing(INamespace::getName)); + new TreeSet<>(Comparator.comparing(INamespace::getPrefix)); protected final List attributes = new ArrayList<>(); protected final Set attrAdapters = new HashSet<>(); @@ -227,7 +227,7 @@ public abstract class BaseView implements IView { public INamespace findRegisteredNamespace(@NonNull String name) { final var namespaces = this.namespaces.stream() - .filter(ns -> name.equals(ns.getName())) + .filter(ns -> name.equals(ns.getPrefix ())) .collect(Collectors.toSet()); if (namespaces.isEmpty()) { @@ -269,8 +269,8 @@ public abstract class BaseView implements IView { newLine(sb, indentCount + 1); // Attributes must be indented by one tab (4 spaces by // default) attr.getNamespace(); - if (attr.getNamespace() != null && attr.getNamespace().getName().length() > 0) { - sb.append(attr.getNamespace().getName()); + if (attr.getNamespace() != null && attr.getNamespace().getPrefix ().length() > 0) { + sb.append(attr.getNamespace().getPrefix ()); sb.append(":"); } sb.append(attr.getAttributeName());