mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
feat(lsp/java): add 'Fix imports' actions
The 'Fix imports' action analyzes the file contents, walks through the diagnostic items looking for 'compiler.err.cant.resolve.location' errors, then performs an edit to import all those classes at once. If multiple classes are found for a class name, the user is asked to choose the class that should be imported.
This commit is contained in:
@@ -24,6 +24,10 @@ import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
|
||||
fun ActionData.requireContext() : Context {
|
||||
return get(Context::class.java) ?: throw IllegalArgumentException("No context instance provided")
|
||||
}
|
||||
|
||||
fun ActionData.requireFile(): File {
|
||||
return get(File::class.java) ?: throw IllegalArgumentException("No file instance provided")
|
||||
}
|
||||
|
||||
Binary file not shown.
+30
-10
@@ -25,9 +25,11 @@ import com.itsaky.androidide.actions.EditorActionItem
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.lsp.api.ILanguageClient
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServerRegistry
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
|
||||
import com.itsaky.androidide.lsp.java.rewrite.Rewrite
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.DocumentUtils
|
||||
@@ -37,6 +39,7 @@ import java.io.File
|
||||
|
||||
/**
|
||||
* Base class for java code actions
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
abstract class BaseJavaCodeAction : EditorActionItem {
|
||||
@@ -47,7 +50,7 @@ abstract class BaseJavaCodeAction : EditorActionItem {
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_CODE_ACTIONS
|
||||
|
||||
protected abstract val titleTextRes: Int
|
||||
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
if (
|
||||
!data.hasRequiredData(Context::class.java, JavaLanguageServer::class.java, File::class.java)
|
||||
@@ -67,20 +70,37 @@ abstract class BaseJavaCodeAction : EditorActionItem {
|
||||
|
||||
fun performCodeAction(data: ActionData, result: Rewrite) {
|
||||
val server = ILanguageServerRegistry.getDefault().getServer(JavaLanguageServer.SERVER_ID)!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(ProjectManager.findModuleForFile(data.requireFile()) ?: return)
|
||||
val compiler = data.requireCompiler()
|
||||
val client = server.client!!
|
||||
|
||||
val file = data.requireFile()
|
||||
|
||||
val actions = try {
|
||||
result.asCodeActions(compiler, label)
|
||||
} catch (e: Exception) {
|
||||
flashError(e.cause?.message ?: e.message)
|
||||
ILogger.instance().error(e)
|
||||
return
|
||||
}
|
||||
val actions =
|
||||
try {
|
||||
result.asCodeActions(compiler, label)
|
||||
} catch (e: Exception) {
|
||||
flashError(e.cause?.message ?: e.message)
|
||||
ILogger.instance().error(e)
|
||||
return
|
||||
}
|
||||
|
||||
client.performCodeAction(file, actions)
|
||||
}
|
||||
|
||||
protected fun ActionData.requireLanguageServer(): JavaLanguageServer {
|
||||
return ILanguageServerRegistry.getDefault().getServer(JavaLanguageServer.SERVER_ID)
|
||||
as JavaLanguageServer
|
||||
}
|
||||
|
||||
protected fun ActionData.getLanguageClient() : ILanguageClient? {
|
||||
return requireLanguageServer().client
|
||||
}
|
||||
|
||||
protected fun ActionData.requireCompiler(): JavaCompilerService {
|
||||
val module = ProjectManager.findModuleForFile(requireFile())
|
||||
requireNotNull(module) {
|
||||
"Cannot get compiler instance. Unable to find module for file: ${requireFile().name}"
|
||||
}
|
||||
return JavaCompilerProvider.get(module)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.itsaky.androidide.lsp.java.actions.common.RemoveUnusedImportsAction
|
||||
import com.itsaky.androidide.lsp.java.actions.common.UncommentAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.AddImportAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.AddThrowsAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.AutoFixImportsAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.CreateMissingMethodAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.FieldToBlockAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.ImplementAbstractMethodsAction
|
||||
@@ -54,6 +55,7 @@ object JavaCodeActionsMenu : IActionsMenuProvider {
|
||||
GoToDefinitionAction(),
|
||||
FindReferencesAction(),
|
||||
AddImportAction(),
|
||||
AutoFixImportsAction(),
|
||||
ImplementAbstractMethodsAction(),
|
||||
VariableToStatementAction(),
|
||||
FieldToBlockAction(),
|
||||
|
||||
+1
-6
@@ -24,9 +24,7 @@ import com.itsaky.androidide.actions.newDialogBuilder
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.javac.services.util.JavaDiagnosticUtils
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServerRegistry
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.AddImport
|
||||
@@ -145,10 +143,7 @@ class AddImportAction : BaseJavaCodeAction() {
|
||||
}
|
||||
|
||||
val compiler = JavaCompilerProvider.get(module)
|
||||
val server =
|
||||
ILanguageServerRegistry.getDefault().getServer(JavaLanguageServer.SERVER_ID)
|
||||
as JavaLanguageServer
|
||||
val client = server.client ?: return
|
||||
val client = data.getLanguageClient() ?: return
|
||||
val actions = mutableListOf<CodeActionItem>()
|
||||
val titles = result.first as List<String>
|
||||
val rewrites = result.second as List<Rewrite>
|
||||
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* 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.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.requireContext
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.utils.positionForImports
|
||||
import com.itsaky.androidide.lsp.models.CodeActionItem
|
||||
import com.itsaky.androidide.lsp.models.CodeActionKind
|
||||
import com.itsaky.androidide.lsp.models.DocumentChange
|
||||
import com.itsaky.androidide.lsp.models.TextEdit
|
||||
import com.itsaky.androidide.models.Range
|
||||
import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.flashInfo
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Analyzes the source file for unresolved names and tries to import all of them at once.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class AutoFixImportsAction : BaseJavaCodeAction() {
|
||||
|
||||
private val log = ILogger.newInstance("AutoFixImportsAction")
|
||||
override val titleTextRes: Int = R.string.title_fix_imports
|
||||
override val id: String = "ide.editor.lsp.java.autoFixImports"
|
||||
override var label: String = ""
|
||||
|
||||
override fun execAction(data: ActionData): Result {
|
||||
val path = data.requirePath()
|
||||
val compiler = data.requireCompiler()
|
||||
return compiler.compile(path).get { task ->
|
||||
val classes = mutableMapOf<String, List<String>>()
|
||||
|
||||
// find all unresolved simple names
|
||||
unresolvedNames(path, task).forEach { simpleName ->
|
||||
|
||||
// if we have already looked for this simple name
|
||||
// we do not need to look it up again
|
||||
if (classes[simpleName] != null) return@forEach
|
||||
|
||||
// find classes with those names
|
||||
compiler.findQualifiedNames(simpleName).let { names ->
|
||||
|
||||
// if we find classes with that specific simple name, map them to the simple name
|
||||
if (names.isNotEmpty()) {
|
||||
classes[simpleName] = names
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return the result
|
||||
Result(getFileImports(task, path), classes)
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is Result) {
|
||||
log.error("Invalid result returned from execAction", result)
|
||||
return
|
||||
}
|
||||
|
||||
if (result.classes.isEmpty()) {
|
||||
flashInfo(R.string.msg_no_unresolved_classes)
|
||||
return
|
||||
}
|
||||
|
||||
// if there are multiple classes with same simple name
|
||||
// ask the user to choose the appropriate class
|
||||
if (result.classes.any { it.value.size > 1 }) {
|
||||
finalizeClassNames(data, result)
|
||||
} else {
|
||||
performEdits(data, result)
|
||||
}
|
||||
}
|
||||
|
||||
private fun finalizeClassNames(data: ActionData, result: Result) {
|
||||
var e: Map.Entry<String, List<String>>? = null
|
||||
for (entry in result.classes) {
|
||||
if (entry.value.size > 1) {
|
||||
e = entry
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (e == null) {
|
||||
performEdits(data, result)
|
||||
return
|
||||
}
|
||||
|
||||
val context = data.requireContext()
|
||||
DialogUtils.newMaterialDialogBuilder(context)
|
||||
.setCancelable(true)
|
||||
.setItems(e.value.toTypedArray()) { dialog, which ->
|
||||
dialog.dismiss()
|
||||
result.classes[e.key] = listOf(e.value[which])
|
||||
|
||||
// once the user decides which class to import for this simple name,
|
||||
// call this method again to see if there any other simple names with multiple options
|
||||
finalizeClassNames(data, result)
|
||||
}
|
||||
.setTitle(context.getString(R.string.title_class_chooser, e.key))
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun performEdits(data: ActionData, result: Result) {
|
||||
val path = data.requirePath()
|
||||
val compiler = data.requireCompiler()
|
||||
val client =
|
||||
data.getLanguageClient()
|
||||
?: run {
|
||||
log.warn("No language client found. Cannot perform edits.")
|
||||
return
|
||||
}
|
||||
|
||||
val classes = result.classes.mapNotNull { it.value.firstOrNull() }
|
||||
|
||||
if (classes.isEmpty()) {
|
||||
flashInfo(R.string.msg_no_unresolved_classes)
|
||||
return
|
||||
}
|
||||
|
||||
val insertText = StringBuilder()
|
||||
if (result.fileImports.isEmpty() && classes.isNotEmpty()) {
|
||||
// if there are no file imports, the new imports will be added just after the package
|
||||
// declaration. To avoid this, add a new line before the imports
|
||||
insertText.append("\n")
|
||||
}
|
||||
|
||||
for (klass in classes) {
|
||||
insertText.append("import ${klass};\n")
|
||||
}
|
||||
|
||||
val position = compiler.compile(path).get { positionForImports(classes[0], it) }
|
||||
|
||||
val change = DocumentChange()
|
||||
change.file = path
|
||||
change.edits = listOf(TextEdit(Range.pointRange(position), insertText.toString()))
|
||||
|
||||
val action = CodeActionItem()
|
||||
action.title = data.requireContext().getString(R.string.title_fix_imports)
|
||||
action.kind = CodeActionKind.QuickFix
|
||||
action.changes = listOf(change)
|
||||
client.performCodeAction(data.requireFile(), action)
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks through the diagnostics of the compilation task, looks for [DiagnosticCode.NOT_IMPORTED]
|
||||
* errors and returns a list of simple names of all not imported classes.
|
||||
*/
|
||||
private fun unresolvedNames(file: Path, task: CompileTask): List<String> {
|
||||
val names = mutableListOf<String>()
|
||||
var docContents: CharSequence? = null
|
||||
val diagnostics =
|
||||
task.diagnostics.filter {
|
||||
it.source.toUri() == file.toUri() && it.code == DiagnosticCode.NOT_IMPORTED.id
|
||||
}
|
||||
for (diagnostic in diagnostics) {
|
||||
val content =
|
||||
try {
|
||||
docContents ?: diagnostic.source.getCharContent(true).also { docContents = it }
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to get contents of file $file", e)
|
||||
continue
|
||||
}
|
||||
|
||||
val name =
|
||||
content.subSequence(diagnostic.startPosition.toInt(), diagnostic.endPosition.toInt())
|
||||
names.add(name.toString())
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
private fun getFileImports(task: CompileTask, file: Path): Set<String> {
|
||||
return task.root(file).imports.map { it.qualifiedIdentifier }.map { it.toString() }.toSet()
|
||||
}
|
||||
|
||||
inner class Result(val fileImports: Set<String>, val classes: MutableMap<String, List<String>>)
|
||||
}
|
||||
@@ -22,17 +22,13 @@ import androidx.annotation.RestrictTo;
|
||||
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompilerProvider;
|
||||
import com.itsaky.androidide.lsp.java.parser.ParseTask;
|
||||
import com.itsaky.androidide.lsp.java.utils.InsertUtilsKt;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.lsp.models.TextEdit;
|
||||
import openjdk.source.tree.ImportTree;
|
||||
import openjdk.source.tree.Tree;
|
||||
import openjdk.source.util.SourcePositions;
|
||||
import openjdk.source.util.Trees;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class AddImport extends Rewrite {
|
||||
@@ -51,41 +47,9 @@ public class AddImport extends Rewrite {
|
||||
@Override
|
||||
public Map<Path, TextEdit[]> rewrite(@NonNull CompilerProvider compiler) {
|
||||
final ParseTask task = compiler.parse(file);
|
||||
Position point = insertPosition(task);
|
||||
Position point = InsertUtilsKt.positionForImports(className, task);
|
||||
String text = "import " + className + ";\n";
|
||||
return Collections.singletonMap(
|
||||
file, new TextEdit[] {new TextEdit(new Range(point, point), text)});
|
||||
}
|
||||
|
||||
private Position insertPosition(@NonNull ParseTask task) {
|
||||
List<? extends ImportTree> imports = task.root.getImports();
|
||||
for (ImportTree i : imports) {
|
||||
String next = i.getQualifiedIdentifier().toString();
|
||||
if (className.compareTo(next) < 0) {
|
||||
return insertBefore(task, i);
|
||||
}
|
||||
}
|
||||
if (!imports.isEmpty()) {
|
||||
Tree last = imports.get(imports.size() - 1);
|
||||
return insertAfter(task, last);
|
||||
}
|
||||
if (task.root.getPackage() != null) {
|
||||
return insertAfter(task, task.root.getPackage());
|
||||
}
|
||||
return new Position(0, 0);
|
||||
}
|
||||
|
||||
private Position insertBefore(ParseTask task, Tree i) {
|
||||
SourcePositions pos = Trees.instance(task.task).getSourcePositions();
|
||||
long offset = pos.getStartPosition(task.root, i);
|
||||
int line = (int) task.root.getLineMap().getLineNumber(offset);
|
||||
return new Position(line - 1, 0);
|
||||
}
|
||||
|
||||
private Position insertAfter(ParseTask task, Tree i) {
|
||||
SourcePositions pos = Trees.instance(task.task).getSourcePositions();
|
||||
long offset = pos.getStartPosition(task.root, i);
|
||||
int line = (int) task.root.getLineMap().getLineNumber(offset);
|
||||
return new Position(line, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ abstract class Rewrite {
|
||||
* @param title The title for the code action.
|
||||
* @return The code action item.
|
||||
*/
|
||||
fun asCodeActions(compiler: CompilerProvider, title: String?): CodeActionItem? {
|
||||
fun asCodeActions(compiler: CompilerProvider, title: String): CodeActionItem? {
|
||||
val edits = rewrite(compiler)
|
||||
if (edits.isEmpty()) {
|
||||
return null
|
||||
@@ -52,7 +52,7 @@ abstract class Rewrite {
|
||||
changes.add(change)
|
||||
}
|
||||
val action = CodeActionItem()
|
||||
action.title = title!!
|
||||
action.title = title
|
||||
action.kind = CodeActionKind.QuickFix
|
||||
action.changes = changes
|
||||
finalizeCodeAction(action)
|
||||
|
||||
@@ -53,15 +53,7 @@ public class EditHelper {
|
||||
public static List<TextEdit> addImportIfNeeded(CompilerProvider compiler, Path file,
|
||||
Set<String> imports, String className
|
||||
) {
|
||||
final String pkgName = Extractors.packageName(className);
|
||||
final String star = pkgName + ".*";
|
||||
if ("java.lang".equals(pkgName) || imports.contains(className) || imports.contains(star) ||
|
||||
file == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final var filePackage = StringSearch.packageName(file);
|
||||
if (filePackage != null && filePackage.equals(pkgName)) {
|
||||
if (file == null || containsImport(file, imports, className)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -69,6 +61,21 @@ public class EditHelper {
|
||||
return Arrays.asList(Objects.requireNonNull(addImport.rewrite(compiler).get(file)));
|
||||
}
|
||||
|
||||
public static boolean containsImport(@NonNull Path file, Set<String> imports, String className) {
|
||||
if (imports == null || imports.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String pkgName = Extractors.packageName(className);
|
||||
final String star = pkgName + ".*";
|
||||
if ("java.lang".equals(pkgName) || imports.contains(className) || imports.contains(star)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final var filePackage = StringSearch.packageName(file);
|
||||
return filePackage != null && filePackage.equals(pkgName);
|
||||
}
|
||||
|
||||
public static TextEdit removeTree(final JavacTask task, final CompilationUnitTree root,
|
||||
final Tree remove
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.utils
|
||||
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.parser.ParseTask
|
||||
import com.itsaky.androidide.models.Position
|
||||
import openjdk.source.tree.CompilationUnitTree
|
||||
import openjdk.source.tree.Tree
|
||||
import openjdk.source.util.JavacTask
|
||||
import openjdk.source.util.Trees
|
||||
|
||||
/** @author Akash Yadav */
|
||||
fun positionForImports(className: String, task: ParseTask): Position {
|
||||
return positionForImports(className, task.task, task.root)
|
||||
}
|
||||
|
||||
fun positionForImports(className: String, task: CompileTask): Position {
|
||||
return positionForImports(className, task.task, task.root())
|
||||
}
|
||||
|
||||
fun positionForImports(className: String, task: JavacTask, root: CompilationUnitTree): Position {
|
||||
val imports = root.imports
|
||||
for (i in imports) {
|
||||
val next = i.qualifiedIdentifier.toString()
|
||||
if (className < next) {
|
||||
return insertBefore(task, root, i)
|
||||
}
|
||||
}
|
||||
if (imports.isNotEmpty()) {
|
||||
val last = imports[imports.size - 1]
|
||||
return insertAfter(task, root, last)
|
||||
}
|
||||
|
||||
return if (root.getPackage() != null) {
|
||||
insertAfter(task, root, root.getPackage())
|
||||
} else Position(0, 0)
|
||||
}
|
||||
|
||||
fun insertBefore(task: JavacTask, root: CompilationUnitTree, tree: Tree): Position {
|
||||
val pos = Trees.instance(task).sourcePositions
|
||||
val offset = pos.getStartPosition(root, tree)
|
||||
val line = root.lineMap.getLineNumber(offset).toInt()
|
||||
return Position(line - 1, 0)
|
||||
}
|
||||
|
||||
fun insertAfter(task: JavacTask, root: CompilationUnitTree, tree: Tree): Position {
|
||||
val pos = Trees.instance(task).sourcePositions
|
||||
val offset = pos.getStartPosition(root, tree)
|
||||
val line = root.lineMap.getLineNumber(offset).toInt()
|
||||
return Position(line, 0)
|
||||
}
|
||||
@@ -543,4 +543,7 @@
|
||||
|
||||
<string name="btn_sync">Sync</string>
|
||||
<string name="btn_ignore_changes">Ignore these changes</string>
|
||||
<string name="title_fix_imports">Fix imports</string>
|
||||
<string name="msg_no_unresolved_classes">No unresolved class names found</string>
|
||||
<string name="title_class_chooser">Which %1$s?</string>
|
||||
</resources>
|
||||
|
||||
@@ -92,6 +92,11 @@ constructor(var start: Position = Position(0, 0), var end: Position = Position(0
|
||||
|
||||
companion object {
|
||||
@JvmField val NONE = Range(Position.NONE, Position.NONE)
|
||||
|
||||
@JvmStatic
|
||||
fun pointRange(position: Position) : Range {
|
||||
return Range(position, position)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user