mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Added utility method to find the module of a file
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
+10
-7
@@ -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 ()
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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/");
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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 "<this-will-not-match>"
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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", "<namespace declarator>");
|
||||
|
||||
/** 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.
|
||||
|
||||
@@ -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<UiNamespace> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public abstract class BaseView implements IView {
|
||||
public static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
|
||||
|
||||
protected final Set<INamespace> namespaces =
|
||||
new TreeSet<>(Comparator.comparing(INamespace::getName));
|
||||
new TreeSet<>(Comparator.comparing(INamespace::getPrefix));
|
||||
protected final List<IAttribute> attributes = new ArrayList<>();
|
||||
protected final Set<IAttributeAdapter> 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());
|
||||
|
||||
Reference in New Issue
Block a user