mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Extracted interface for IDEEditor
This commit is contained in:
@@ -133,6 +133,7 @@ dependencies {
|
||||
// Local projects here
|
||||
implementation project (':build-tools:aaptcompiler')
|
||||
implementation project (':build-tools:xml-utils')
|
||||
implementation project (':editor')
|
||||
implementation project (':eventbus-android')
|
||||
implementation project (':eventbus-events')
|
||||
implementation project (':build-tools:projects')
|
||||
|
||||
Binary file not shown.
@@ -617,7 +617,7 @@ public class EditorActivity extends IDEActivity
|
||||
}
|
||||
|
||||
if (editor != null && editor.getEditor() != null) {
|
||||
editor.getEditor().close();
|
||||
editor.getEditor().notifyClose();
|
||||
editor.getEditor().release();
|
||||
} else {
|
||||
LOG.error("Cannot save file before close. Editor instance is null");
|
||||
@@ -657,7 +657,7 @@ public class EditorActivity extends IDEActivity
|
||||
for (int i = 0; i < count; i++) {
|
||||
final var editor = getEditorAtIndex(i);
|
||||
if (editor != null && editor.getEditor() != null) {
|
||||
editor.getEditor().close();
|
||||
editor.getEditor().notifyClose();
|
||||
} else {
|
||||
LOG.error("Unable to close file at index:", i);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,6 @@ import com.itsaky.androidide.language.groovy.GroovyLanguage;
|
||||
import com.itsaky.androidide.language.java.JavaLanguage;
|
||||
import com.itsaky.androidide.language.kotlin.KotlinLanguage;
|
||||
import com.itsaky.androidide.language.xml.XMLLanguage;
|
||||
import com.itsaky.androidide.lexers.xml.XMLLexer;
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServer;
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServerRegistry;
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer;
|
||||
@@ -72,20 +71,15 @@ import com.itsaky.androidide.utils.LSPUtils;
|
||||
import com.itsaky.androidide.utils.TypefaceUtils;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.github.rosemoe.sora.event.ContentChangeEvent;
|
||||
import io.github.rosemoe.sora.lang.EmptyLanguage;
|
||||
import io.github.rosemoe.sora.lang.Language;
|
||||
import io.github.rosemoe.sora.text.LineSeparator;
|
||||
@@ -102,13 +96,11 @@ public class CodeEditorView extends FrameLayout {
|
||||
private static final ILogger LOG = ILogger.newInstance("CodeEditorView");
|
||||
private final File file;
|
||||
private final LayoutCodeEditorBinding binding;
|
||||
private boolean isModified;
|
||||
|
||||
public CodeEditorView(
|
||||
@NonNull Context context, @NonNull File file, final @NonNull Range selection) {
|
||||
super(context);
|
||||
this.file = file;
|
||||
this.isModified = false;
|
||||
|
||||
this.binding = LayoutCodeEditorBinding.inflate(LayoutInflater.from(context));
|
||||
this.binding.editor.setTypefaceText(TypefaceUtils.jetbrainsMono());
|
||||
@@ -117,8 +109,6 @@ public class CodeEditorView extends FrameLayout {
|
||||
this.binding.editor.setDividerWidth(SizeUtils.dp2px(1));
|
||||
this.binding.editor.setColorScheme(new SchemeAndroidIDE());
|
||||
this.binding.editor.setLineSeparator(LineSeparator.LF);
|
||||
this.binding.editor.subscribeEvent(
|
||||
ContentChangeEvent.class, ((event, unsubscribe) -> handleContentChange(event)));
|
||||
|
||||
this.binding.diagnosticTextContainer.setVisibility(GONE);
|
||||
|
||||
@@ -162,73 +152,6 @@ public class CodeEditorView extends FrameLayout {
|
||||
return file;
|
||||
}
|
||||
|
||||
private void handleContentChange(@NonNull ContentChangeEvent event) {
|
||||
isModified = true;
|
||||
|
||||
if (event.getAction() == ContentChangeEvent.ACTION_INSERT) {
|
||||
final var editor = event.getEditor();
|
||||
final var content = event.getChangedText();
|
||||
final var endLine = event.getChangeEnd().line;
|
||||
final var endColumn = event.getChangeEnd().column;
|
||||
if (file.getName().endsWith(".xml")) {
|
||||
boolean isOpen = false;
|
||||
try {
|
||||
isOpen = editor.getText().charAt(editor.getCursor().getLeft() - 2) == '<';
|
||||
} catch (Throwable th) {
|
||||
LOG.error(th);
|
||||
}
|
||||
|
||||
if (isOpen && "/".contentEquals(content)) {
|
||||
closeCurrentTag(editor.getText().toString(), endLine, endColumn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void closeCurrentTag(String text, int line, int col) {
|
||||
try {
|
||||
XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(text)));
|
||||
Token token;
|
||||
boolean wasSlash = false, wasOpen = false;
|
||||
ArrayList<String> currentNames = new ArrayList<>();
|
||||
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
|
||||
final int type = token.getType();
|
||||
if (type == XMLLexer.OPEN) {
|
||||
wasOpen = true;
|
||||
} else if (type == XMLLexer.Name) {
|
||||
if (wasOpen && wasSlash && currentNames.size() > 0) {
|
||||
currentNames.remove(0);
|
||||
} else if (wasOpen) {
|
||||
currentNames.add(0, token.getText());
|
||||
wasOpen = false;
|
||||
}
|
||||
} else if (type == XMLLexer.OPEN_SLASH) {
|
||||
int l = token.getLine() - 1;
|
||||
int c = token.getCharPositionInLine();
|
||||
if (l == line && c == col) {
|
||||
break;
|
||||
} else if (currentNames.size() > 0) {
|
||||
currentNames.remove(0);
|
||||
}
|
||||
} else if (type == XMLLexer.SLASH_CLOSE || type == XMLLexer.SPECIAL_CLOSE) {
|
||||
if (currentNames.size() > 0 && token.getText().trim().endsWith("/>")) {
|
||||
currentNames.remove(0);
|
||||
}
|
||||
} else if (type == XMLLexer.SLASH) {
|
||||
wasSlash = true;
|
||||
} else {
|
||||
wasOpen = wasSlash = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentNames.size() > 0) {
|
||||
binding.editor.getText().insert(line, col + 2, currentNames.get(0));
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Unable to close current tag", th);
|
||||
}
|
||||
}
|
||||
|
||||
public IDEEditor getEditor() {
|
||||
return binding.editor;
|
||||
}
|
||||
@@ -363,7 +286,7 @@ public class CodeEditorView extends FrameLayout {
|
||||
* <p>Marks this editor as unmodified. Used only when the activity is being destroyed.
|
||||
*/
|
||||
public void markUnmodified() {
|
||||
isModified = false;
|
||||
binding.editor.markUnmodified();
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@@ -421,7 +344,6 @@ public class CodeEditorView extends FrameLayout {
|
||||
try {
|
||||
FileUtil.writeFile(file, text);
|
||||
notifySaved();
|
||||
isModified = false;
|
||||
return true;
|
||||
} catch (IOException io) {
|
||||
LOG.error("Failed to save file", file, io);
|
||||
@@ -430,7 +352,7 @@ public class CodeEditorView extends FrameLayout {
|
||||
}
|
||||
|
||||
public boolean isModified() {
|
||||
return isModified;
|
||||
return binding.editor.isModified();
|
||||
}
|
||||
|
||||
private void notifySaved() {
|
||||
@@ -473,7 +395,6 @@ public class CodeEditorView extends FrameLayout {
|
||||
|
||||
/** Mark this files as saved. Even if it not saved. */
|
||||
public void markAsSaved() {
|
||||
isModified = false;
|
||||
notifySaved();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
package com.itsaky.androidide.views.editor;
|
||||
|
||||
import static com.itsaky.androidide.R.*;
|
||||
import static com.itsaky.androidide.R.string;
|
||||
import static com.itsaky.androidide.models.prefs.EditorPreferencesKt.getVisiblePasswordFlag;
|
||||
|
||||
import android.app.Dialog;
|
||||
@@ -29,7 +29,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.CompletionListAdapter;
|
||||
import com.itsaky.androidide.eventbus.events.editor.ChangeType;
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent;
|
||||
@@ -69,7 +68,7 @@ import io.github.rosemoe.sora.widget.IDEEditorSearcher;
|
||||
import io.github.rosemoe.sora.widget.component.EditorAutoCompletion;
|
||||
import io.github.rosemoe.sora.widget.component.EditorTextActionWindow;
|
||||
|
||||
public class IDEEditor extends CodeEditor {
|
||||
public class IDEEditor extends CodeEditor implements com.itsaky.androidide.editor.IEditor {
|
||||
|
||||
public static final String KEY_FILE = "editor_file";
|
||||
private static final ILogger LOG = ILogger.newInstance("IDEEditor");
|
||||
@@ -77,6 +76,7 @@ public class IDEEditor extends CodeEditor {
|
||||
private IDEEditorSearcher searcher;
|
||||
private int fileVersion;
|
||||
private File file;
|
||||
private boolean isModified;
|
||||
private ILanguageServer languageServer;
|
||||
private SignatureHelpWindow signatureHelpWindow;
|
||||
private DiagnosticWindow diagnosticWindow;
|
||||
@@ -97,8 +97,9 @@ public class IDEEditor extends CodeEditor {
|
||||
public IDEEditor(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
|
||||
actionsMenu = new EditorActionsMenu(this);
|
||||
actionsMenu.init();
|
||||
this.isModified = false;
|
||||
this.actionsMenu = new EditorActionsMenu(this);
|
||||
this.actionsMenu.init();
|
||||
|
||||
final var window = new EditorCompletionWindow(this);
|
||||
window.setAdapter(new CompletionListAdapter());
|
||||
@@ -140,6 +141,7 @@ public class IDEEditor extends CodeEditor {
|
||||
*
|
||||
* @return The file instance.
|
||||
*/
|
||||
@Override
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
@@ -173,50 +175,16 @@ public class IDEEditor extends CodeEditor {
|
||||
EventBus.getDefault().post(openEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the language server that the content of this file has been changed.
|
||||
*
|
||||
* @param event The content change event.
|
||||
*/
|
||||
private void handleContentChange(ContentChangeEvent event, Unsubscribe unsubscribe) {
|
||||
if (getFile() == null || languageServer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
CompletableFuture.runAsync(
|
||||
() -> {
|
||||
dispatchDocumentChangeEvent(event);
|
||||
checkForSignatureHelp(event);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the content change event should trigger signature help. Signature help trigger
|
||||
* characters are :
|
||||
*
|
||||
* <ul>
|
||||
* <li><code>'('</code> (parentheses)
|
||||
* <li><code>','</code> (comma)
|
||||
* </ul>
|
||||
*
|
||||
* @param event The content change event.
|
||||
*/
|
||||
private void checkForSignatureHelp(@NonNull ContentChangeEvent event) {
|
||||
if (event.getAction() != ContentChangeEvent.ACTION_INSERT
|
||||
|| event.getChangedText().length() != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var ch = event.getChangedText().charAt(0);
|
||||
if (ch == '(' || ch == ',') {
|
||||
signatureHelp();
|
||||
}
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return isModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* If any language server is set, requests signature help at the cursor's position. On a valid
|
||||
* response, shows the signature help in a popup window.
|
||||
*/
|
||||
@Override
|
||||
public void signatureHelp() {
|
||||
if (languageServer != null && getFile() != null) {
|
||||
final CompletableFuture<SignatureHelp> future =
|
||||
@@ -248,6 +216,7 @@ public class IDEEditor extends CodeEditor {
|
||||
*
|
||||
* @param help The signature help data to show.
|
||||
*/
|
||||
@Override
|
||||
public void showSignatureHelp(SignatureHelp help) {
|
||||
getSignatureHelpWindow().setupAndDisplay(help);
|
||||
}
|
||||
@@ -260,70 +229,167 @@ public class IDEEditor extends CodeEditor {
|
||||
return signatureHelpWindow;
|
||||
}
|
||||
|
||||
protected void dispatchDocumentChangeEvent(final ContentChangeEvent event) {
|
||||
if (getFile() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var file = getFile().toPath();
|
||||
var type = ChangeType.INSERT;
|
||||
if (event.getAction() == ContentChangeEvent.ACTION_DELETE) {
|
||||
type = ChangeType.DELETE;
|
||||
} else if (event.getAction() == ContentChangeEvent.ACTION_SET_NEW_TEXT) {
|
||||
type = ChangeType.NEW_TEXT;
|
||||
}
|
||||
|
||||
var changeDelta = type == ChangeType.NEW_TEXT ? 0 : event.getChangedText().length();
|
||||
if (type == ChangeType.DELETE) {
|
||||
changeDelta = -changeDelta;
|
||||
}
|
||||
|
||||
final var start = event.getChangeStart();
|
||||
final var end = event.getChangeEnd();
|
||||
final var changeRange =
|
||||
new Range(
|
||||
new Position(start.line, start.column, start.index),
|
||||
new Position(end.line, end.column, end.index));
|
||||
|
||||
final var changeEvent =
|
||||
new DocumentChangeEvent(
|
||||
file, getText().toString(), fileVersion + 1, type, changeDelta, changeRange);
|
||||
EventBus.getDefault().post(changeEvent);
|
||||
}
|
||||
|
||||
public static int createInputFlags() {
|
||||
var flags =
|
||||
EditorInfo.TYPE_CLASS_TEXT
|
||||
| EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
| EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
|
||||
if (getVisiblePasswordFlag()) {
|
||||
flags |= EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
public void analyze() {
|
||||
if (languageServer != null && getFile() != null && getEditorLanguage() instanceof IDELanguage) {
|
||||
CompletableFuture.supplyAsync(() -> languageServer.analyze(getFile().toPath()))
|
||||
.whenComplete(
|
||||
(diagnostics, throwable) -> {
|
||||
if (languageClient != null) {
|
||||
languageClient.publishDiagnostics(diagnostics);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the selection of this editor to the given position.
|
||||
*
|
||||
* @param position The position to select.
|
||||
*/
|
||||
@Override
|
||||
public void setSelection(@NonNull Position position) {
|
||||
setSelection(position.getLine(), position.getColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set selection to the given range.
|
||||
*
|
||||
* @param range The range to select.
|
||||
*/
|
||||
@Override
|
||||
public void setSelection(@NonNull Range range) {
|
||||
if (isValidRange(range)) {
|
||||
setSelectionRegion(
|
||||
range.getStart().getLine(),
|
||||
range.getStart().getColumn(),
|
||||
range.getEnd().getLine(),
|
||||
range.getEnd().getColumn());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cursor's selection range in the form of {@link Range}.
|
||||
*
|
||||
* @return The {@link Range} of the cursor.
|
||||
*/
|
||||
@Override
|
||||
public Range getCursorLSPRange() {
|
||||
final var cursor = getCursor();
|
||||
final var start = new Position(cursor.getLeftLine(), cursor.getLeftColumn());
|
||||
final var end = new Position(cursor.getRightLine(), cursor.getRightColumn());
|
||||
return new Range(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cursor's position in the form of {@link Position}.
|
||||
*
|
||||
* @return The {@link Position} of the cursor.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unused")
|
||||
public Position getCursorLSPPosition() {
|
||||
return new Position(getCursor().getLeftLine(), getCursor().getLeftColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the range if it is invalid and returns a valid range.
|
||||
*
|
||||
* @param range Th range to validate.
|
||||
* @return A new, validated range.
|
||||
*/
|
||||
@Override
|
||||
public Range validateRange(@NonNull final Range range) {
|
||||
final var start = range.getStart();
|
||||
final var end = range.getEnd();
|
||||
|
||||
if (start.getLine() < 0) {
|
||||
start.setLine(0);
|
||||
} else if (start.getLine() >= getText().getLineCount()) {
|
||||
start.setLine(getText().getLineCount() - 1);
|
||||
}
|
||||
|
||||
if (end.getLine() < 0) {
|
||||
end.setLine(0);
|
||||
} else if (end.getLine() >= getText().getLineCount()) {
|
||||
end.setLine(getText().getLineCount() - 1);
|
||||
}
|
||||
|
||||
if (end.getLine() < start.getLine()) {
|
||||
var l = end.getLine();
|
||||
var l2 = start.getLine();
|
||||
start.setLine(l);
|
||||
end.setLine(l2);
|
||||
}
|
||||
|
||||
if (start.getColumn() < 0) {
|
||||
start.setColumn(0);
|
||||
} else if (start.getColumn() >= getText().getColumnCount(start.getLine())) {
|
||||
start.setColumn(getText().getColumnCount(start.getLine()) - 1);
|
||||
}
|
||||
|
||||
if (end.getColumn() < 0) {
|
||||
end.setColumn(0);
|
||||
} else if (end.getColumn() >= getText().getColumnCount(end.getLine())) {
|
||||
end.setColumn(getText().getColumnCount(end.getLine()) - 1);
|
||||
}
|
||||
|
||||
if (end.getColumn() < start.getColumn()) {
|
||||
final var c = start.getColumn();
|
||||
final var c2 = end.getColumn();
|
||||
start.setColumn(c2);
|
||||
end.setColumn(c);
|
||||
}
|
||||
|
||||
return new Range(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given range is valid for this editor's text.
|
||||
*
|
||||
* @param range The range to check.
|
||||
* @return <code>true</code> if valid, <code>false</code> otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean isValidRange(final Range range) {
|
||||
if (range == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final var start = range.getStart();
|
||||
final var end = range.getEnd();
|
||||
|
||||
return isValidPosition(start)
|
||||
&& isValidPosition(end)
|
||||
&& start.compareTo(end) < 0; // make sure start position is before end position
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given position is valid for this editor's text.
|
||||
*
|
||||
* @param position The position to check.
|
||||
* @return <code>true</code> if valid, <code>false</code> otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean isValidPosition(final Position position) {
|
||||
if (position == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isValidLine(position.getLine())
|
||||
&& isValidColumn(position.getLine(), position.getColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given line is valid for this editor's text.
|
||||
*
|
||||
* @param line The line to check.
|
||||
* @return <code>true</code> if valid, <code>false</code> otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean isValidLine(int line) {
|
||||
return line >= 0 && line < getText().getLineCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given column is valid for this editor's text.
|
||||
*
|
||||
* @param line The line of the column to check.
|
||||
* @param column The column to check.
|
||||
* @return <code>true</code> if valid, <code>false</code> otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean isValidColumn(int line, int column) {
|
||||
return column >= 0 && column < getText().getColumnCount(line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the language server that this editor will connect with. If the language client is not set,
|
||||
* it'll be set to {@link IDELanguageClientImpl}.
|
||||
@@ -331,6 +397,7 @@ public class IDEEditor extends CodeEditor {
|
||||
* @param server The server to set. Provide <code>null</code> to disable all the language server
|
||||
* features.
|
||||
*/
|
||||
@Override
|
||||
public void setLanguageServer(ILanguageServer server) {
|
||||
this.languageServer = server;
|
||||
|
||||
@@ -345,6 +412,7 @@ public class IDEEditor extends CodeEditor {
|
||||
* @param text The text to append.
|
||||
* @return The line at which the text was appended.
|
||||
*/
|
||||
@Override
|
||||
public int append(CharSequence text) {
|
||||
final var content = getText();
|
||||
if (getLineCount() <= 0) {
|
||||
@@ -360,6 +428,7 @@ public class IDEEditor extends CodeEditor {
|
||||
}
|
||||
|
||||
/** Set the selection of the editor's cursor to the last line of the it's content. */
|
||||
@Override
|
||||
public void goToEnd() {
|
||||
final var line = getText().getLineCount() - 1;
|
||||
setSelection(line, 0);
|
||||
@@ -372,6 +441,7 @@ public class IDEEditor extends CodeEditor {
|
||||
* <p>If the server returns a valid response, and the file specified in the response is same the
|
||||
* file in this editor, the range specified in the response will be selected.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public void findDefinition() {
|
||||
if (getFile() == null) {
|
||||
@@ -452,76 +522,6 @@ public class IDEEditor extends CodeEditor {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set selection to the given range.
|
||||
*
|
||||
* @param range The range to select.
|
||||
*/
|
||||
public void setSelection(@NonNull Range range) {
|
||||
if (isValidRange(range)) {
|
||||
setSelectionRegion(
|
||||
range.getStart().getLine(),
|
||||
range.getStart().getColumn(),
|
||||
range.getEnd().getLine(),
|
||||
range.getEnd().getColumn());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given range is valid for this editor's text.
|
||||
*
|
||||
* @param range The range to check.
|
||||
* @return <code>true</code> if valid, <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean isValidRange(final Range range) {
|
||||
if (range == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final var start = range.getStart();
|
||||
final var end = range.getEnd();
|
||||
|
||||
return isValidPosition(start)
|
||||
&& isValidPosition(end)
|
||||
&& start.compareTo(end) < 0; // make sure start position is before end position
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given position is valid for this editor's text.
|
||||
*
|
||||
* @param position The position to check.
|
||||
* @return <code>true</code> if valid, <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean isValidPosition(final Position position) {
|
||||
if (position == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isValidLine(position.getLine())
|
||||
&& isValidColumn(position.getLine(), position.getColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given line is valid for this editor's text.
|
||||
*
|
||||
* @param line The line to check.
|
||||
* @return <code>true</code> if valid, <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean isValidLine(int line) {
|
||||
return line >= 0 && line < getText().getLineCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given column is valid for this editor's text.
|
||||
*
|
||||
* @param line The line of the column to check.
|
||||
* @param column The column to check.
|
||||
* @return <code>true</code> if valid, <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean isValidColumn(int line, int column) {
|
||||
return column >= 0 && column < getText().getColumnCount(line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the given dialog on the UI thread.
|
||||
*
|
||||
@@ -539,6 +539,7 @@ public class IDEEditor extends CodeEditor {
|
||||
* <p>If the server returns a valid response, that response is forwarded to the {@link
|
||||
* IDELanguageClientImpl}.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unused")
|
||||
public void findReferences() {
|
||||
if (getFile() == null) {
|
||||
@@ -619,20 +620,43 @@ public class IDEEditor extends CodeEditor {
|
||||
});
|
||||
}
|
||||
|
||||
/** Notify the language server that the file in this editor is about to be closed. */
|
||||
public void close() {
|
||||
if (getFile() == null) {
|
||||
LOG.info("No language server is available for this file");
|
||||
/**
|
||||
* Requests the language server to provided a semantically larger selection than the current
|
||||
* selection. If a valid response is received, that range will be selected.
|
||||
*/
|
||||
@Override
|
||||
public void expandSelection() {
|
||||
if (languageServer == null || getFile() == null) {
|
||||
LOG.error("Cannot expand selection. Language server or file is null");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchDocumentCloseEvent();
|
||||
//noinspection deprecation
|
||||
final var pd =
|
||||
ProgressDialog.show(
|
||||
getContext(), null, getContext().getString(string.please_wait), true, false);
|
||||
final CompletableFuture<Range> future =
|
||||
CompletableFuture.supplyAsync(
|
||||
() ->
|
||||
languageServer.expandSelection(
|
||||
new ExpandSelectionParams(getFile().toPath(), getCursorLSPRange())));
|
||||
|
||||
actionsMenu.unsubscribeEvents();
|
||||
ensureWindowsDismissed();
|
||||
future.whenComplete(
|
||||
((range, throwable) -> {
|
||||
pd.dismiss();
|
||||
|
||||
if (throwable != null) {
|
||||
LOG.error("Error computing expanded selection range", throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection ConstantConditions
|
||||
ThreadUtils.runOnUiThread(() -> setSelection(range));
|
||||
}));
|
||||
}
|
||||
|
||||
/** Ensures that all the windows are dismissed. */
|
||||
@Override
|
||||
public void ensureWindowsDismissed() {
|
||||
if (getDiagnosticWindow().isShowing()) {
|
||||
getDiagnosticWindow().dismiss();
|
||||
@@ -649,6 +673,121 @@ public class IDEEditor extends CodeEditor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the language server that the content of this file has been changed.
|
||||
*
|
||||
* @param event The content change event.
|
||||
*/
|
||||
private void handleContentChange(ContentChangeEvent event, Unsubscribe unsubscribe) {
|
||||
if (getFile() == null || languageServer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getAction() != ContentChangeEvent.ACTION_SET_NEW_TEXT) {
|
||||
isModified = true;
|
||||
}
|
||||
CompletableFuture.runAsync(
|
||||
() -> {
|
||||
dispatchDocumentChangeEvent(event);
|
||||
checkForSignatureHelp(event);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the content change event should trigger signature help. Signature help trigger
|
||||
* characters are :
|
||||
*
|
||||
* <ul>
|
||||
* <li><code>'('</code> (parentheses)
|
||||
* <li><code>','</code> (comma)
|
||||
* </ul>
|
||||
*
|
||||
* @param event The content change event.
|
||||
*/
|
||||
private void checkForSignatureHelp(@NonNull ContentChangeEvent event) {
|
||||
if (event.getAction() != ContentChangeEvent.ACTION_INSERT
|
||||
|| event.getChangedText().length() != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var ch = event.getChangedText().charAt(0);
|
||||
if (ch == '(' || ch == ',') {
|
||||
signatureHelp();
|
||||
}
|
||||
}
|
||||
|
||||
protected void dispatchDocumentChangeEvent(final ContentChangeEvent event) {
|
||||
if (getFile() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var file = getFile().toPath();
|
||||
var type = ChangeType.INSERT;
|
||||
if (event.getAction() == ContentChangeEvent.ACTION_DELETE) {
|
||||
type = ChangeType.DELETE;
|
||||
} else if (event.getAction() == ContentChangeEvent.ACTION_SET_NEW_TEXT) {
|
||||
type = ChangeType.NEW_TEXT;
|
||||
}
|
||||
|
||||
var changeDelta = type == ChangeType.NEW_TEXT ? 0 : event.getChangedText().length();
|
||||
if (type == ChangeType.DELETE) {
|
||||
changeDelta = -changeDelta;
|
||||
}
|
||||
|
||||
final var start = event.getChangeStart();
|
||||
final var end = event.getChangeEnd();
|
||||
final var changeRange =
|
||||
new Range(
|
||||
new Position(start.line, start.column, start.index),
|
||||
new Position(end.line, end.column, end.index));
|
||||
|
||||
final var changeEvent =
|
||||
new DocumentChangeEvent(
|
||||
file, getText().toString(), fileVersion + 1, type, changeDelta, changeRange);
|
||||
EventBus.getDefault().post(changeEvent);
|
||||
}
|
||||
|
||||
public static int createInputFlags() {
|
||||
var flags =
|
||||
EditorInfo.TYPE_CLASS_TEXT
|
||||
| EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
| EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
|
||||
if (getVisiblePasswordFlag()) {
|
||||
flags |= EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
public void markUnmodified() {
|
||||
this.isModified = false;
|
||||
}
|
||||
|
||||
public void analyze() {
|
||||
if (languageServer != null && getFile() != null && getEditorLanguage() instanceof IDELanguage) {
|
||||
CompletableFuture.supplyAsync(() -> languageServer.analyze(getFile().toPath()))
|
||||
.whenComplete(
|
||||
(diagnostics, throwable) -> {
|
||||
if (languageClient != null) {
|
||||
languageClient.publishDiagnostics(diagnostics);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Notify the language server that the file in this editor is about to be closed. */
|
||||
public void notifyClose() {
|
||||
if (getFile() == null) {
|
||||
LOG.info("No language server is available for this file");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchDocumentCloseEvent();
|
||||
|
||||
actionsMenu.unsubscribeEvents();
|
||||
ensureWindowsDismissed();
|
||||
}
|
||||
|
||||
protected void dispatchDocumentCloseEvent() {
|
||||
if (getFile() == null) {
|
||||
return;
|
||||
@@ -697,113 +836,6 @@ public class IDEEditor extends CodeEditor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the language server to provided a semantically larger selection than the current
|
||||
* selection. If a valid response is received, that range will be selected.
|
||||
*/
|
||||
public void expandSelection() {
|
||||
if (languageServer == null || getFile() == null) {
|
||||
LOG.error("Cannot expand selection. Language server or file is null");
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection deprecation
|
||||
final var pd =
|
||||
ProgressDialog.show(
|
||||
getContext(), null, getContext().getString(string.please_wait), true, false);
|
||||
final CompletableFuture<Range> future =
|
||||
CompletableFuture.supplyAsync(
|
||||
() ->
|
||||
languageServer.expandSelection(
|
||||
new ExpandSelectionParams(getFile().toPath(), getCursorLSPRange())));
|
||||
|
||||
future.whenComplete(
|
||||
((range, throwable) -> {
|
||||
pd.dismiss();
|
||||
|
||||
if (throwable != null) {
|
||||
LOG.error("Error computing expanded selection range", throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection ConstantConditions
|
||||
ThreadUtils.runOnUiThread(() -> setSelection(range));
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cursor's selection range in the form of {@link Range}.
|
||||
*
|
||||
* @return The {@link Range} of the cursor.
|
||||
*/
|
||||
public Range getCursorLSPRange() {
|
||||
final var cursor = getCursor();
|
||||
final var start = new Position(cursor.getLeftLine(), cursor.getLeftColumn());
|
||||
final var end = new Position(cursor.getRightLine(), cursor.getRightColumn());
|
||||
return new Range(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cursor's position in the form of {@link Position}.
|
||||
*
|
||||
* @return The {@link Position} of the cursor.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public Position getCursorLSPPosition() {
|
||||
return new Position(getCursor().getLeftLine(), getCursor().getLeftColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the range if it is invalid and returns a valid range.
|
||||
*
|
||||
* @param range Th range to validate.
|
||||
* @return A new, validated range.
|
||||
*/
|
||||
public Range validateRange(@NonNull final Range range) {
|
||||
final var start = range.getStart();
|
||||
final var end = range.getEnd();
|
||||
|
||||
if (start.getLine() < 0) {
|
||||
start.setLine(0);
|
||||
} else if (start.getLine() >= getText().getLineCount()) {
|
||||
start.setLine(getText().getLineCount() - 1);
|
||||
}
|
||||
|
||||
if (end.getLine() < 0) {
|
||||
end.setLine(0);
|
||||
} else if (end.getLine() >= getText().getLineCount()) {
|
||||
end.setLine(getText().getLineCount() - 1);
|
||||
}
|
||||
|
||||
if (end.getLine() < start.getLine()) {
|
||||
var l = end.getLine();
|
||||
var l2 = start.getLine();
|
||||
start.setLine(l);
|
||||
end.setLine(l2);
|
||||
}
|
||||
|
||||
if (start.getColumn() < 0) {
|
||||
start.setColumn(0);
|
||||
} else if (start.getColumn() >= getText().getColumnCount(start.getLine())) {
|
||||
start.setColumn(getText().getColumnCount(start.getLine()) - 1);
|
||||
}
|
||||
|
||||
if (end.getColumn() < 0) {
|
||||
end.setColumn(0);
|
||||
} else if (end.getColumn() >= getText().getColumnCount(end.getLine())) {
|
||||
end.setColumn(getText().getColumnCount(end.getLine()) - 1);
|
||||
}
|
||||
|
||||
if (end.getColumn() < start.getColumn()) {
|
||||
final var c = start.getColumn();
|
||||
final var c2 = end.getColumn();
|
||||
start.setColumn(c2);
|
||||
end.setColumn(c);
|
||||
}
|
||||
|
||||
return new Range(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDEEditorSearcher getSearcher() {
|
||||
return searcher;
|
||||
@@ -828,6 +860,7 @@ public class IDEEditor extends CodeEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
isModified = false;
|
||||
final var saveEvent = new DocumentSaveEvent(getFile().toPath());
|
||||
EventBus.getDefault().post(saveEvent);
|
||||
}
|
||||
|
||||
+2
-2
@@ -38,8 +38,8 @@ buildscript {
|
||||
versionName = Versions.INSTANCE.versionName
|
||||
packageName = "com.itsaky.androidide"
|
||||
|
||||
javaSourceVersion = JavaVersion.VERSION_11
|
||||
javaTargetVersion = JavaVersion.VERSION_11
|
||||
javaSourceVersion = Versions.INSTANCE.javaVersion
|
||||
javaTargetVersion = Versions.INSTANCE.javaVersion
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
package com.itsaky.androidide
|
||||
|
||||
import org.gradle.api.JavaVersion
|
||||
|
||||
/**
|
||||
* Version constants for the IDE.
|
||||
*
|
||||
@@ -52,6 +54,9 @@ object Versions {
|
||||
version
|
||||
}
|
||||
|
||||
/** The source and target Java compatibility. */
|
||||
val javaVersion = JavaVersion.VERSION_11
|
||||
|
||||
/** The release version name. */
|
||||
const val releaseVersionName = "2.1.2-beta"
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,39 @@
|
||||
import com.itsaky.androidide.Versions
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.itsaky.androidide.editor"
|
||||
compileSdk = Versions.compileSdk
|
||||
buildToolsVersion = Versions.buildTools
|
||||
|
||||
defaultConfig {
|
||||
minSdk = Versions.minSdk
|
||||
targetSdk = Versions.targetSdk
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = Versions.javaVersion
|
||||
targetCompatibility = Versions.javaVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(path = ":common"))
|
||||
implementation(project(path = ":shared"))
|
||||
implementation(project(path = ":lsp:api"))
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.editor;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("com.itsaky.androidide.editor.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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.editor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServer;
|
||||
import com.itsaky.androidide.lsp.models.SignatureHelp;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Interface for other modules to access the editor.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface IEditor {
|
||||
|
||||
/**
|
||||
* Get the file that this editor is currently editing.
|
||||
*
|
||||
* @return The file instance.
|
||||
*/
|
||||
File getFile();
|
||||
|
||||
/**
|
||||
* Whether the content of this editor differs from the content of the file on disk.
|
||||
*
|
||||
* @return <code>true</code> if the content has been modified, <code>false</code> otherwise.
|
||||
*/
|
||||
boolean isModified();
|
||||
|
||||
/**
|
||||
* If any language server is set, requests signature help at the cursor's position. On a valid
|
||||
* response, shows the signature help in a popup window.
|
||||
*/
|
||||
void signatureHelp();
|
||||
|
||||
/**
|
||||
* Shows the given signature help in the editor.
|
||||
*
|
||||
* @param help The signature help data to show.
|
||||
*/
|
||||
void showSignatureHelp(SignatureHelp help);
|
||||
|
||||
/**
|
||||
* Set the selection of this editor to the given position.
|
||||
*
|
||||
* @param position The position to select.
|
||||
*/
|
||||
void setSelection(@NonNull Position position);
|
||||
|
||||
/**
|
||||
* Set selection to the given range.
|
||||
*
|
||||
* @param range The range to select.
|
||||
*/
|
||||
void setSelection(@NonNull Range range);
|
||||
|
||||
/**
|
||||
* Get the cursor's selection range in the form of {@link Range}.
|
||||
*
|
||||
* @return The {@link Range} of the cursor.
|
||||
*/
|
||||
Range getCursorLSPRange();
|
||||
|
||||
/**
|
||||
* Get the cursor's position in the form of {@link Position}.
|
||||
*
|
||||
* @return The {@link Position} of the cursor.
|
||||
*/
|
||||
Position getCursorLSPPosition();
|
||||
|
||||
/**
|
||||
* Validates the range if it is invalid and returns a valid range.
|
||||
*
|
||||
* @param range Th range to validate.
|
||||
* @return A new, validated range.
|
||||
*/
|
||||
Range validateRange(@NonNull Range range);
|
||||
|
||||
/**
|
||||
* Checks if the given range is valid for this editor's text.
|
||||
*
|
||||
* @param range The range to check.
|
||||
* @return <code>true</code> if valid, <code>false</code> otherwise.
|
||||
*/
|
||||
boolean isValidRange(Range range);
|
||||
|
||||
/**
|
||||
* Checks if the given position is valid for this editor's text.
|
||||
*
|
||||
* @param position The position to check.
|
||||
* @return <code>true</code> if valid, <code>false</code> otherwise.
|
||||
*/
|
||||
boolean isValidPosition(Position position);
|
||||
|
||||
/**
|
||||
* Checks if the given line is valid for this editor's text.
|
||||
*
|
||||
* @param line The line to check.
|
||||
* @return <code>true</code> if valid, <code>false</code> otherwise.
|
||||
*/
|
||||
boolean isValidLine(int line);
|
||||
|
||||
/**
|
||||
* Checks if the given column is valid for this editor's text.
|
||||
*
|
||||
* @param line The line of the column to check.
|
||||
* @param column The column to check.
|
||||
* @return <code>true</code> if valid, <code>false</code> otherwise.
|
||||
*/
|
||||
boolean isValidColumn(int line, int column);
|
||||
|
||||
/**
|
||||
* Set the language server that this editor will connect with. If the language client is not set,
|
||||
* it'll be set to {@link IDELanguageClientImpl}.
|
||||
*
|
||||
* @param server The server to set. Provide <code>null</code> to disable all the language server
|
||||
* features.
|
||||
*/
|
||||
void setLanguageServer(ILanguageServer server);
|
||||
|
||||
/**
|
||||
* Append the given text at the end of the editor's content.
|
||||
*
|
||||
* @param text The text to append.
|
||||
* @return The line at which the text was appended.
|
||||
*/
|
||||
int append(CharSequence text);
|
||||
|
||||
/** Set the selection of the editor's cursor to the last line of the it's content. */
|
||||
void goToEnd();
|
||||
|
||||
/**
|
||||
* If any language server is set, asks the language server to find the definition of token at the
|
||||
* cursor position.
|
||||
*
|
||||
* <p>If the server returns a valid response, and the file specified in the response is same the
|
||||
* file in this editor, the range specified in the response will be selected.
|
||||
*/
|
||||
void findDefinition();
|
||||
|
||||
/**
|
||||
* If any language server instance is set, finds the references to of the token at the current
|
||||
* cursor position.
|
||||
*
|
||||
* <p>If the server returns a valid response, that response is forwarded to the {@link
|
||||
* IDELanguageClientImpl}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
void findReferences();
|
||||
|
||||
/**
|
||||
* Requests the language server to provided a semantically larger selection than the current
|
||||
* selection. If a valid response is received, that range will be selected.
|
||||
*/
|
||||
void expandSelection();
|
||||
|
||||
/** Ensures that all the windows are dismissed. */
|
||||
void ensureWindowsDismissed();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.editor;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
@@ -43,3 +43,4 @@ include ':build-tools:tooling-api-testing'
|
||||
include ':build-tools:xml-dom'
|
||||
include ':build-tools:xml-formatter'
|
||||
include ':build-tools:xml-utils'
|
||||
include ':editor'
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user