mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Working to support lsp4j
This commit is contained in:
+2
-1
@@ -1,5 +1,4 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'com.itsaky.androidide'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
apply plugin: 'stringfog'
|
||||
@@ -136,6 +135,8 @@ dependencies {
|
||||
implementation project(':emulator')
|
||||
implementation project(':emulatorview')
|
||||
implementation project(':javapoet')
|
||||
|
||||
implementation 'org.eclipse.lsp4j:org.eclipse.lsp4j:0.12.0'
|
||||
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -31,6 +31,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.transition.MaterialContainerTransform;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.itsaky.androidide.adapters.EditorPagerAdapter;
|
||||
import com.itsaky.androidide.app.StudioActivity;
|
||||
@@ -48,8 +50,11 @@ import com.itsaky.androidide.handlers.IDEHandler;
|
||||
import com.itsaky.androidide.handlers.jls.JLSHandler;
|
||||
import com.itsaky.androidide.handlers.jls.LanguageClientHandler;
|
||||
import com.itsaky.androidide.interfaces.DiagnosticClickListener;
|
||||
import com.itsaky.androidide.interfaces.EditorActivityProvider;
|
||||
import com.itsaky.androidide.language.buildout.BuildOutputLanguage;
|
||||
import com.itsaky.androidide.language.logs.LogLanguageImpl;
|
||||
import com.itsaky.androidide.lsp.LSP;
|
||||
import com.itsaky.androidide.lsp.LSPProvider;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.managers.VersionedFileManager;
|
||||
import com.itsaky.androidide.models.AndroidProject;
|
||||
@@ -85,10 +90,16 @@ import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Pattern;
|
||||
import me.piruin.quickaction.ActionItem;
|
||||
import me.piruin.quickaction.QuickAction;
|
||||
import org.eclipse.lsp4j.DidChangeConfigurationParams;
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import com.itsaky.androidide.utils.LSPUtils;
|
||||
|
||||
public class EditorActivity extends StudioActivity implements FileTreeFragment.FileActionListener,
|
||||
TabLayout.OnTabSelectedListener,
|
||||
@@ -96,7 +107,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
DiagnosticClickListener,
|
||||
EditorFragment.FileOpenListener,
|
||||
EditorPagerAdapter.EditorStateListener,
|
||||
IDEHandler.Provider {
|
||||
IDEHandler.Provider,
|
||||
EditorActivityProvider {
|
||||
|
||||
private ActivityEditorBinding mBinding;
|
||||
private EditorPagerAdapter mPagerAdapter;
|
||||
@@ -131,6 +143,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
|
||||
private static final String TAG_FILE_OPTIONS_FRAGMENT = "file_options_fragment";
|
||||
public static final String TAG = "EditorActivity";
|
||||
|
||||
private static final org.eclipse.lsp4j.Range Range_ofZero = new org.eclipse.lsp4j.Range(new Position(0, 0), new Position(0, 0));
|
||||
|
||||
private static final int ACTIONID_CLOSE = 100;
|
||||
private static final int ACTIONID_OTHERS = 101;
|
||||
@@ -204,6 +218,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
registerLogReceiver();
|
||||
setupContainers();
|
||||
setupSignatureText();
|
||||
|
||||
startLanguageServers();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -380,6 +396,11 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
invalidateOptionsMenu();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EditorActivity provide() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EditorActivity provideEditorActivity() {
|
||||
@@ -592,7 +613,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
|
||||
@Override
|
||||
public void onDiagnosticClick(File file, Diagnostic diagnostic) {
|
||||
openFileAndSelect(file, diagnostic.range);
|
||||
// openFileAndSelect(file, diagnostic.getRange());
|
||||
hideDiagnostics();
|
||||
}
|
||||
|
||||
@@ -639,16 +660,16 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
}
|
||||
}
|
||||
|
||||
public void openFileAndSelect(File file, Range range) {
|
||||
public void openFileAndSelect(File file, org.eclipse.lsp4j.Range range) {
|
||||
openFile(file, range);
|
||||
EditorFragment opened = mPagerAdapter.findEditorByFile(file);
|
||||
if (opened != null && opened.getEditor() != null) {
|
||||
CodeEditor editor = opened.getEditor();
|
||||
editor.post(() -> {
|
||||
if (range.start.equals(range.end)) {
|
||||
editor.setSelection(range.start.line, range.start.column);
|
||||
if (LSPUtils.isEqual(range.getStart(), range.getEnd())) {
|
||||
editor.setSelection(range.getStart().getLine(), range.getEnd().getCharacter());
|
||||
} else {
|
||||
editor.setSelectionRegion(range.start.line, range.start.column, range.end.line, range.end.column);
|
||||
editor.setSelectionRegion(range.getStart().getLine(), range.getStart().getCharacter(), range.getEnd().getLine(), range.getEnd().getCharacter());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -867,8 +888,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
openFile(file, null);
|
||||
}
|
||||
|
||||
public EditorFragment openFile(File file, Range selection) {
|
||||
if(selection == null) selection = Range.ofZero();
|
||||
public EditorFragment openFile(File file, org.eclipse.lsp4j.Range selection) {
|
||||
if(selection == null) selection = Range_ofZero;
|
||||
int i = mPagerAdapter.openFile(file, selection, this, mJLSHandler);
|
||||
if(i >= 0 && !mBinding.tabs.getTabAt(i).isSelected())
|
||||
mBinding.tabs.getTabAt(i).select();
|
||||
@@ -935,7 +956,27 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
public void createServices() {
|
||||
new TaskExecutor().executeAsync(() -> {
|
||||
getApp().createCompletionService();
|
||||
mJLSHandler.start();
|
||||
|
||||
LanguageServer javaServer = LSPProvider.getServerForLanguage(LSPProvider.LANGUAGE_JAVA);
|
||||
if(javaServer == null) return null;
|
||||
|
||||
List<String> cps = mProject.getClassPaths();
|
||||
JsonObject settings = new JsonObject();
|
||||
JsonObject java = new JsonObject();
|
||||
JsonArray classPath = new JsonArray();
|
||||
|
||||
for(int i=0;i<cps.size();i++) {
|
||||
classPath.add(cps.get(i));
|
||||
}
|
||||
|
||||
java.add("classPath", classPath);
|
||||
settings.add("java", java);
|
||||
|
||||
DidChangeConfigurationParams params = new DidChangeConfigurationParams();
|
||||
params.setSettings(settings);
|
||||
|
||||
javaServer.getWorkspaceService().didChangeConfiguration(params);
|
||||
|
||||
return null;
|
||||
}, __ -> {
|
||||
setStatus(getString(getApp().areCompletorsStarted() ? R.string.msg_service_started : R.string.msg_starting_completion_failed));
|
||||
@@ -997,6 +1038,19 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
////////////// PRIVATE APIS /////////////////////
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
private void startLanguageServers() {
|
||||
LSP.Java.start(() -> {
|
||||
Optional<InitializeResult> result = LSP.Java.init(mProject.getProjectPath());
|
||||
|
||||
if(result.isPresent()) {
|
||||
// TODO Handle initialize result
|
||||
// TODO Register server capablities
|
||||
} else if(true) {
|
||||
// TODO Show user that we cant initialize Java language server
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void getProjectFromIntent() {
|
||||
this.mProject = getIntent().getParcelableExtra(EXTRA_PROJECT);
|
||||
getApp().getPrefManager().setOpenedProject(this.mProject.getProjectPath());
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
@@ -18,7 +19,6 @@ import com.itsaky.androidide.databinding.LayoutCompletionItemBinding;
|
||||
import com.itsaky.androidide.models.CompletionItemWrapper;
|
||||
import com.itsaky.androidide.models.CompletionListItem;
|
||||
import com.itsaky.androidide.models.SuggestItem;
|
||||
import com.itsaky.androidide.utils.Either;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.androidide.utils.TypefaceUtils;
|
||||
import com.itsaky.apiinfo.ApiInfo;
|
||||
@@ -26,10 +26,10 @@ import com.itsaky.apiinfo.models.ClassInfo;
|
||||
import com.itsaky.apiinfo.models.FieldInfo;
|
||||
import com.itsaky.apiinfo.models.Info;
|
||||
import com.itsaky.apiinfo.models.MethodInfo;
|
||||
import com.itsaky.lsp.CompletionItemKind;
|
||||
import io.github.rosemoe.editor.struct.CompletionItem;
|
||||
import io.github.rosemoe.editor.widget.EditorCompletionAdapter;
|
||||
import java.util.Locale;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
|
||||
public class CompletionListAdapter extends EditorCompletionAdapter {
|
||||
|
||||
@@ -44,23 +44,10 @@ public class CompletionListAdapter extends EditorCompletionAdapter {
|
||||
protected View getView(int position, View convertView, ViewGroup parent, boolean isCurrentCursorPosition) {
|
||||
LayoutCompletionItemBinding binding = LayoutCompletionItemBinding.inflate(LayoutInflater.from(getContext()), parent, false);
|
||||
|
||||
Either<SuggestItem, CompletionItem> e = getItem(position);
|
||||
String label = "", desc = "", type = "";
|
||||
String header = "";
|
||||
if (e != null && e.isLeft()) {
|
||||
SuggestItem s = e.getLeft();
|
||||
label = s.getName();
|
||||
desc = s.getDescription();
|
||||
type = s.getReturnType();
|
||||
header = String.valueOf(s.getTypeHeader()).toUpperCase(Locale.US);
|
||||
} else if (e != null && e.isRight()) {
|
||||
CompletionListItem item = (CompletionListItem) e.getRight();
|
||||
header = item.getType() == null ? CompletionListItem.Type.OBJECT.getAsString() : item.getType().getAsString();
|
||||
label = item.getLabel();
|
||||
type = item.getItemType().contains(".") ? item.getItemType().substring(item.getItemType().lastIndexOf(".") + 1) : item.getItemType();
|
||||
desc = item.getType() != CompletionListItem.Type.NOT_IMPORTED_CLASS ? item.getDetail() : getContext().getString(R.string.label_not_imported, item.getDetail());
|
||||
}
|
||||
|
||||
CompletionItem item = getItem(position);
|
||||
String label = item.getLabel(), desc = item.getDetail(), type = item.getKind().toString();
|
||||
String header = type == null || type.length() <= 0 ? "O" : String.valueOf(type.toString().charAt(0));
|
||||
|
||||
binding.completionIconText.setText(header);
|
||||
binding.completionLabel.setText(label);
|
||||
binding.completionType.setText(type);
|
||||
@@ -75,8 +62,8 @@ public class CompletionListAdapter extends EditorCompletionAdapter {
|
||||
|
||||
binding.completionApiInfo.setVisibility(View.GONE);
|
||||
|
||||
if (e != null && e.isLeft() && e.getLeft() instanceof CompletionItemWrapper) {
|
||||
showApiInfoIfNeeded((CompletionItemWrapper) e.getLeft(), binding.completionApiInfo);
|
||||
if (item != null) {
|
||||
showApiInfoIfNeeded(item, binding.completionApiInfo);
|
||||
}
|
||||
|
||||
binding.getRoot().getViewTreeObserver().addOnGlobalLayoutListener(() -> {
|
||||
@@ -89,22 +76,22 @@ public class CompletionListAdapter extends EditorCompletionAdapter {
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void showApiInfoIfNeeded(final CompletionItemWrapper item, final TextView completionApiInfo) {
|
||||
private void showApiInfoIfNeeded(final CompletionItem item, final TextView completionApiInfo) {
|
||||
|
||||
new Thread(() -> {
|
||||
final ApiInfo info = StudioApp.getInstance().getApiInfo();
|
||||
boolean hasRead = info != null && info.hasRead();
|
||||
boolean isValid = isValidForApiVersion(item.getItem());
|
||||
boolean isValid = isValidForApiVersion(item);
|
||||
|
||||
if (hasRead && isValid) {
|
||||
|
||||
JsonElement element = item.getItem().data;
|
||||
JsonElement element = new Gson().toJsonTree(item.getData());
|
||||
if (element == null || !element.isJsonObject()) return;
|
||||
JsonObject data = element.getAsJsonObject();
|
||||
if (!data.has("className")) return;
|
||||
|
||||
final String className = data.get("className").getAsString();
|
||||
int kind = item.getItem().kind;
|
||||
CompletionItemKind kind = item.getKind();
|
||||
|
||||
ClassInfo clazz = info.getClassByName(className);
|
||||
if(clazz == null) return;
|
||||
@@ -168,10 +155,10 @@ public class CompletionListAdapter extends EditorCompletionAdapter {
|
||||
}).start();
|
||||
}
|
||||
|
||||
private boolean isValidForApiVersion(com.itsaky.lsp.CompletionItem item) {
|
||||
private boolean isValidForApiVersion(CompletionItem item) {
|
||||
if (item == null) return false;
|
||||
final int type = item.kind;
|
||||
JsonElement element = item.data;
|
||||
final CompletionItemKind type = item.getKind();
|
||||
JsonElement element = new Gson().toJsonTree(item.getData());
|
||||
if (
|
||||
|
||||
/**
|
||||
|
||||
@@ -66,7 +66,7 @@ public class EditorPagerAdapter extends FragmentStatePagerAdapter implements Edi
|
||||
return null;
|
||||
}
|
||||
|
||||
public int openFile(File file, Range selection, EditorFragment.FileOpenListener listener, JLSRequestor jlsRequestor) {
|
||||
public int openFile(File file, org.eclipse.lsp4j.Range selection, EditorFragment.FileOpenListener listener, JLSRequestor jlsRequestor) {
|
||||
int openedFileIndex = -1;
|
||||
for (int i=0;i < mOpenedFiles.size();i++) {
|
||||
File f = mOpenedFiles.get(i);
|
||||
|
||||
@@ -44,6 +44,7 @@ import java.util.Map;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import io.github.rosemoe.editor.text.Content;
|
||||
import com.itsaky.androidide.utils.LSPUtils;
|
||||
|
||||
public class EditorFragment extends BaseFragment implements EditorEventListener {
|
||||
|
||||
@@ -104,13 +105,13 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
|
||||
}
|
||||
}
|
||||
|
||||
public static EditorFragment newInstance(File file, AndroidProject project, Range selection) {
|
||||
public static EditorFragment newInstance(File file, AndroidProject project, org.eclipse.lsp4j.Range selection) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KEY_FILE_PATH, file.getAbsolutePath());
|
||||
bundle.putInt(KEY_LINE_START, selection.start.line);
|
||||
bundle.putInt(KEY_LINE_END, selection.end.line);
|
||||
bundle.putInt(KEY_COLUMN_START, selection.start.column);
|
||||
bundle.putInt(KEY_COLUMN_END, selection.end.column);
|
||||
bundle.putInt(KEY_LINE_START, selection.getStart().getLine());
|
||||
bundle.putInt(KEY_LINE_END, selection.getEnd().getLine());
|
||||
bundle.putInt(KEY_COLUMN_START, selection.getStart().getCharacter());
|
||||
bundle.putInt(KEY_COLUMN_END, selection.getEnd().getCharacter());
|
||||
bundle.putParcelable(KEY_PROJECT, project);
|
||||
EditorFragment frag = new EditorFragment();
|
||||
frag.setArguments(bundle);
|
||||
@@ -164,32 +165,32 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
|
||||
|
||||
configureEditorIfNeeded();
|
||||
|
||||
final Range range = fromArgs(getArguments());
|
||||
final org.eclipse.lsp4j.Range range = fromArgs(getArguments());
|
||||
new TaskExecutor().executeAsync(new ReadFileTask(mFile), result -> {
|
||||
mBinding.editor.setText(result);
|
||||
postRead();
|
||||
mBinding.editor.post(() -> {
|
||||
if(range.start.equals(range.end)) {
|
||||
getEditor().setSelection(range.start.line, range.start.column);
|
||||
if(LSPUtils.isEqual(range.getStart(), range.getEnd())) {
|
||||
getEditor().setSelection(range.getStart().getLine(), range.getStart().getCharacter());
|
||||
} else {
|
||||
getEditor().setSelectionRegion(range.start.line, range.start.column, range.end.line, range.end.column);
|
||||
getEditor().setSelectionRegion(range.getStart().getLine(), range.getStart().getCharacter(), range.getEnd().getLine(), range.getEnd().getCharacter());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private Range fromArgs(Bundle args) {
|
||||
private org.eclipse.lsp4j.Range fromArgs(Bundle args) {
|
||||
if(!(args.containsKey(KEY_LINE_START)
|
||||
&& args.containsKey(KEY_COLUMN_START)
|
||||
&& args.containsKey(KEY_LINE_END)
|
||||
&& args.containsKey(KEY_COLUMN_END)))
|
||||
return Range.ofZero();
|
||||
return LSPUtils.Range_ofZero;
|
||||
|
||||
return new Range(
|
||||
new Position(
|
||||
return new org.eclipse.lsp4j.Range(
|
||||
new org.eclipse.lsp4j.Position(
|
||||
args.getInt(KEY_LINE_START),
|
||||
args.getInt(KEY_COLUMN_START)),
|
||||
new Position(
|
||||
new org.eclipse.lsp4j.Position(
|
||||
args.getInt(KEY_LINE_END),
|
||||
args.getInt(KEY_COLUMN_END)
|
||||
));
|
||||
@@ -302,7 +303,6 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
|
||||
}
|
||||
|
||||
private void postRead() {
|
||||
mBinding.editor.setFile(getFile());
|
||||
if (mFile.isFile() && mFile.getName().endsWith(EXT_JAVA)) {
|
||||
mBinding.editor.setEditorLanguage(mJavaLanguage = new JavaLanguage(mProject));
|
||||
} else if (mFile.isFile() && mFile.getName().endsWith(EXT_XML)) {
|
||||
@@ -312,6 +312,11 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
|
||||
} else {
|
||||
mBinding.editor.setEditorLanguage(new EmptyLanguage());
|
||||
}
|
||||
|
||||
// File must be set only after setting the language server
|
||||
// This will make sure that textDocument/didOpen is sent
|
||||
mBinding.editor.setFile(getFile());
|
||||
|
||||
mBinding.editor.setColorScheme(new SchemeAndroidIDE());
|
||||
isRead = true;
|
||||
if(mOpenListener != null)
|
||||
@@ -427,16 +432,7 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
|
||||
}
|
||||
|
||||
private void notifySaved(boolean wrote, String text) {
|
||||
if (wrote && mJLSRequestor != null) {
|
||||
try {
|
||||
TextDocumentIdentifier id = new TextDocumentIdentifier();
|
||||
id.uri = mFile.toURI();
|
||||
DidSaveTextDocumentParams p = new DidSaveTextDocumentParams();
|
||||
p.text = text;
|
||||
p.textDocument = id;
|
||||
mJLSRequestor.didSave(p);
|
||||
} catch (Throwable th) {}
|
||||
}
|
||||
mBinding.editor.didSave();
|
||||
|
||||
if(mModificationStateListener != null)
|
||||
mModificationStateListener.onSaved(this);
|
||||
|
||||
@@ -65,7 +65,7 @@ public class JLSHandler extends IDEHandler implements JLSRequestor {
|
||||
this.client.start();
|
||||
|
||||
this.languageServer = new JavaLanguageServer(project, this.client);
|
||||
this.languageServer.startServer();
|
||||
// this.languageServer.startServer();
|
||||
|
||||
StudioApp.getInstance().setJavaLanguageServer(languageServer);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.view.View;
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.ThrowableUtils;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.itsaky.androidide.EditorActivity;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.DiagnosticsAdapter;
|
||||
import com.itsaky.androidide.adapters.SearchListAdapter;
|
||||
@@ -24,7 +23,6 @@ import com.itsaky.lsp.Location;
|
||||
import com.itsaky.lsp.Message;
|
||||
import com.itsaky.lsp.ParameterInformation;
|
||||
import com.itsaky.lsp.PublishDiagnosticsParams;
|
||||
import com.itsaky.lsp.Range;
|
||||
import com.itsaky.lsp.ShowMessageParams;
|
||||
import com.itsaky.lsp.SignatureHelp;
|
||||
import com.itsaky.lsp.SignatureInformation;
|
||||
@@ -82,31 +80,7 @@ public class LanguageClientHandler extends IDEHandler implements LanguageClient
|
||||
activity().getApp().toast(R.string.msg_no_definition, Toaster.Type.ERROR);
|
||||
return;
|
||||
}
|
||||
Location loc = locations.get(0);
|
||||
final File file = new File(loc.uri);
|
||||
final Range range = loc.range;
|
||||
try {
|
||||
if(activity().getPagerAdapter() == null) return;
|
||||
if(activity().getPagerAdapter().getCount() <= 0) {
|
||||
activity().openFile(file, range);
|
||||
return;
|
||||
}
|
||||
EditorFragment frag = activity().getPagerAdapter().getFrag(activity().getBinding().tabs.getSelectedTabPosition());
|
||||
if(frag != null
|
||||
&& frag.getFile() != null
|
||||
&& frag.getEditor() != null
|
||||
&& frag.getFile().getAbsolutePath().equals(file.getAbsolutePath())) {
|
||||
if(range.start.equals(range.end)) {
|
||||
frag.getEditor().setSelection(range.start.line, range.start.column);
|
||||
} else {
|
||||
frag.getEditor().setSelectionRegion(range.start.line, range.start.column, range.end.line, range.end.column);
|
||||
}
|
||||
} else {
|
||||
activity().openFileAndSelect(file, range);
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
LOG.error(ThrowableUtils.getFullStackTrace(th));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,36 +99,36 @@ public class LanguageClientHandler extends IDEHandler implements LanguageClient
|
||||
}
|
||||
|
||||
final Map<File, List<SearchResult>> results = new HashMap<>();
|
||||
for(int i=0;i<references.size();i++) {
|
||||
try {
|
||||
final Location loc = references.get(i);
|
||||
if(loc == null || loc.uri == null || loc.range == null) continue;
|
||||
final File file = new File(loc.uri);
|
||||
if(!file.exists() || !file.isFile()) continue;
|
||||
EditorFragment frag = activity().getPagerAdapter().findEditorByFile(file);
|
||||
Content content;
|
||||
if(frag != null && frag.getEditor() != null)
|
||||
content = frag.getEditor().getText();
|
||||
else content = new Content(null, FileIOUtils.readFile2String(file));
|
||||
final List<SearchResult> matches = results.containsKey(file) ? results.get(file) : new ArrayList<>();
|
||||
matches.add(
|
||||
new SearchResult(
|
||||
loc.range,
|
||||
file,
|
||||
content.getLineString(loc.range.start.line),
|
||||
content.subContent(
|
||||
loc.range.start.line,
|
||||
loc.range.start.column,
|
||||
loc.range.end.line,
|
||||
loc.range.end.column
|
||||
).toString()
|
||||
)
|
||||
);
|
||||
results.put(file, matches);
|
||||
} catch (Throwable th) {
|
||||
LOG.error(ThrowableUtils.getFullStackTrace(th));
|
||||
}
|
||||
}
|
||||
// for(int i=0;i<references.size();i++) {
|
||||
// try {
|
||||
// final Location loc = references.get(i);
|
||||
// if(loc == null || loc.uri == null || loc.range == null) continue;
|
||||
// final File file = new File(loc.uri);
|
||||
// if(!file.exists() || !file.isFile()) continue;
|
||||
// EditorFragment frag = activity().getPagerAdapter().findEditorByFile(file);
|
||||
// Content content;
|
||||
// if(frag != null && frag.getEditor() != null)
|
||||
// content = frag.getEditor().getText();
|
||||
// else content = new Content(null, FileIOUtils.readFile2String(file));
|
||||
// final List<SearchResult> matches = results.containsKey(file) ? results.get(file) : new ArrayList<>();
|
||||
// matches.add(
|
||||
// new SearchResult(
|
||||
// loc.range,
|
||||
// file,
|
||||
// content.getLineString(loc.range.start.line),
|
||||
// content.subContent(
|
||||
// loc.range.start.line,
|
||||
// loc.range.start.column,
|
||||
// loc.range.end.line,
|
||||
// loc.range.end.column
|
||||
// ).toString()
|
||||
// )
|
||||
// );
|
||||
// results.put(file, matches);
|
||||
// } catch (Throwable th) {
|
||||
// LOG.error(ThrowableUtils.getFullStackTrace(th));
|
||||
// }
|
||||
// }
|
||||
|
||||
activity().handleSearchResults(results);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.itsaky.androidide.interfaces;
|
||||
|
||||
import com.itsaky.androidide.EditorActivity;
|
||||
|
||||
public interface EditorActivityProvider {
|
||||
|
||||
/**
|
||||
* Provides a reference to {@link com.itsaky.androidide.EditorActivity EditorActivity}.
|
||||
* This is used by LanguageClients to get reference to EditorActivity without keeping a strong reference.
|
||||
*/
|
||||
EditorActivity provide();
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
|
||||
import io.github.rosemoe.editor.interfaces.NewlineHandler;
|
||||
import io.github.rosemoe.editor.widget.SymbolPairMatch;
|
||||
import io.github.rosemoe.editor.langs.EmptyLanguage;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
|
||||
public class BuildOutputLanguage extends BaseLanguage {
|
||||
|
||||
@@ -19,6 +20,16 @@ public class BuildOutputLanguage extends BaseLanguage {
|
||||
return new EmptyLanguage.EmptyAutoCompleteProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LanguageServer getLanguageServer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLanguageCode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutoCompleteChar(char ch) {
|
||||
return false;
|
||||
|
||||
@@ -2,51 +2,54 @@ package com.itsaky.androidide.language.groovy;
|
||||
|
||||
import com.itsaky.androidide.models.GradleSuggestItem;
|
||||
import com.itsaky.androidide.models.SuggestItem;
|
||||
import com.itsaky.androidide.utils.Either;
|
||||
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
|
||||
import io.github.rosemoe.editor.struct.CompletionItem;
|
||||
import io.github.rosemoe.editor.text.TextAnalyzeResult;
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.eclipse.lsp4j.InsertTextFormat;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
|
||||
public class GroovyAutoComplete implements AutoCompleteProvider {
|
||||
|
||||
private static final List<String> ANDROIDX_ARTIFACTS = createArtifacts();
|
||||
private static final List<String> CONFIGURATIONS = createConfigs();
|
||||
private static final List<String> OTHERS = createOtherCompletions();
|
||||
|
||||
@Override
|
||||
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
|
||||
List<Either<SuggestItem, CompletionItem>> result = new ArrayList<>();
|
||||
|
||||
for(String artifact : ANDROIDX_ARTIFACTS) {
|
||||
if(!artifact.toLowerCase(Locale.US).startsWith(prefix.toLowerCase(Locale.US))) continue;
|
||||
SuggestItem item = createCompletionItem(artifact, prefix, true);
|
||||
Either<SuggestItem, CompletionItem> either = Either.forLeft(item);
|
||||
result.add(either);
|
||||
}
|
||||
|
||||
for(String config : CONFIGURATIONS) {
|
||||
if(!config.toLowerCase(Locale.US).startsWith(prefix.toLowerCase(Locale.US))) continue;
|
||||
SuggestItem item = createCompletionItem(config, prefix, false);
|
||||
Either<SuggestItem, CompletionItem> either = Either.forLeft(item);
|
||||
result.add(either);
|
||||
}
|
||||
|
||||
for(String other : OTHERS) {
|
||||
if(!other.toLowerCase(Locale.US).startsWith(prefix.toLowerCase(Locale.US))) continue;
|
||||
SuggestItem item = createCompletionItem(other, prefix, false);
|
||||
Either<SuggestItem, CompletionItem> either = Either.forLeft(item);
|
||||
result.add(either);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private SuggestItem createCompletionItem(String itemLabel, String prefix, boolean artifact) {
|
||||
return new GradleSuggestItem(itemLabel, "", prefix, artifact);
|
||||
@Override
|
||||
public List<CompletionItem> getAutoCompleteItems(String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) throws Exception {
|
||||
List<CompletionItem> result = new ArrayList<>();
|
||||
|
||||
for(String artifact : ANDROIDX_ARTIFACTS) {
|
||||
if(!artifact.toLowerCase(Locale.US).startsWith(prefix.toLowerCase(Locale.US))) continue;
|
||||
result.add(createCompletionItem(artifact, prefix, true));
|
||||
}
|
||||
|
||||
for(String config : CONFIGURATIONS) {
|
||||
if(!config.toLowerCase(Locale.US).startsWith(prefix.toLowerCase(Locale.US))) continue;
|
||||
result.add(createCompletionItem(config, prefix, false));
|
||||
}
|
||||
|
||||
for(String other : OTHERS) {
|
||||
if(!other.toLowerCase(Locale.US).startsWith(prefix.toLowerCase(Locale.US))) continue;
|
||||
result.add(createCompletionItem(other, prefix, false));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private CompletionItem createCompletionItem(String itemLabel, String prefix, boolean artifact) {
|
||||
CompletionItem item = new CompletionItem();
|
||||
item.setLabel(itemLabel);
|
||||
item.setDetail("");
|
||||
item.setInsertText(itemLabel);
|
||||
item.setSortText("0" + itemLabel);
|
||||
item.setInsertTextFormat(InsertTextFormat.PlainText);
|
||||
item.setKind(CompletionItemKind.Text);
|
||||
return item;
|
||||
}
|
||||
|
||||
private static List<String> createArtifacts() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.itsaky.androidide.language.groovy;
|
||||
import com.itsaky.androidide.language.BaseLanguage;
|
||||
import com.itsaky.androidide.language.groovy.lexer.GroovyLexer;
|
||||
import com.itsaky.androidide.language.java.manager.JavaCharacter;
|
||||
import com.itsaky.androidide.lsp.LSPProvider;
|
||||
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
|
||||
import io.github.rosemoe.editor.interfaces.CodeAnalyzer;
|
||||
import io.github.rosemoe.editor.interfaces.NewlineHandler;
|
||||
@@ -11,6 +12,7 @@ import io.github.rosemoe.editor.widget.SymbolPairMatch;
|
||||
import java.io.StringReader;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
|
||||
public class GroovyLanguage extends BaseLanguage {
|
||||
|
||||
@@ -21,6 +23,16 @@ public class GroovyLanguage extends BaseLanguage {
|
||||
analyzer = new GroovyAnalyzer();
|
||||
completor = new GroovyAutoComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LanguageServer getLanguageServer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLanguageCode() {
|
||||
return LSPProvider.LANGUAGE_GROOVY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeAnalyzer getAnalyzer() {
|
||||
|
||||
@@ -1,67 +1,77 @@
|
||||
package com.itsaky.androidide.language.java;
|
||||
|
||||
import androidx.core.util.Pair;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.language.java.JavaLanguageAnalyzer;
|
||||
import com.itsaky.androidide.language.java.server.JavaLanguageServer;
|
||||
import com.itsaky.androidide.models.CompletionItemWrapper;
|
||||
import com.itsaky.androidide.models.SuggestItem;
|
||||
import com.itsaky.androidide.utils.Either;
|
||||
import com.itsaky.lsp.CompletionList;
|
||||
import com.itsaky.lsp.Position;
|
||||
import com.itsaky.lsp.TextDocumentIdentifier;
|
||||
import com.itsaky.lsp.TextDocumentPositionParams;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
|
||||
import io.github.rosemoe.editor.struct.CompletionItem;
|
||||
import io.github.rosemoe.editor.text.TextAnalyzeResult;
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.CompletionParams;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
import com.itsaky.androidide.utils.JSONUtility;
|
||||
|
||||
public class JavaAutoComplete implements AutoCompleteProvider {
|
||||
|
||||
private JavaLanguageAnalyzer analyzer;
|
||||
private int lastId = -1;
|
||||
private JavaLanguage language;
|
||||
private CompletableFuture<Either<List<CompletionItem>, CompletionList>> future;
|
||||
|
||||
public JavaAutoComplete(JavaLanguageAnalyzer analyzer) {
|
||||
this.analyzer = analyzer;
|
||||
public JavaAutoComplete(JavaLanguage lang) {
|
||||
this.language = lang;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
|
||||
final JavaLanguageServer server = StudioApp.getInstance().getJavaLanguageServer();
|
||||
if(server != null) {
|
||||
if(lastId != -1) server.cancelRequest(lastId);
|
||||
TextDocumentIdentifier id = new TextDocumentIdentifier();
|
||||
id.uri = editor.getFile().toURI();
|
||||
TextDocumentPositionParams p = new TextDocumentPositionParams();
|
||||
p.position = new Position(line, editor.getCursor().getLeftColumn());
|
||||
p.textDocument = id;
|
||||
Pair<Integer, CompletionList> response = server.completion(p);
|
||||
lastId = response.first;
|
||||
CompletionList list = response.second;
|
||||
if(list != null && list.items != null) {
|
||||
List<Either<SuggestItem, CompletionItem>> result = new ArrayList<>();
|
||||
for(int i=0;i<list.items.size();i++) {
|
||||
com.itsaky.lsp.CompletionItem item = list.items.get(i);
|
||||
if(item == null) continue;
|
||||
Either<SuggestItem, CompletionItem> either = Either.forLeft(new CompletionItemWrapper(item, prefix));
|
||||
result.add(either);
|
||||
}
|
||||
Collections.sort(result, RESULT_SORTER);
|
||||
return result;
|
||||
public List<CompletionItem> getAutoCompleteItems(String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) throws Exception {
|
||||
LanguageServer languageServer = language.getLanguageServer();
|
||||
if(languageServer != null && fileUri != null) {
|
||||
|
||||
if(future != null && !future.isDone()) future.cancel(true);
|
||||
|
||||
CompletionParams params = new CompletionParams();
|
||||
params.setPosition(new Position(line, column));
|
||||
params.setTextDocument(new TextDocumentIdentifier(fileUri));
|
||||
future = languageServer.getTextDocumentService().completion(params);
|
||||
|
||||
if(future.isCancelled()) return new ArrayList<CompletionItem>();
|
||||
|
||||
try {
|
||||
Either<List<CompletionItem>, CompletionList> either = future.get();
|
||||
if(either.isLeft())
|
||||
return sort(either.getLeft());
|
||||
|
||||
if(either.isRight())
|
||||
return sort(either.getRight().getItems());
|
||||
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Java completion error: ", th);
|
||||
// Ignore this exception. An empty list will be sent after this
|
||||
// This exeption is thrown probably because the request was cancelled or interrupted
|
||||
}
|
||||
}
|
||||
return new ArrayList<Either<SuggestItem, CompletionItem>>();
|
||||
return new ArrayList<CompletionItem>();
|
||||
}
|
||||
|
||||
private static final Comparator<Either<SuggestItem, CompletionItem>> RESULT_SORTER = new Comparator<Either<SuggestItem, CompletionItem>>(){
|
||||
|
||||
private List<CompletionItem> sort(List<CompletionItem> items) {
|
||||
Collections.sort(items, RESULT_SORTER);
|
||||
return items;
|
||||
}
|
||||
|
||||
private static final Comparator<CompletionItem> RESULT_SORTER = new Comparator<CompletionItem>(){
|
||||
|
||||
@Override
|
||||
public int compare(Either<SuggestItem, CompletionItem> p1, Either<SuggestItem, CompletionItem> p2) {
|
||||
return p1.getLeft().getSortText().compareTo(p2.getLeft().getSortText());
|
||||
public int compare(CompletionItem p1, CompletionItem p2) {
|
||||
String s1 = p1.getSortText() == null ? p1.getLabel() : p1.getSortText();
|
||||
String s2 = p2.getSortText() == null ? p2.getLabel() : p2.getSortText();
|
||||
return s1.compareTo(s2);
|
||||
}
|
||||
};
|
||||
|
||||
private static final Logger LOG = Logger.instance("JavaAutoComplete");
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.itsaky.androidide.language.BaseLanguage;
|
||||
import com.itsaky.androidide.language.java.manager.JavaCharacter;
|
||||
import com.itsaky.androidide.language.java.parser.JavaLexer;
|
||||
import com.itsaky.androidide.language.java.parser.JavaParser;
|
||||
import com.itsaky.androidide.lsp.LSPProvider;
|
||||
import com.itsaky.androidide.models.AndroidProject;
|
||||
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
|
||||
import io.github.rosemoe.editor.interfaces.CodeAnalyzer;
|
||||
@@ -13,6 +14,7 @@ import io.github.rosemoe.editor.widget.SymbolPairMatch;
|
||||
import java.io.StringReader;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
|
||||
public class JavaLanguage extends BaseLanguage {
|
||||
|
||||
@@ -22,10 +24,20 @@ public class JavaLanguage extends BaseLanguage {
|
||||
|
||||
public JavaLanguage(AndroidProject project) {
|
||||
this.analyzer = new JavaLanguageAnalyzer();
|
||||
this.complete = new JavaAutoComplete(analyzer);
|
||||
this.complete = new JavaAutoComplete(this);
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LanguageServer getLanguageServer() {
|
||||
return LSPProvider.getServerForLanguage(LSPProvider.LANGUAGE_JAVA);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLanguageCode() {
|
||||
return LSPProvider.LANGUAGE_JAVA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeAnalyzer getAnalyzer() {
|
||||
return analyzer;
|
||||
@@ -105,19 +117,6 @@ public class JavaLanguage extends BaseLanguage {
|
||||
return new HandleResult(sb, shiftLeft);
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] getKeywords() {
|
||||
return new String[] {
|
||||
"abstract", "assert", "boolean", "break", "byte", "case",
|
||||
"catch", "char", "class", "const", "continue", "default",
|
||||
"do", "double", "else", "enum", "extends", "final", "finally",
|
||||
"float", "for", "if", "goto", "implements", "import", "instanceof",
|
||||
"int", "interface", "long", "native", "new", "package", "private",
|
||||
"protected", "public", "return", "short", "static", "strictfp",
|
||||
"super", "switch", "synchronized", "this", "throw", "throws",
|
||||
"transient", "try", "void", "volatile", "while", "null"
|
||||
};
|
||||
}
|
||||
|
||||
private class JavaSymbolPairs extends SymbolPairMatch {
|
||||
public JavaSymbolPairs() {
|
||||
|
||||
@@ -2,18 +2,17 @@ package com.itsaky.androidide.language.logs;
|
||||
|
||||
import com.itsaky.androidide.language.BaseLanguage;
|
||||
import com.itsaky.androidide.models.LogLine;
|
||||
import com.itsaky.androidide.models.SuggestItem;
|
||||
import com.itsaky.androidide.utils.Either;
|
||||
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
|
||||
import io.github.rosemoe.editor.interfaces.CodeAnalyzer;
|
||||
import io.github.rosemoe.editor.interfaces.NewlineHandler;
|
||||
import io.github.rosemoe.editor.struct.CompletionItem;
|
||||
import io.github.rosemoe.editor.text.TextAnalyzeResult;
|
||||
import io.github.rosemoe.editor.text.TextAnalyzer;
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
import io.github.rosemoe.editor.widget.SymbolPairMatch;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
|
||||
public class LogLanguageImpl extends BaseLanguage {
|
||||
|
||||
@@ -23,6 +22,16 @@ public class LogLanguageImpl extends BaseLanguage {
|
||||
public LogAnalyzer addLine(LogLine line) {
|
||||
return analyzer.addLine(line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LanguageServer getLanguageServer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLanguageCode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeAnalyzer getAnalyzer() {
|
||||
@@ -91,8 +100,8 @@ public class LogLanguageImpl extends BaseLanguage {
|
||||
private static class LogCompletor implements AutoCompleteProvider {
|
||||
|
||||
@Override
|
||||
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
|
||||
return new ArrayList<Either<SuggestItem, CompletionItem>>();
|
||||
public List<CompletionItem> getAutoCompleteItems(String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) {
|
||||
return new ArrayList<CompletionItem>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,16 @@ import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.language.xml.completion.XMLCompletionService;
|
||||
import com.itsaky.androidide.models.CompletionListItem;
|
||||
import com.itsaky.androidide.models.SuggestItem;
|
||||
import com.itsaky.androidide.utils.Either;
|
||||
import com.itsaky.androidide.utils.EitherOfResult;
|
||||
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
|
||||
import io.github.rosemoe.editor.struct.CompletionItem;
|
||||
import io.github.rosemoe.editor.text.TextAnalyzeResult;
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
|
||||
public class XMLAutoComplete implements AutoCompleteProvider {
|
||||
|
||||
@@ -21,26 +21,20 @@ public class XMLAutoComplete implements AutoCompleteProvider {
|
||||
|
||||
@Override
|
||||
public int compare(CompletionItem p1, CompletionItem p2) {
|
||||
return ((CompletionListItem) p1).getSortText().compareTo(((CompletionListItem) p2).getSortText());
|
||||
return p1.getSortText().compareTo(p2.getSortText());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: Change this to a language server implementation and remove the use of passing editor content
|
||||
*/
|
||||
@Override
|
||||
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
|
||||
// We do not store the completion service here in this class
|
||||
// This is because, if we modify the instance of the service in app,
|
||||
// it'll be updated here
|
||||
// automatically
|
||||
public List<CompletionItem> getAutoCompleteItems(String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) {
|
||||
final XMLCompletionService service = StudioApp.getInstance().getXmlCompletionService();
|
||||
List<CompletionItem> list = new ArrayList<CompletionItem>(sort(service.complete(editor, prefix.trim())));
|
||||
List<Either<SuggestItem, CompletionItem>> result = new ArrayList<>();
|
||||
for(CompletionItem item : list) {
|
||||
result.add(new EitherOfResult(null, item));
|
||||
}
|
||||
return result;
|
||||
return sort(service.complete(index, line, column, prefix.toLowerCase().trim()));
|
||||
}
|
||||
|
||||
private List<CompletionListItem> sort(List<CompletionListItem> result) {
|
||||
private List<CompletionItem> sort(List<CompletionItem> result) {
|
||||
Collections.sort(result, RESULTS_SORTER);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.itsaky.androidide.language.xml;
|
||||
import com.itsaky.androidide.language.BaseLanguage;
|
||||
import com.itsaky.androidide.language.java.manager.JavaCharacter;
|
||||
import com.itsaky.androidide.language.xml.lexer.XMLLexer;
|
||||
import com.itsaky.androidide.lsp.LSPProvider;
|
||||
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
|
||||
import io.github.rosemoe.editor.interfaces.CodeAnalyzer;
|
||||
import io.github.rosemoe.editor.interfaces.NewlineHandler;
|
||||
@@ -11,6 +12,7 @@ import io.github.rosemoe.editor.widget.SymbolPairMatch;
|
||||
import java.io.StringReader;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
|
||||
public class XMLLanguage extends BaseLanguage {
|
||||
|
||||
@@ -24,6 +26,16 @@ public class XMLLanguage extends BaseLanguage {
|
||||
this.newlineHandlers = new NewlineHandler[]{new IndentHandler()};
|
||||
}
|
||||
|
||||
@Override
|
||||
public LanguageServer getLanguageServer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLanguageCode() {
|
||||
return LSPProvider.LANGUAGE_XML;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeAnalyzer getAnalyzer() {
|
||||
return analyzer;
|
||||
|
||||
+40
-39
@@ -1,7 +1,6 @@
|
||||
package com.itsaky.androidide.language.xml.completion;
|
||||
|
||||
import com.itsaky.androidide.language.xml.lexer.XMLLexer;
|
||||
import com.itsaky.androidide.models.CompletionListItem;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.FileUtil;
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
@@ -17,10 +16,13 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.eclipse.lsp4j.InsertTextFormat;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
|
||||
public class XMLCompletionService {
|
||||
private HashMap<String, Attr> attrs;
|
||||
@@ -50,11 +52,8 @@ public class XMLCompletionService {
|
||||
}
|
||||
|
||||
|
||||
public List<CompletionListItem> complete(CodeEditor editor, String prefix) {
|
||||
prefix = prefix.toLowerCase(Locale.US).trim();
|
||||
final int line = editor.getCursor().getLeftLine();
|
||||
final int column = editor.getCursor().getLeftColumn();
|
||||
final List<CompletionListItem> result = new ArrayList<>();
|
||||
public List<CompletionItem> complete(int index, int line, int column, String prefix) {
|
||||
final List<CompletionItem> result = new ArrayList<>();
|
||||
if(prefix.startsWith("<") || prefix.startsWith("</")) {
|
||||
boolean slash = false;
|
||||
prefix = prefix.substring(1).trim();
|
||||
@@ -63,13 +62,17 @@ public class XMLCompletionService {
|
||||
prefix = prefix.substring(1);
|
||||
}
|
||||
for(String name : widgets) {
|
||||
if(simpleName(name).toLowerCase(Locale.US).contains(prefix))
|
||||
if(simpleName(name).toLowerCase(Locale.US).startsWith(prefix))
|
||||
result.add(widgetNameAsCompletion(name, slash));
|
||||
}
|
||||
return handleResults(result);
|
||||
} else {
|
||||
final IsInValueScanner scanner = new IsInValueScanner(editor.getCursor().getLeft());
|
||||
final String name = scanner.scan(editor.getText().toString());
|
||||
/**
|
||||
* TODO: Change to LSP
|
||||
*/
|
||||
final String content = "";
|
||||
final IsInValueScanner scanner = new IsInValueScanner(index);
|
||||
final String name = scanner.scan(content);
|
||||
if(name != null) {
|
||||
final String attrName = name.contains(":") ? name.substring(name.indexOf(":") + 1) : name;
|
||||
if (attrs.containsKey(attrName)) {
|
||||
@@ -77,14 +80,14 @@ public class XMLCompletionService {
|
||||
if(attr.hasPossibleValues()) {
|
||||
Set<String> values = attr.possibleValues;
|
||||
for(String value : values)
|
||||
if(value.toLowerCase(Locale.US).contains(prefix))
|
||||
if(value.toLowerCase(Locale.US).startsWith(prefix))
|
||||
result.add(valueAsCompletion(value));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(Map.Entry<String, Attr> entry : attrs.entrySet()) {
|
||||
Attr attr = entry.getValue();
|
||||
if(attr.name.toLowerCase(Locale.US).contains(prefix))
|
||||
if(attr.name.toLowerCase(Locale.US).startsWith(prefix))
|
||||
result.add(attrAsCompletion(attr));
|
||||
}
|
||||
}
|
||||
@@ -92,39 +95,37 @@ public class XMLCompletionService {
|
||||
return handleResults(result);
|
||||
}
|
||||
|
||||
private CompletionListItem valueAsCompletion(String value) {
|
||||
CompletionListItem item = new CompletionListItem();
|
||||
item.setLabel(value)
|
||||
.setCommit(value)
|
||||
.setDesc("Attribute Value")
|
||||
.setDetail(item.getDesc())
|
||||
.setItemType("int")
|
||||
.setType(CompletionListItem.Type.ATTRIBUTE)
|
||||
.cursorOffset(item.getCommit().length());
|
||||
private CompletionItem valueAsCompletion(String value) {
|
||||
CompletionItem item = new CompletionItem();
|
||||
item.setLabel(value);
|
||||
item.setDetail("Attribute value");
|
||||
item.setInsertText(value);
|
||||
item.setInsertTextFormat(InsertTextFormat.PlainText);
|
||||
item.setSortText("0" + value);
|
||||
item.setKind(CompletionItemKind.Value);
|
||||
return item;
|
||||
}
|
||||
|
||||
private CompletionListItem attrAsCompletion(Attr attr) {
|
||||
CompletionListItem item = new CompletionListItem();
|
||||
item.setLabel(attr.name)
|
||||
.setCommit(attr.prefix + ":" + attr.name + "=\"" + (attr.prefix.equals("android") && attr.name.equals("id") ? "@+id/\"" : "\""))
|
||||
.setDesc("Attribute")
|
||||
.setDetail(item.getDesc())
|
||||
.setItemType("int")
|
||||
.setType(CompletionListItem.Type.ATTRIBUTE)
|
||||
.cursorOffset(item.getCommit().length() - 1);
|
||||
private CompletionItem attrAsCompletion(Attr attr) {
|
||||
CompletionItem item = new CompletionItem();
|
||||
item.setLabel(attr.name);
|
||||
item.setDetail("Attribute");
|
||||
item.setInsertText(attr.prefix + ":" + attr.name + "=\"" + (attr.prefix.equals("android") && attr.name.equals("id") ? "@+id/\"" : "\""));
|
||||
item.setInsertTextFormat(InsertTextFormat.PlainText);
|
||||
item.setSortText("1" + attr.name);
|
||||
item.setKind(CompletionItemKind.Snippet);
|
||||
return item;
|
||||
}
|
||||
|
||||
private CompletionListItem widgetNameAsCompletion(String name, boolean slash) {
|
||||
CompletionListItem item = new CompletionListItem();
|
||||
final String n = simpleName(name);
|
||||
item.setLabel(n)
|
||||
.setCommit("<" + (slash ? "/" : "") + n)
|
||||
.setDesc(name)
|
||||
.setDetail(item.getDesc())
|
||||
.setItemType(" ")
|
||||
.setType(CompletionListItem.Type.WIDGET);
|
||||
private CompletionItem widgetNameAsCompletion(String name, boolean slash) {
|
||||
final String n = simpleName(name);
|
||||
CompletionItem item = new CompletionItem();
|
||||
item.setLabel(n);
|
||||
item.setDetail(name);
|
||||
item.setInsertText("<" + (slash ? "/" : "") + n);
|
||||
item.setInsertTextFormat(InsertTextFormat.PlainText);
|
||||
item.setSortText("2" + n);
|
||||
item.setKind(CompletionItemKind.Value);
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -132,7 +133,7 @@ public class XMLCompletionService {
|
||||
return name.substring(name.lastIndexOf(".") + 1);
|
||||
}
|
||||
|
||||
private List<CompletionListItem> handleResults(List<CompletionListItem> result) {
|
||||
private List<CompletionItem> handleResults(List<CompletionItem> result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.itsaky.androidide.lsp;
|
||||
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.google.gson.Gson;
|
||||
import com.itsaky.androidide.EditorActivity;
|
||||
import com.itsaky.androidide.fragments.EditorFragment;
|
||||
import com.itsaky.androidide.interfaces.EditorActivityProvider;
|
||||
import com.itsaky.androidide.utils.LSPUtils;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.ShowDocumentParams;
|
||||
import org.eclipse.lsp4j.ShowDocumentResult;
|
||||
import org.eclipse.lsp4j.services.LanguageClient;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
|
||||
/**
|
||||
* AndroidIDE specific implementation of the LanguageClient
|
||||
*/
|
||||
public abstract class IDELanguageClient implements LanguageClient {
|
||||
|
||||
protected static final Gson gson = new Gson();
|
||||
protected static final Logger LOG = Logger.instance("IDELanguageClient");
|
||||
|
||||
protected EditorActivityProvider activityProvider;
|
||||
|
||||
private final StarterListener starterListener;
|
||||
private final OnConnectedListener onConnectedListener;
|
||||
|
||||
private boolean isConnected;
|
||||
|
||||
public IDELanguageClient(StarterListener starterListener, OnConnectedListener onConnectedListener) {
|
||||
this.starterListener = starterListener;
|
||||
this.onConnectedListener = onConnectedListener;
|
||||
}
|
||||
|
||||
public void setActivityProvider(EditorActivityProvider provider) {
|
||||
this.activityProvider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we connected to the server?
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return isConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the LanguageServer is connected successfully
|
||||
*/
|
||||
protected void onServerConnected(LanguageServer server) {
|
||||
this.isConnected = true;
|
||||
if (onConnectedListener != null)
|
||||
onConnectedListener.onConnected(server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the connection to the LanguageServer was cancelled.
|
||||
*/
|
||||
protected void onServerDisconnected() {
|
||||
this.isConnected = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the ServerSocket is started. At this point, the {@link LSPClient} waits for the server to connect. <br>
|
||||
* This is (probably) the proper time to start your language server as ServerSocket is waiting for a client to connect.
|
||||
*/
|
||||
protected void startServer() {
|
||||
if (this.starterListener != null) {
|
||||
starterListener.startServer();
|
||||
}
|
||||
}
|
||||
|
||||
protected EditorActivity activity() {
|
||||
if (activityProvider == null) return null;
|
||||
return activityProvider.provide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link io.github.rosemoe.editor.widget.CodeEditor CodeEditor} to show locations in EditorActivity
|
||||
*/
|
||||
public void showLocations(List<? extends Location> locations) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link io.github.rosemoe.editor.widget.CodeEditor CodeEditor} to show locations in EditorActivity
|
||||
*/
|
||||
public void showLocationLinks(List<? extends LocationLink> locations) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Usually called by {@link io.github.rosemoe.editor.widget.CodeEditor CodeEditor} to show a specific document in EditorActivity and select the specified range
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<ShowDocumentResult> showDocument(ShowDocumentParams params) {
|
||||
ShowDocumentResult result = new ShowDocumentResult();
|
||||
boolean success = false;
|
||||
|
||||
if(params != null && params.getUri() != null && params.getSelection() != null) {
|
||||
File file = new File(URI.create(params.getUri()));
|
||||
if(file.exists() && file.isFile() && FileUtils.isUtf8(file)) {
|
||||
final Range range = params.getSelection();
|
||||
EditorFragment frag = activity().getPagerAdapter().getFrag(activity().getBinding().tabs.getSelectedTabPosition());
|
||||
if(frag != null
|
||||
&& frag.getFile() != null
|
||||
&& frag.getEditor() != null
|
||||
&& frag.getFile().getAbsolutePath().equals(file.getAbsolutePath())) {
|
||||
if(LSPUtils.isEqual(range.getStart(), range.getEnd())) {
|
||||
frag.getEditor().setSelection(range.getStart().getLine(), range.getStart().getCharacter());
|
||||
} else {
|
||||
frag.getEditor().setSelectionRegion(range.getStart().getLine(), range.getStart().getCharacter(), range.getEnd().getLine(), range.getEnd().getCharacter());
|
||||
}
|
||||
} else {
|
||||
activity().openFileAndSelect(file, range);
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
result.setSuccess(success);
|
||||
return CompletableFuture.completedFuture(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void telemetryEvent(Object p1) {
|
||||
LOG.info("telemetryEvent: ", gson.toJson(p1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports connection progress
|
||||
*/
|
||||
protected abstract void connectionReport(String message);
|
||||
|
||||
/**
|
||||
* Called when there was an error connecting to server.
|
||||
*/
|
||||
protected abstract void connectionError(Throwable th);
|
||||
|
||||
public static interface StarterListener {
|
||||
void startServer();
|
||||
}
|
||||
|
||||
public static interface OnConnectedListener {
|
||||
void onConnected(LanguageServer server);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package com.itsaky.androidide.lsp;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.lsp.client.java.JavaLanguageClient;
|
||||
import com.itsaky.androidide.shell.ShellServer;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.ClientCapabilities;
|
||||
import org.eclipse.lsp4j.ClientInfo;
|
||||
import org.eclipse.lsp4j.GeneralClientCapabilities;
|
||||
import org.eclipse.lsp4j.InitializeParams;
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
import org.eclipse.lsp4j.InitializedParams;
|
||||
import org.eclipse.lsp4j.TextDocumentClientCapabilities;
|
||||
import org.eclipse.lsp4j.WindowClientCapabilities;
|
||||
import org.eclipse.lsp4j.WorkspaceClientCapabilities;
|
||||
import org.eclipse.lsp4j.WorkspaceFolder;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.lsp4j.CallHierarchyCapabilities;
|
||||
import org.eclipse.lsp4j.CodeActionCapabilities;
|
||||
import org.eclipse.lsp4j.DidChangeConfigurationCapabilities;
|
||||
import org.eclipse.lsp4j.DidChangeWatchedFilesCapabilities;
|
||||
import org.eclipse.lsp4j.ExecuteCommandCapabilities;
|
||||
import org.eclipse.lsp4j.FileOperationsWorkspaceCapabilities;
|
||||
import org.eclipse.lsp4j.SemanticTokensCapabilities;
|
||||
import org.eclipse.lsp4j.SynchronizationCapabilities;
|
||||
import org.eclipse.lsp4j.ColorProviderCapabilities;
|
||||
import org.eclipse.lsp4j.CompletionCapabilities;
|
||||
import org.eclipse.lsp4j.CompletionItemCapabilities;
|
||||
import com.itsaky.androidide.interfaces.EditorActivityProvider;
|
||||
|
||||
/**
|
||||
* A manager class to manage Language Server Protocol implementations
|
||||
* An initiaize request must be sent to the server only through specific nested classes.
|
||||
* This will make sure that we map the capabilites of each language server we support.
|
||||
*/
|
||||
public class LSP {
|
||||
|
||||
private static EditorActivityProvider PROVIDER;
|
||||
private static final Logger LOG = Logger.instance("LSP");
|
||||
|
||||
/**
|
||||
* Stores information about ports at which the specified language server must be started
|
||||
*/
|
||||
public static class Ports {
|
||||
public static final int JAVA = 4116;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages Java Language Server
|
||||
*/
|
||||
public static class Java {
|
||||
private static ShellServer mShell;
|
||||
private static JavaLanguageClient javaClient;
|
||||
private static LSPClientLauncher mLauncher;
|
||||
|
||||
/**
|
||||
* Starts the Java Language server and the client
|
||||
*/
|
||||
public static void start(Runnable onStarted) {
|
||||
if (mShell != null && mLauncher != null && mLauncher.isConnected()) {
|
||||
return;
|
||||
}
|
||||
javaClient = new JavaLanguageClient(() -> LSP.Java.startJLS(false), server -> storeServerInfoAndNotify(server, onStarted));
|
||||
javaClient.setActivityProvider(LSP.PROVIDER);
|
||||
mLauncher = new LSPClientLauncher(javaClient, Ports.JAVA);
|
||||
mLauncher.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends initialize request to the Java Language Server
|
||||
*/
|
||||
public static Optional<InitializeResult> init(String rootPath) {
|
||||
|
||||
final LanguageServer server = LSPProvider.getServerForLanguage(LSPProvider.LANGUAGE_JAVA);
|
||||
if (server == null)
|
||||
return Optional.empty();
|
||||
|
||||
final List<WorkspaceFolder> folders = new ArrayList<>();
|
||||
final File file = new File(rootPath);
|
||||
final WorkspaceFolder folder = new WorkspaceFolder();
|
||||
|
||||
folder.setUri(file.toURI().toString());
|
||||
folder.setName(file.getName().toString());
|
||||
folders.add(folder);
|
||||
|
||||
InitializeParams params = new InitializeParams();
|
||||
params.setClientInfo(LSP.getClientInfo());
|
||||
params.setWorkspaceFolders(folders);
|
||||
params.setCapabilities(LSP.getCapabilities());
|
||||
|
||||
CompletableFuture<InitializeResult> initResult = server.initialize(params);
|
||||
try {
|
||||
InitializeResult result = initResult.get();
|
||||
if(result.getCapabilities() != null) {
|
||||
LSPProvider.setServerCapabilitesForLanguage(LSPProvider.LANGUAGE_JAVA, result.getCapabilities());
|
||||
}
|
||||
return Optional.of(result);
|
||||
} catch (Throwable e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a shutdown request to the java language server
|
||||
*/
|
||||
|
||||
public static void shutdown() {
|
||||
if(mLauncher != null) {
|
||||
mLauncher.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start JDT LS from the command line
|
||||
*/
|
||||
private static void startJLS(boolean quiet) {
|
||||
mShell = StudioApp.getInstance().newShell(t -> LOG.verbose(t));
|
||||
mShell.append(String.format("java -Djls.client.port=%d -jar %s%s", Ports.JAVA, Environment.JLS_JAR.getAbsolutePath(), quiet ? /* IMPORTANT: Do not forget the leading space */ " --quiet" : ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the instance of Java Language Server in LSPProvider
|
||||
*/
|
||||
private static void storeServerInfoAndNotify(LanguageServer server, Runnable startedListener) {
|
||||
LSPProvider.setLanguageServer(LSPProvider.LANGUAGE_JAVA, server);
|
||||
LSPProvider.setClientForLanguage(LSPProvider.LANGUAGE_JAVA, javaClient);
|
||||
|
||||
if(startedListener != null)
|
||||
startedListener.run();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setActivityProvider(EditorActivityProvider provider) {
|
||||
PROVIDER = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down all known language server
|
||||
*/
|
||||
public static void shutdownAll() {
|
||||
// Shutdown language servers one by one
|
||||
LSP.Java.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Global LSP client information that should be passed to LanguageServer with InitializeParams
|
||||
*/
|
||||
public static ClientInfo getClientInfo() {
|
||||
ClientInfo info = new ClientInfo();
|
||||
info.setName("AndroidIDE");
|
||||
info.setVersion(com.itsaky.androidide.BuildConfig.VERSION_NAME);
|
||||
return info;
|
||||
}
|
||||
|
||||
public static ClientCapabilities getCapabilities() {
|
||||
ClientCapabilities capabilities = new ClientCapabilities();
|
||||
capabilities.setGeneral(LSP.getGeneralCapabilities());
|
||||
capabilities.setTextDocument(LSP.getTextDocumentCapabilities());
|
||||
capabilities.setWindow(LSP.getWindowCapabilities());
|
||||
capabilities.setWorkspace(LSP.getWorkspaceCapabilities());
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
private static GeneralClientCapabilities getGeneralCapabilities() {
|
||||
GeneralClientCapabilities c = new GeneralClientCapabilities();
|
||||
return c;
|
||||
}
|
||||
|
||||
private static TextDocumentClientCapabilities getTextDocumentCapabilities() {
|
||||
TextDocumentClientCapabilities c = new TextDocumentClientCapabilities();
|
||||
c.setSynchronization(synchronizationCapabilities());
|
||||
c.setColorProvider(new ColorProviderCapabilities());
|
||||
c.setCompletion(completionCapabilities());
|
||||
return c;
|
||||
}
|
||||
|
||||
private static CompletionCapabilities completionCapabilities() {
|
||||
CompletionCapabilities c = new CompletionCapabilities();
|
||||
return c;
|
||||
}
|
||||
|
||||
private static SynchronizationCapabilities synchronizationCapabilities() {
|
||||
SynchronizationCapabilities c = new SynchronizationCapabilities();
|
||||
c.setDidSave(true);
|
||||
c.setWillSave(false);
|
||||
c.setWillSaveWaitUntil(false);
|
||||
return c;
|
||||
}
|
||||
|
||||
private static WindowClientCapabilities getWindowCapabilities() {
|
||||
WindowClientCapabilities c = new WindowClientCapabilities();
|
||||
return c;
|
||||
}
|
||||
|
||||
private static WorkspaceClientCapabilities getWorkspaceCapabilities() {
|
||||
WorkspaceClientCapabilities c = new WorkspaceClientCapabilities();
|
||||
c.setApplyEdit(true);
|
||||
c.setConfiguration(true);
|
||||
c.setDidChangeConfiguration(new DidChangeConfigurationCapabilities(false));
|
||||
c.setDidChangeWatchedFiles(new DidChangeWatchedFilesCapabilities(false));
|
||||
c.setExecuteCommand(new ExecuteCommandCapabilities(true));
|
||||
c.setFileOperations(fileOperationsWorkspaceCapabilities());
|
||||
return c;
|
||||
}
|
||||
|
||||
private static FileOperationsWorkspaceCapabilities fileOperationsWorkspaceCapabilities() {
|
||||
FileOperationsWorkspaceCapabilities c = new FileOperationsWorkspaceCapabilities();
|
||||
c.setDidCreate(true);
|
||||
c.setDidDelete(true);
|
||||
c.setDidRename(true);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package com.itsaky.androidide.lsp;
|
||||
|
||||
import com.blankj.utilcode.util.CloseUtils;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.AsynchronousServerSocketChannel;
|
||||
import java.nio.channels.AsynchronousSocketChannel;
|
||||
import java.nio.channels.Channels;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Future;
|
||||
import org.eclipse.lsp4j.jsonrpc.Launcher;
|
||||
import org.eclipse.lsp4j.launch.LSPLauncher;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
|
||||
/**
|
||||
* Launches a client for a Language server and handles connection.
|
||||
* A callback is sent to the IDELanguageClient to tell the client to start the server if necessary.
|
||||
*/
|
||||
public class LSPClientLauncher extends Thread {
|
||||
|
||||
private final int PORT;
|
||||
private Future<Void> listeningFuture;
|
||||
private final IDELanguageClient languageClient;
|
||||
|
||||
private AsynchronousServerSocketChannel serverSocket;
|
||||
private LanguageServer server;
|
||||
|
||||
public LSPClientLauncher(IDELanguageClient client, int port) {
|
||||
this.languageClient = client;
|
||||
this.PORT = port;
|
||||
|
||||
setDaemon(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connected LanguageServer, or {@code null}
|
||||
*
|
||||
* @return The LanguageServer or {@code null}
|
||||
*/
|
||||
public LanguageServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we connected to the server?
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return languageClient != null && languageClient.isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
languageClient.connectionReport("Starting server socket");
|
||||
serverSocket = AsynchronousServerSocketChannel.open();
|
||||
serverSocket.bind(new InetSocketAddress(PORT));
|
||||
languageClient.connectionReport("Waiting for server to connect...");
|
||||
languageClient.startServer();
|
||||
|
||||
final AsynchronousSocketChannel server = serverSocket.accept().get();
|
||||
final OutputStream outWriter = new OutputStreamWrapper(Channels.newOutputStream(server));
|
||||
final InputStream inReader = Channels.newInputStream(server);
|
||||
languageClient.connectionReport("Server connected. Launching client...");
|
||||
|
||||
Launcher<LanguageServer> launcher = LSPLauncher.createClientLauncher(languageClient, inReader, outWriter);
|
||||
listeningFuture = launcher.startListening();
|
||||
|
||||
this.server = launcher.getRemoteProxy();
|
||||
languageClient.onServerConnected(this.server);
|
||||
languageClient.connectionReport("Server is now listening.");
|
||||
|
||||
while (!(listeningFuture.isCancelled() || listeningFuture.isDone())) {
|
||||
// Take some rest
|
||||
sleep(50);
|
||||
}
|
||||
|
||||
// Make sure we close the server. Sockets are limited resources...
|
||||
CloseUtils.closeIOQuietly(inReader, outWriter, server);
|
||||
languageClient.connectionReport("Server disconnected.");
|
||||
languageClient.onServerDisconnected();
|
||||
} catch (Throwable th) {
|
||||
languageClient.connectionError(th);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shut down the language server
|
||||
*/
|
||||
public void shutdown() {
|
||||
try {
|
||||
if (this.server != null) {
|
||||
server.shutdown();
|
||||
server.exit();
|
||||
}
|
||||
serverSocket.close();
|
||||
} catch (Throwable th) {
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an output stream to make sure that we don't write on UI Thread
|
||||
* Writing to a socket's output stream is considered as network operation
|
||||
* Trying to write to a socket on UI Thread will result in NetworkOnMainThreadExeption
|
||||
*/
|
||||
|
||||
private class OutputStreamWrapper extends OutputStream {
|
||||
|
||||
private final AsyncWriter writer;
|
||||
private final Thread writerThread;
|
||||
|
||||
public OutputStreamWrapper(OutputStream actualStream) {
|
||||
this.writer = new AsyncWriter(actualStream);
|
||||
this.writerThread = new Thread(writer);
|
||||
|
||||
this.writerThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int p1) throws IOException {
|
||||
// Not needed
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException {
|
||||
// Do not call super.write(b);
|
||||
// If done, it will further call write(int)
|
||||
this.writer.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
this.writer.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actual implementation of the writer thread
|
||||
*/
|
||||
private class AsyncWriter implements Runnable, Closeable {
|
||||
|
||||
private final OutputStream out;
|
||||
private boolean closed;
|
||||
|
||||
private final BlockingQueue<Writable> writeQueue = new ArrayBlockingQueue<>(10);
|
||||
|
||||
public AsyncWriter(OutputStream out) {
|
||||
this.out = out;
|
||||
this.closed = false;
|
||||
}
|
||||
|
||||
public void write(byte[] b) {
|
||||
this.writeQueue.add(new Writable(b));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!closed) {
|
||||
try {
|
||||
Writable data = writeQueue.take();
|
||||
this.out.write(data.data);
|
||||
this.out.flush();
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Error writing to LanguageServer");
|
||||
}
|
||||
}
|
||||
|
||||
LOG.info("Writer thread stopped");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.closed = true;
|
||||
this.out.close();
|
||||
}
|
||||
|
||||
class Writable {
|
||||
byte[] data;
|
||||
|
||||
public Writable(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger LOG = Logger.instance("LSPClientLauncher");
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.itsaky.androidide.lsp;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
import org.eclipse.lsp4j.ServerCapabilities;
|
||||
|
||||
/**
|
||||
* A provider that holds references to active {@link LanguageServer}s, its capabilites and its client implementation. <br>
|
||||
* To get language server for a specific language, just call {@link #getServerForLanguage(String)}
|
||||
*/
|
||||
public class LSPProvider {
|
||||
|
||||
/**
|
||||
* Language code for Java language
|
||||
*/
|
||||
public static final String LANGUAGE_JAVA = "java";
|
||||
|
||||
/**
|
||||
* Language code for Kotlin language
|
||||
*/
|
||||
public static final String LANGUAGE_KOTLIN = "kotlin";
|
||||
|
||||
/**
|
||||
* Language code for XML language
|
||||
*/
|
||||
public static final String LANGUAGE_XML = "xml";
|
||||
|
||||
/**
|
||||
* Language code for Groovy language
|
||||
*/
|
||||
public static final String LANGUAGE_GROOVY = "groovy";
|
||||
|
||||
/**
|
||||
* Map a reference to active language servers with its language id as a key
|
||||
*/
|
||||
private static final Map<String, LanguageServer> serverMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Maps a reference to the capabilities of a language server with its language id as a key
|
||||
*/
|
||||
private static final Map<String, ServerCapabilities> capabilitiesMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Maps a reference to the client of a language server with its language id as a key
|
||||
*/
|
||||
private static final Map<String, IDELanguageClient> clientMap = new HashMap<>();
|
||||
|
||||
static void setLanguageServer(String languageCode, LanguageServer server) {
|
||||
serverMap.put(languageCode, server);
|
||||
}
|
||||
|
||||
static void setServerCapabilitesForLanguage(String languageCode, ServerCapabilities capabilities) {
|
||||
capabilitiesMap.put(languageCode, capabilities);
|
||||
}
|
||||
|
||||
static void setClientForLanguage(String languageCode, IDELanguageClient client) {
|
||||
clientMap.put(languageCode, client);
|
||||
}
|
||||
|
||||
public static Map<String, LanguageServer> getAvailableServers() {
|
||||
return serverMap;
|
||||
}
|
||||
|
||||
public static Map<String, ServerCapabilities> getAllServerCapabilities() {
|
||||
return capabilitiesMap;
|
||||
}
|
||||
|
||||
public static Map<String, IDELanguageClient> getAllServerClients() {
|
||||
return clientMap;
|
||||
}
|
||||
|
||||
public static LanguageServer getServerForLanguage(String languageCode) {
|
||||
return languageCode == null ? null : serverMap.get(languageCode);
|
||||
}
|
||||
|
||||
public static ServerCapabilities getServerCapabilitiesForLanguage(String language) {
|
||||
return language == null ? null : capabilitiesMap.get(language);
|
||||
}
|
||||
|
||||
public static IDELanguageClient getClientForLanguage(String language) {
|
||||
return language == null ? null : clientMap.get(language);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.itsaky.androidide.lsp.client.java;
|
||||
|
||||
import com.itsaky.androidide.lsp.IDELanguageClient;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
|
||||
import org.eclipse.lsp4j.ApplyWorkspaceEditResponse;
|
||||
import org.eclipse.lsp4j.ConfigurationParams;
|
||||
import org.eclipse.lsp4j.LogTraceParams;
|
||||
import org.eclipse.lsp4j.MessageActionItem;
|
||||
import org.eclipse.lsp4j.MessageParams;
|
||||
import org.eclipse.lsp4j.ProgressParams;
|
||||
import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
||||
import org.eclipse.lsp4j.RegistrationParams;
|
||||
import org.eclipse.lsp4j.SetTraceParams;
|
||||
import org.eclipse.lsp4j.ShowDocumentParams;
|
||||
import org.eclipse.lsp4j.ShowDocumentResult;
|
||||
import org.eclipse.lsp4j.ShowMessageRequestParams;
|
||||
import org.eclipse.lsp4j.UnregistrationParams;
|
||||
import org.eclipse.lsp4j.WorkDoneProgressCreateParams;
|
||||
import org.eclipse.lsp4j.WorkspaceFolder;
|
||||
|
||||
public class JavaLanguageClient extends IDELanguageClient {
|
||||
|
||||
public JavaLanguageClient(StarterListener starterListener, OnConnectedListener onConnectedListener) {
|
||||
super(starterListener, onConnectedListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void connectionReport(String message) {
|
||||
LOG.info("Connection report: ", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void connectionError(Throwable th) {
|
||||
LOG.info("Connection error: ", th);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams p1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<Object>> configuration(ConfigurationParams p1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> createProgress(WorkDoneProgressCreateParams p1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logMessage(MessageParams p1) {
|
||||
LOG.info("logMessage: ", gson.toJson(p1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logTrace(LogTraceParams p1) {
|
||||
LOG.info("logTrace: ", gson.toJson(p1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyProgress(ProgressParams p1) {
|
||||
LOG.info("notifyProgress: ", gson.toJson(p1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishDiagnostics(PublishDiagnosticsParams p1) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> refreshCodeLenses() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> refreshSemanticTokens() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> registerCapability(RegistrationParams p1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrace(SetTraceParams p1) {
|
||||
LOG.info("setTrace: ", gson.toJson(p1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ShowDocumentResult> showDocument(ShowDocumentParams p1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMessage(MessageParams p1) {
|
||||
LOG.info("showMessage: ", gson.toJson(p1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams p1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> unregisterCapability(UnregistrationParams p1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<WorkspaceFolder>> workspaceFolders() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public class ToolsManager {
|
||||
|
||||
private static PreferenceManager prefs;
|
||||
|
||||
public static final int JLS_VERSION = 8;
|
||||
public static final int JLS_VERSION = 12;
|
||||
public static final int LOGSENDER_VERSION = 1;
|
||||
public static final int CLEANER_VERSION = 1;
|
||||
public static final int GRADLE_API_VERSION = 2;
|
||||
@@ -62,18 +62,9 @@ public class ToolsManager {
|
||||
|
||||
|
||||
private static void extractJlsIfNeeded() {
|
||||
boolean gson = false, jls = false, proto = false;
|
||||
final List<File> files = FileUtils.listFilesInDir(Environment.JLS_HOME, true);
|
||||
for(File f : files) {
|
||||
gson = gson == true ? gson : f.getName().equals("gson.jar");
|
||||
jls = jls == true ? jls : f.getName().equals("jls.jar");
|
||||
proto = proto == true ? proto : f.getName().equals("protobuf.jar");
|
||||
}
|
||||
|
||||
final boolean exists = gson && jls && proto;
|
||||
final boolean isOld = JLS_VERSION > prefs.getInt(KEY_JLS_VERSION, 0);
|
||||
|
||||
if(!exists || isOld) {
|
||||
if(!Environment.JLS_JAR.exists() || isOld) {
|
||||
try {
|
||||
extractJls();
|
||||
} catch (Throwable e) {}
|
||||
@@ -81,14 +72,7 @@ public class ToolsManager {
|
||||
}
|
||||
|
||||
private static void extractJls(){
|
||||
final File jlsZip = new File(Environment.JLS_HOME, "jls.zip");
|
||||
ResourceUtils.copyFileFromAssets("data/jls.zip", jlsZip.getAbsolutePath());
|
||||
try {
|
||||
ZipFile file = new ZipFile(jlsZip, ConstantsBridge.JLS_ZIP_PASSWORD_HASH.toCharArray());
|
||||
file.extractAll(Environment.JLS_HOME.getAbsolutePath());
|
||||
FileUtils.delete(jlsZip);
|
||||
prefs.putInt(KEY_JLS_VERSION, JLS_VERSION);
|
||||
} catch (Throwable th) {}
|
||||
ResourceUtils.copyFileFromAssets("data/jls.jar", Environment.JLS_JAR.getAbsolutePath());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.itsaky.androidide.models;
|
||||
|
||||
import com.itsaky.lsp.Range;
|
||||
import java.io.File;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
|
||||
public class SearchResult extends Range {
|
||||
public File file;
|
||||
@@ -9,8 +9,8 @@ public class SearchResult extends Range {
|
||||
public String match;
|
||||
|
||||
public SearchResult(Range src, File file, String line, String match) {
|
||||
this.start = src.start;
|
||||
this.end = src.end;
|
||||
this.setStart(src.getStart());
|
||||
this.setEnd(src.getEnd());
|
||||
this.file = file;
|
||||
this.line = line;
|
||||
this.match = match;
|
||||
|
||||
@@ -35,6 +35,7 @@ public final class Environment {
|
||||
|
||||
public static final File INIT_SCRIPT;
|
||||
public static final File PROJECT_DATA_FILE;
|
||||
public static final File JLS_JAR;
|
||||
|
||||
public static final File GRADLE_HOME;
|
||||
public static final File GRADLE_PROPS;
|
||||
@@ -71,7 +72,8 @@ public final class Environment {
|
||||
IDEPROPS = new File(SYSROOT, "etc/ide-environment.properties");
|
||||
LIBHOOK = new File(LIBDIR, "libhook.so");
|
||||
PROJECT_DATA_FILE = new File(TMP_DIR, "ide_project");
|
||||
|
||||
JLS_JAR = new File(JLS_HOME, "jls.jar");
|
||||
|
||||
INIT_SCRIPT = new File(mkdirIfNotExits(new File(app.getIDEDataDir(), "init")), "androidide.init.gradle");
|
||||
BOOTCLASSPATH = new File("");
|
||||
GRADLE_USER_HOME = new File(HOME, ".gradle");
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.itsaky.androidide.utils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
public class JSONUtility {
|
||||
public static final Gson gson = new Gson();
|
||||
public static final Gson prettyPrinter = new GsonBuilder().setPrettyPrinting().create();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.itsaky.androidide.utils;
|
||||
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
|
||||
public class LSPUtils {
|
||||
|
||||
public static final Position Position_ofZero;
|
||||
public static final Range Range_ofZero;
|
||||
|
||||
static {
|
||||
Position_ofZero = new Position(0, 0);
|
||||
Range_ofZero = new Range(Position_ofZero, Position_ofZero);
|
||||
}
|
||||
|
||||
public static boolean isEqual(Position p1, Position p2) {
|
||||
if(p1 == null) return false;
|
||||
if(p2 == null) return false;
|
||||
|
||||
return p1.getLine() == p2.getLine()
|
||||
&& p1.getCharacter() == p2.getCharacter();
|
||||
}
|
||||
|
||||
public static boolean isEqual(Range r1, Range r2) {
|
||||
if(r1 == null) return false;
|
||||
if(r2 == null) return false;
|
||||
|
||||
return LSPUtils.isEqual(r1.getStart(), r2.getStart())
|
||||
&& LSPUtils.isEqual(r1.getEnd(), r2.getEnd());
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,19 @@ import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.itsaky.androidide.models.SearchResult;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.lsp.Position;
|
||||
import com.itsaky.lsp.Range;
|
||||
import io.github.rosemoe.editor.text.CharPosition;
|
||||
import io.github.rosemoe.editor.text.Content;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.HashMap;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
|
||||
/**
|
||||
* This class provides API to search in files recursively
|
||||
@@ -84,8 +84,8 @@ public class RecursiveFileSearcher {
|
||||
final Range range = new Range();
|
||||
final CharPosition start = content.getIndexer().getCharPosition(matcher.start());
|
||||
final CharPosition end = content.getIndexer().getCharPosition(matcher.end());
|
||||
range.start = new Position(start.line, start.column);
|
||||
range.end = new Position(end.line, end.column);
|
||||
range.setStart(new Position(start.line, start.column));
|
||||
range.setEnd(new Position(end.line, end.column));
|
||||
String sub = "...".concat(text.substring(Math.max(0, matcher.start() - 30), Math.min(matcher.end() + 31, text.length()))).trim().concat("...");
|
||||
String match = content.subContent(start.line, start.column, end.line, end.column).toString();
|
||||
ranges.add(new SearchResult(range, file, sub.replaceAll("\\s+", " "), match));
|
||||
|
||||
@@ -15,12 +15,11 @@
|
||||
*/
|
||||
package io.github.rosemoe.editor.interfaces;
|
||||
|
||||
import com.itsaky.androidide.models.SuggestItem;
|
||||
import com.itsaky.androidide.utils.Either;
|
||||
import io.github.rosemoe.editor.struct.CompletionItem;
|
||||
import io.github.rosemoe.editor.text.TextAnalyzeResult;
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
import java.util.List;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
|
||||
/**
|
||||
* Interface for auto completion analysis
|
||||
@@ -28,7 +27,7 @@ import java.util.List;
|
||||
* @author Rose
|
||||
*/
|
||||
public interface AutoCompleteProvider {
|
||||
|
||||
|
||||
/**
|
||||
* Analyze auto complete items
|
||||
*
|
||||
@@ -38,7 +37,7 @@ public interface AutoCompleteProvider {
|
||||
* @param line The line of cursor
|
||||
* @return Analyzed items
|
||||
*/
|
||||
List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line);
|
||||
List<CompletionItem> getAutoCompleteItems(String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package io.github.rosemoe.editor.interfaces;
|
||||
|
||||
import io.github.rosemoe.editor.widget.SymbolPairMatch;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
|
||||
/**
|
||||
* Language for editor
|
||||
@@ -32,7 +33,19 @@ import io.github.rosemoe.editor.widget.SymbolPairMatch;
|
||||
* @author Rose
|
||||
*/
|
||||
public interface EditorLanguage {
|
||||
|
||||
|
||||
/**
|
||||
* Get the language server for this available
|
||||
* May return null.
|
||||
*/
|
||||
LanguageServer getLanguageServer();
|
||||
|
||||
/**
|
||||
* Get the language code of this language.
|
||||
* This must be the one specified in {@link com.itsaky.androidide.lsp.LSPProvider LSPProvider}
|
||||
*/
|
||||
String getLanguageCode();
|
||||
|
||||
/**
|
||||
* Get CodeAnalyzer of this language object
|
||||
*
|
||||
|
||||
@@ -15,27 +15,36 @@
|
||||
*/
|
||||
package io.github.rosemoe.editor.langs;
|
||||
|
||||
import com.itsaky.androidide.models.SuggestItem;
|
||||
import com.itsaky.androidide.utils.Either;
|
||||
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
|
||||
import io.github.rosemoe.editor.interfaces.CodeAnalyzer;
|
||||
import io.github.rosemoe.editor.interfaces.EditorLanguage;
|
||||
import io.github.rosemoe.editor.interfaces.NewlineHandler;
|
||||
import io.github.rosemoe.editor.struct.CompletionItem;
|
||||
import io.github.rosemoe.editor.text.TextAnalyzeResult;
|
||||
import io.github.rosemoe.editor.text.TextAnalyzer.AnalyzeThread.Delegate;
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
import io.github.rosemoe.editor.widget.SymbolPairMatch;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Empty language without any effect
|
||||
*
|
||||
* @author Rose
|
||||
*/
|
||||
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
|
||||
import io.github.rosemoe.editor.interfaces.CodeAnalyzer;
|
||||
import io.github.rosemoe.editor.interfaces.EditorLanguage;
|
||||
import io.github.rosemoe.editor.interfaces.NewlineHandler;
|
||||
import io.github.rosemoe.editor.text.TextAnalyzeResult;
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
import io.github.rosemoe.editor.widget.SymbolPairMatch;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.xtend.lib.annotations.Delegate;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
|
||||
public class EmptyLanguage implements EditorLanguage {
|
||||
|
||||
@Override
|
||||
public LanguageServer getLanguageServer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLanguageCode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence format(CharSequence text) {
|
||||
return text;
|
||||
@@ -80,8 +89,8 @@ public class EmptyLanguage implements EditorLanguage {
|
||||
public static class EmptyAutoCompleteProvider implements AutoCompleteProvider {
|
||||
|
||||
@Override
|
||||
public List<Either<SuggestItem, CompletionItem>> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) {
|
||||
return new ArrayList<Either<SuggestItem, CompletionItem>>();
|
||||
public List<CompletionItem> getAutoCompleteItems(String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) {
|
||||
return new ArrayList<CompletionItem>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -89,10 +98,9 @@ public class EmptyLanguage implements EditorLanguage {
|
||||
private static class EmptyCodeAnalyzer implements CodeAnalyzer {
|
||||
|
||||
@Override
|
||||
public void analyze(CharSequence content, TextAnalyzeResult colors, Delegate delegate) {
|
||||
public void analyze(CharSequence content, TextAnalyzeResult colors, io.github.rosemoe.editor.text.TextAnalyzer.AnalyzeThread.Delegate delegate) {
|
||||
colors.addNormalIfNull();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,27 +58,21 @@ import androidx.annotation.Px;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.appcompat.view.menu.MenuBuilder;
|
||||
import com.blankj.utilcode.util.ThrowableUtils;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.LayoutDialogTextInputBinding;
|
||||
import com.itsaky.androidide.interfaces.JLSRequestor;
|
||||
import com.itsaky.androidide.managers.VersionedFileManager;
|
||||
import com.itsaky.androidide.lsp.LSPProvider;
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.androidide.utils.Symbols;
|
||||
import com.itsaky.androidide.utils.TypefaceUtils;
|
||||
import com.itsaky.lsp.CodeAction;
|
||||
import com.itsaky.lsp.Diagnostic;
|
||||
import com.itsaky.lsp.DiagnosticSeverity;
|
||||
import com.itsaky.lsp.DidChangeTextDocumentParams;
|
||||
import com.itsaky.lsp.Position;
|
||||
import com.itsaky.lsp.Range;
|
||||
import com.itsaky.lsp.ReferenceContext;
|
||||
import com.itsaky.lsp.ReferenceParams;
|
||||
import com.itsaky.lsp.TextDocumentContentChangeEvent;
|
||||
import com.itsaky.lsp.TextDocumentIdentifier;
|
||||
import com.itsaky.lsp.TextDocumentPositionParams;
|
||||
import com.itsaky.lsp.VersionedTextDocumentIdentifier;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
import io.github.rosemoe.editor.interfaces.EditorEventListener;
|
||||
import io.github.rosemoe.editor.interfaces.EditorLanguage;
|
||||
import io.github.rosemoe.editor.interfaces.NewlineHandler;
|
||||
@@ -106,8 +100,29 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.itsaky.lsp.CodeActionParams;
|
||||
import com.itsaky.lsp.CodeAction;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.DefinitionParams;
|
||||
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.ServerCapabilities;
|
||||
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.TextDocumentItem;
|
||||
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
import android.app.ProgressDialog;
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.itsaky.androidide.lsp.IDELanguageClient;
|
||||
import org.eclipse.lsp4j.ShowDocumentParams;
|
||||
import org.eclipse.lsp4j.CodeActionOptions;
|
||||
import org.eclipse.lsp4j.DefinitionOptions;
|
||||
import org.eclipse.lsp4j.ReferenceOptions;
|
||||
|
||||
/**
|
||||
* CodeEditor is a editor that can highlight text regions by doing basic syntax analyzing
|
||||
@@ -194,6 +209,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
private int mCachedLineNumberWidth;
|
||||
private int mErrorColor;
|
||||
private int mWarningColor;
|
||||
private int mFileVersion = 0;
|
||||
private float mDpUnit;
|
||||
private float mDividerWidth;
|
||||
private float mDividerMargin;
|
||||
@@ -213,6 +229,9 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
private boolean mLineNumberEnabled;
|
||||
private boolean mBlockLineEnabled;
|
||||
private boolean mAutoCompletionEnabled;
|
||||
private boolean mFindReferencesEnabled;
|
||||
private boolean mGotoDefinitionEnabled;
|
||||
private boolean mCodeActionsEnabled;
|
||||
private boolean mCompletionOnComposing;
|
||||
private boolean mHighlightSelectedText;
|
||||
private boolean mHighlightCurrentBlock;
|
||||
@@ -245,6 +264,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
private EditorColorScheme mColors;
|
||||
private String mLnTip = "Line:";
|
||||
private EditorLanguage mLanguage;
|
||||
private LanguageServer mLanguageServer;
|
||||
private long mLastMakeVisible = 0;
|
||||
private EditorAutoCompleteWindow mCompletionWindow;
|
||||
private DiagnosticWindow mDiagnosticWindow;
|
||||
@@ -253,7 +273,6 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
private GestureDetector mBasicDetector;
|
||||
protected EditorTextActionPresenter mTextActionPresenter;
|
||||
private ScaleGestureDetector mScaleDetector;
|
||||
EditorInputConnection mConnection;
|
||||
private CursorAnchorInfo.Builder mAnchorInfoBuilder;
|
||||
private MaterialEdgeEffect mVerticalEdgeGlow;
|
||||
private MaterialEdgeEffect mHorizontalGlow;
|
||||
@@ -269,6 +288,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
private SymbolPairMatch mOverrideSymbolPairs;
|
||||
private LongArrayList mPostDrawLineNumbers = new LongArrayList();
|
||||
private CharPosition mLockedSelection;
|
||||
EditorInputConnection mConnection;
|
||||
KeyMetaStates mKeyMetaStates = new KeyMetaStates(this);
|
||||
|
||||
private Map<HexColor, Integer> mLineColors;
|
||||
@@ -306,8 +326,42 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
return currentFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current file whose contents this editor is editing <br>
|
||||
* This method must be called only once in this editors lifetime <br>
|
||||
* This will also make sure to send 'textDocument/didOpen' to any provided language server.
|
||||
*
|
||||
* @param file The file that was opened
|
||||
*/
|
||||
public void setFile(File file) {
|
||||
this.currentFile = file;
|
||||
|
||||
if(mLanguageServer != null) {
|
||||
TextDocumentItem item = new TextDocumentItem();
|
||||
item.setLanguageId(getLanguageIdForFile());
|
||||
item.setText(getText().toString());
|
||||
item.setUri(file.toURI().toString());
|
||||
item.setVersion(mFileVersion = 0);
|
||||
mLanguageServer.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(item));
|
||||
}
|
||||
}
|
||||
|
||||
private String getLanguageIdForFile() {
|
||||
final String name = getFile().getName();
|
||||
|
||||
if(name.endsWith(".java")) {
|
||||
return "java";
|
||||
}
|
||||
|
||||
if(name.endsWith(".xml")) {
|
||||
return "xml";
|
||||
}
|
||||
|
||||
if(name.endsWith(".kt")) {
|
||||
return "kotlin";
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -555,14 +609,14 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
int leftCol = getCursor().getLeftColumn();
|
||||
int rightLine = getCursor().getRightLine();
|
||||
int rightCol = getCursor().getRightColumn();
|
||||
final Range range = new Range(new Position(leftLine, leftCol), new Position(rightLine, rightCol));
|
||||
final org.eclipse.lsp4j. Range range = new org.eclipse.lsp4j. Range(new Position(leftLine, leftCol), new Position(rightLine, rightCol));
|
||||
final Set<Range> keys = mDiagnostics.keySet();
|
||||
Range found = null;
|
||||
for(Range r : keys) {
|
||||
if(r.start.line <= range.start.line
|
||||
&& r.start.column <= range.start.column
|
||||
&& r.end.line >= range.end.line
|
||||
&& r.end.column >= range.end.column) {
|
||||
if(r.start.line <= range.getStart().getLine()
|
||||
&& r.start.column <= range.getStart().getCharacter()
|
||||
&& r.end.line >= range.getEnd().getLine()
|
||||
&& r.end.column >= range.getEnd().getCharacter()) {
|
||||
found = r;
|
||||
break;
|
||||
}
|
||||
@@ -749,7 +803,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
lang = new EmptyLanguage();
|
||||
}
|
||||
this.mLanguage = lang;
|
||||
|
||||
this.mLanguageServer = mLanguage.getLanguageServer();
|
||||
|
||||
// Update spanner
|
||||
if (mSpanner != null) {
|
||||
mSpanner.shutdown();
|
||||
@@ -781,13 +836,63 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
if (mCursor != null) {
|
||||
mCursor.setLanguage(mLanguage);
|
||||
}
|
||||
|
||||
setupLanguageServerCapabilities();
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public EditorLanguage getEditorLanguage() {
|
||||
return mLanguage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the {@link org.eclipse.lsp4j.services.LanguageServer current language server} which is set to this editor
|
||||
* May return null;
|
||||
*
|
||||
* @return Current language server or {@code null}.
|
||||
*/
|
||||
public LanguageServer getLanguageServer () {
|
||||
return mLanguageServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up this editor according to the capabilities of the current language server
|
||||
*/
|
||||
private void setupLanguageServerCapabilities() {
|
||||
if(mLanguage == null || mLanguageServer == null) return;
|
||||
|
||||
ServerCapabilities c = LSPProvider.getServerCapabilitiesForLanguage(mLanguage.getLanguageCode());
|
||||
if(c == null) {
|
||||
mCodeActionsEnabled = mGotoDefinitionEnabled = mFindReferencesEnabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Either<Boolean, CodeActionOptions> codeActionOptions = c.getCodeActionProvider();
|
||||
if(codeActionOptions.isLeft()) {
|
||||
mCodeActionsEnabled = codeActionOptions.getLeft();
|
||||
} else {
|
||||
CodeActionOptions options = codeActionOptions.getRight();
|
||||
mCodeActionsEnabled = options != null;
|
||||
}
|
||||
|
||||
Either<Boolean, DefinitionOptions> definitionOptions = c.getDefinitionProvider();
|
||||
if(definitionOptions.isLeft()) {
|
||||
mGotoDefinitionEnabled = definitionOptions.getLeft();
|
||||
} else {
|
||||
DefinitionOptions options = definitionOptions.getRight();
|
||||
mGotoDefinitionEnabled = options != null;
|
||||
}
|
||||
|
||||
Either<Boolean, ReferenceOptions> referenceOptions = c.getReferencesProvider();
|
||||
if(definitionOptions.isLeft()) {
|
||||
mFindReferencesEnabled = referenceOptions.getLeft();
|
||||
} else {
|
||||
ReferenceOptions options = referenceOptions.getRight();
|
||||
mFindReferencesEnabled = options != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter
|
||||
*
|
||||
@@ -2575,6 +2680,28 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
mCompletionWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether we should provide any code actions
|
||||
* <p>
|
||||
*/
|
||||
public void setCodeActionsEnabled(boolean codeActionsEnabled) {
|
||||
mCodeActionsEnabled = codeActionsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether we should provide any 'Go to definition' support
|
||||
*/
|
||||
public void setGotoDefinitionEnabled(boolean gotoDefinitionEnabled) {
|
||||
mGotoDefinitionEnabled = gotoDefinitionEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether we should provide any 'Find references' support
|
||||
*/
|
||||
public void setFindReferencesEnabled(boolean findReferencesEnabled) {
|
||||
mFindReferencesEnabled = findReferencesEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CodeEditor#setAutoCompletionEnabled(boolean)
|
||||
@@ -2582,6 +2709,18 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
public boolean isAutoCompletionEnabled() {
|
||||
return mAutoCompletionEnabled;
|
||||
}
|
||||
|
||||
public boolean isCodeActionsEnabled() {
|
||||
return mCodeActionsEnabled && mLanguageServer != null;
|
||||
}
|
||||
|
||||
public boolean isGotoDefinitionEnabled() {
|
||||
return mGotoDefinitionEnabled && mLanguageServer != null;
|
||||
}
|
||||
|
||||
public boolean isFindReferencesEnabled() {
|
||||
return mFindReferencesEnabled && mLanguageServer != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets non-printable painting flags.
|
||||
@@ -3767,8 +3906,6 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
mListener.onNewTextSet(this);
|
||||
}
|
||||
|
||||
notifyChanged();
|
||||
|
||||
if (mInputMethodManager != null) {
|
||||
mInputMethodManager.restartInput(this);
|
||||
}
|
||||
@@ -4495,43 +4632,143 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
}
|
||||
|
||||
public void findDefinition() {
|
||||
if(jlsRequestor == null || getFile() == null) return;
|
||||
if(!isGotoDefinitionEnabled()) return;
|
||||
|
||||
final ProgressDialog pd = ProgressDialog.show(getContext(), null, getContext().getString(R.string.msg_finding_definition));
|
||||
|
||||
try {
|
||||
TextDocumentPositionParams params = new TextDocumentPositionParams();
|
||||
params.position = new Position(getCursor().getLeftLine(), getCursor().getLeftColumn());
|
||||
params.textDocument = new TextDocumentIdentifier(getFile().toURI());
|
||||
jlsRequestor.findDefinition(params);
|
||||
final DefinitionParams params = new DefinitionParams();
|
||||
params.setTextDocument(new TextDocumentIdentifier(getFile().toURI().toString()));
|
||||
params.setPosition(getCursorAsLSPPosition());
|
||||
final CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> future = mLanguageServer.getTextDocumentService().definition(params);
|
||||
future.whenComplete((e, t) -> {
|
||||
final Either<List<? extends Location>, List<? extends LocationLink>> either = e;
|
||||
final Throwable th = t;
|
||||
final IDELanguageClient client = LSPProvider.getClientForLanguage(mLanguage.getLanguageCode());
|
||||
if(client == null || future.isCancelled() || future.isCompletedExceptionally()) {
|
||||
showDefinitionNotFound(pd);
|
||||
return;
|
||||
}
|
||||
|
||||
if(either.isLeft()) {
|
||||
List<? extends Location> locations = either.getLeft();
|
||||
if(locations == null || locations.size() <= 0) {
|
||||
showDefinitionNotFound(pd);
|
||||
return;
|
||||
}
|
||||
|
||||
if(locations.size() == 1) {
|
||||
Location location = locations.get(0);
|
||||
showDocument(client, location.getUri(), location.getRange());
|
||||
} else {
|
||||
client.showLocations(locations);
|
||||
}
|
||||
} else if(either.isRight()) {
|
||||
List<? extends LocationLink> locations = either.getRight();
|
||||
if(locations == null || locations.size() <= 0) {
|
||||
showDefinitionNotFound(pd);
|
||||
return;
|
||||
}
|
||||
|
||||
if(locations.size() == 1) {
|
||||
LocationLink location = locations.get(0);
|
||||
showDocument(client, location.getTargetUri(), location.getTargetSelectionRange());
|
||||
} else {
|
||||
client.showLocationLinks(locations);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable th) {
|
||||
LOG.error(ThrowableUtils.getFullStackTrace(th));
|
||||
showDefinitionNotFound(pd);
|
||||
}
|
||||
}
|
||||
|
||||
private void showDefinitionNotFound(final ProgressDialog pd) {
|
||||
ThreadUtils.runOnUiThread(() -> {
|
||||
toast(R.string.msg_no_definition, Toaster.Type.ERROR);
|
||||
pd.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
public void findReferences() {
|
||||
if(jlsRequestor == null || getFile() == null) return;
|
||||
if(!isFindReferencesEnabled()) return;
|
||||
|
||||
final ProgressDialog pd = ProgressDialog.show(getContext(), null, getContext().getString(R.string.msg_finding_definition));
|
||||
|
||||
try {
|
||||
ReferenceParams params = new ReferenceParams();
|
||||
params.context = new ReferenceContext(true);
|
||||
params.position = new Position(getCursor().getLeftLine(), getCursor().getLeftColumn());
|
||||
params.textDocument = new TextDocumentIdentifier(getFile().toURI());
|
||||
jlsRequestor.findReferences(params);
|
||||
|
||||
} catch (Throwable th) {
|
||||
LOG.error(ThrowableUtils.getFullStackTrace(th));
|
||||
showReferencesNotFound(pd);
|
||||
}
|
||||
}
|
||||
|
||||
public void performCodeActions(List<CodeAction> actions) {
|
||||
if(jlsRequestor != null && getFile() != null) {
|
||||
jlsRequestor.performCodeActions(getFile(), actions);
|
||||
private void showReferencesNotFound(final ProgressDialog pd) {
|
||||
ThreadUtils.runOnUiThread(() -> {
|
||||
toast(R.string.msg_no_references, Toaster.Type.ERROR);
|
||||
pd.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the client to show the specific document in an editor and select the provided range
|
||||
*/
|
||||
private void showDocument(IDELanguageClient client, String uri, org.eclipse.lsp4j.Range range) {
|
||||
ShowDocumentParams params = new ShowDocumentParams();
|
||||
params.setExternal(false);
|
||||
params.setSelection(range);
|
||||
params.setUri(uri);
|
||||
client.showDocument(params);
|
||||
}
|
||||
|
||||
public void didSave () {
|
||||
if(mLanguageServer != null && getFile() != null) {
|
||||
mLanguageServer.getTextDocumentService().didSave(new DidSaveTextDocumentParams(new org.eclipse.lsp4j.TextDocumentIdentifier(getFile().toURI().toString())));
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if(mLanguageServer != null && getFile() != null) {
|
||||
mLanguageServer.getTextDocumentService().didClose(new DidCloseTextDocumentParams(new org.eclipse.lsp4j.TextDocumentIdentifier(getFile().toURI().toString())));
|
||||
}
|
||||
}
|
||||
|
||||
public void codeAction() {
|
||||
|
||||
}
|
||||
|
||||
public Position getCursorAsLSPPosition() {
|
||||
return new Position(getCursor().getLeftLine(), getCursor().getLeftColumn());
|
||||
}
|
||||
|
||||
private boolean isInString() {
|
||||
final int line = getCursor().getLeftLine();
|
||||
final int column = getCursor().getLeftColumn();
|
||||
if(stringMap == null || stringMap.isEmpty() || !stringMap.containsKey(line)) return false;
|
||||
List<Range> strings = stringMap.get(line);
|
||||
if(strings == null || strings.isEmpty()) return false;
|
||||
for(int i=0;i<strings.size();i++) {
|
||||
Range r = strings.get(i);
|
||||
if(r.start.column <= column && column <= r.end.column)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void toast(int msg, Toaster.Type type) {
|
||||
StudioApp.getInstance().toast(msg, type);
|
||||
}
|
||||
|
||||
private void toast(String msg, Toaster.Type type) {
|
||||
StudioApp.getInstance().toast(msg, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterInsert(Content content, int startLine, int startColumn, int endLine, int endColumn, CharSequence insertedContent) {
|
||||
notifyChanged();
|
||||
|
||||
if(mDiagnosticWindow != null)
|
||||
mDiagnosticWindow.hide();
|
||||
|
||||
notifyChanged();
|
||||
|
||||
// Update spans
|
||||
if (isSpanMapPrepared(true, endLine - startLine)) {
|
||||
if (startLine == endLine) {
|
||||
@@ -4591,44 +4828,13 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
mListener.afterInsert(this, mText, startLine, startColumn, endLine, endColumn, insertedContent);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInString() {
|
||||
final int line = getCursor().getLeftLine();
|
||||
final int column = getCursor().getLeftColumn();
|
||||
if(stringMap == null || stringMap.isEmpty() || !stringMap.containsKey(line)) return false;
|
||||
List<Range> strings = stringMap.get(line);
|
||||
if(strings == null || strings.isEmpty()) return false;
|
||||
for(int i=0;i<strings.size();i++) {
|
||||
Range r = strings.get(i);
|
||||
if(r.start.column <= column && column <= r.end.column)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void notifyChanged() {
|
||||
if (getFile() != null && getFile().getName().endsWith(".java") && jlsRequestor != null) {
|
||||
TextDocumentContentChangeEvent event = new TextDocumentContentChangeEvent();
|
||||
VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier();
|
||||
DidChangeTextDocumentParams p = new DidChangeTextDocumentParams();
|
||||
List<TextDocumentContentChangeEvent> changes = new ArrayList<>();
|
||||
event.text = getText().toString();
|
||||
event.range = null;
|
||||
changes.add(event);
|
||||
id.uri = getFile().toURI();
|
||||
id.version = VersionedFileManager.incrementVersion(getFile());
|
||||
p.textDocument = id;
|
||||
p.contentChanges = changes;
|
||||
jlsRequestor.didChange(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterDelete(Content content, int startLine, int startColumn, int endLine, int endColumn, CharSequence deletedContent) {
|
||||
notifyChanged();
|
||||
|
||||
if(mDiagnosticWindow != null)
|
||||
mDiagnosticWindow.hide();
|
||||
|
||||
notifyChanged();
|
||||
|
||||
if (isSpanMapPrepared(false, endLine - startLine)) {
|
||||
if (startLine == endLine) {
|
||||
@@ -4721,6 +4927,38 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
postInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyChanged() {
|
||||
if(mLanguageServer != null) {
|
||||
DidChangeTextDocumentParams p = didChangeParams();
|
||||
if(p != null) {
|
||||
mLanguageServer.getTextDocumentService().didChange(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected DidChangeTextDocumentParams didChangeParams() {
|
||||
if(getFile() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
VersionedTextDocumentIdentifier doc = new VersionedTextDocumentIdentifier();
|
||||
doc.setVersion(++mFileVersion);
|
||||
doc.setUri(getFile().toURI().toString());
|
||||
DidChangeTextDocumentParams p = new DidChangeTextDocumentParams();
|
||||
p.setTextDocument(doc);
|
||||
p.setContentChanges(createChangeEvents());
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
private List<TextDocumentContentChangeEvent> createChangeEvents() {
|
||||
final List<TextDocumentContentChangeEvent> events = new ArrayList<>();
|
||||
TextDocumentContentChangeEvent event = new TextDocumentContentChangeEvent();
|
||||
event.setText(getText().toString());
|
||||
events.add(event);
|
||||
return events;
|
||||
}
|
||||
|
||||
public void onEndTextSelect() {
|
||||
showTextActionPopup();
|
||||
|
||||
+16
-23
@@ -15,6 +15,11 @@
|
||||
*/
|
||||
package io.github.rosemoe.editor.widget;
|
||||
|
||||
/**
|
||||
* Adapter to display results
|
||||
*
|
||||
* @author Rose
|
||||
*/
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.TypedValue;
|
||||
@@ -25,15 +30,8 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.models.SuggestItem;
|
||||
import com.itsaky.androidide.utils.Either;
|
||||
import io.github.rosemoe.editor.struct.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
|
||||
/**
|
||||
* Adapter to display results
|
||||
*
|
||||
* @author Rose
|
||||
*/
|
||||
@SuppressWarnings("CanBeFinal")
|
||||
class DefaultCompletionItemAdapter extends EditorCompletionAdapter {
|
||||
|
||||
@@ -47,29 +45,24 @@ class DefaultCompletionItemAdapter extends EditorCompletionAdapter {
|
||||
if (view == null) {
|
||||
view = LayoutInflater.from(getContext()).inflate(R.layout.default_completion_result_item, parent, false);
|
||||
}
|
||||
Either<SuggestItem, CompletionItem> item = getItem(pos);
|
||||
String label, desc;
|
||||
Drawable icon;
|
||||
if(item.isLeft()) {
|
||||
label = item.getLeft().getName();
|
||||
desc = item.getLeft().getDescription();
|
||||
icon = ContextCompat.getDrawable(parent.getContext(), R.mipmap.box_red);
|
||||
} else {
|
||||
label = item.getRight().label;
|
||||
desc = item.getRight().desc;
|
||||
icon = item.getRight().icon;
|
||||
}
|
||||
TextView tv = (TextView) view.findViewById(R.id.result_item_label);
|
||||
final CompletionItem item = getItem(pos);
|
||||
final String label = item.getLabel();
|
||||
final String desc = item.getDetail();
|
||||
final Drawable icon = ContextCompat.getDrawable(parent.getContext(), R.mipmap.box_red);
|
||||
|
||||
TextView tv = view.findViewById(R.id.result_item_label);
|
||||
tv.setText(label);
|
||||
tv = (TextView) view.findViewById(R.id.result_item_desc);
|
||||
tv = view.findViewById(R.id.result_item_desc);
|
||||
tv.setText(desc);
|
||||
view.setTag(pos);
|
||||
|
||||
if (isCurrentCursorPosition) {
|
||||
view.setBackgroundColor(0xffdddddd);
|
||||
} else {
|
||||
view.setBackgroundColor(0xffffffff);
|
||||
}
|
||||
ImageView iv = (ImageView) view.findViewById(R.id.result_item_image);
|
||||
|
||||
ImageView iv = view.findViewById(R.id.result_item_image);
|
||||
iv.setImageDrawable(icon);
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -15,29 +15,30 @@
|
||||
*/
|
||||
package io.github.rosemoe.editor.widget;
|
||||
|
||||
/**
|
||||
* Auto complete window for editing code quicker
|
||||
*
|
||||
* @author Rose
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
import com.itsaky.androidide.databinding.LayoutCompletionWindowBinding;
|
||||
import com.itsaky.androidide.models.CompletionEdit;
|
||||
import com.itsaky.androidide.models.CompletionListItem;
|
||||
import com.itsaky.androidide.models.SuggestItem;
|
||||
import com.itsaky.androidide.utils.Either;
|
||||
import com.itsaky.androidide.language.java.manager.JavaCharacter;
|
||||
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
|
||||
import io.github.rosemoe.editor.struct.CompletionItem;
|
||||
import io.github.rosemoe.editor.text.CharPosition;
|
||||
import io.github.rosemoe.editor.text.Cursor;
|
||||
import io.github.rosemoe.editor.text.Content;
|
||||
import io.github.rosemoe.editor.text.TextAnalyzeResult;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
|
||||
/**
|
||||
* Auto complete window for editing code quicker
|
||||
*
|
||||
* @author Rose
|
||||
*/
|
||||
public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
|
||||
private final static String TIP = "Refreshing...";
|
||||
private final CodeEditor mEditor;
|
||||
@@ -185,47 +186,54 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
|
||||
* @param pos Index of auto complete item
|
||||
*/
|
||||
public void select(int pos) {
|
||||
Either<SuggestItem, CompletionItem> either = ((EditorCompletionAdapter) mBinding.list.getAdapter()).getItem(pos);
|
||||
if(either != null && either.isLeft()) {
|
||||
SuggestItem item = either.getLeft();
|
||||
item.onSelectThis(mEditor);
|
||||
} else if(either != null && either.isRight()) {
|
||||
final CompletionItem item = either.getRight();
|
||||
Cursor cursor = mEditor.getCursor();
|
||||
if (!cursor.isSelected()) {
|
||||
int l = 1;
|
||||
if(mLastPrefix.endsWith("."))
|
||||
l = 0;
|
||||
else if(mLastPrefix.contains("."))
|
||||
l = mLastPrefix.substring(mLastPrefix.lastIndexOf(".") + 1, mLastPrefix.length() - 1).length();
|
||||
else l = mLastPrefix.length() - 1;
|
||||
l++;
|
||||
if(mLastPrefix.endsWith(".")) l--;
|
||||
mEditor.getText().delete(cursor.getLeftLine(), cursor.getLeftColumn() - l, cursor.getLeftLine(), cursor.getLeftColumn());
|
||||
cursor.onCommitText(item.commit);
|
||||
|
||||
if (item.cursorOffset != item.commit.length()) {
|
||||
int delta = (item.commit.length() - item.cursorOffset);
|
||||
if (delta != 0) {
|
||||
int newSel = Math.max(mEditor.getCursor().getLeft() - delta, 0);
|
||||
CharPosition charPosition = mEditor.getCursor().getIndexer().getCharPosition(newSel);
|
||||
mEditor.setSelection(charPosition.line, charPosition.column);
|
||||
}
|
||||
}
|
||||
|
||||
if(item instanceof CompletionListItem && ((CompletionListItem) item).hasAdditionalEdits()) {
|
||||
for(CompletionEdit edit : ((CompletionListItem) item).getAdditionalEdits()) {
|
||||
if(edit.position.column == -1) {
|
||||
int col = mEditor.getText().getColumnCount(edit.position.line);
|
||||
edit.position.column = col;
|
||||
}
|
||||
mEditor.getText().insert(edit.position.line, edit.position.column, edit.insertText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CompletionItem item = ((EditorCompletionAdapter) mBinding.list.getAdapter()).getItem(pos);
|
||||
Range range = getIdentifierRange(mEditor.getCursor().getRight(), mEditor.getText());
|
||||
String text = item.getInsertText() == null ? item.getLabel() : item.getInsertText();
|
||||
final boolean shiftLeft = text.contains("$0");
|
||||
final boolean atEnd = text.endsWith("$0");
|
||||
text = text.replace("$0", "");
|
||||
|
||||
mEditor.getText().delete(
|
||||
range.getStart().getLine(),
|
||||
range.getStart().getCharacter(),
|
||||
range.getEnd().getLine(),
|
||||
range.getEnd().getCharacter()
|
||||
);
|
||||
mEditor.getCursor().onCommitText(text);
|
||||
|
||||
if(shiftLeft && !atEnd) {
|
||||
mEditor.moveSelectionLeft();
|
||||
}
|
||||
|
||||
final List<TextEdit> edits = item.getAdditionalTextEdits();
|
||||
if(edits != null && edits.size() > 0) {
|
||||
for(int i=0;i<edits.size();i++) {
|
||||
final TextEdit edit = edits.get(i);
|
||||
if(edit == null || edit.getRange() == null || edit.getNewText() == null) continue;
|
||||
final Position start = edit.getRange().getStart();
|
||||
final Position end = edit.getRange().getEnd();
|
||||
if(start == null || end == null) continue;
|
||||
if(start.equals(end)) {
|
||||
mEditor.getText().insert(start.getLine(), start.getCharacter(), edit.getNewText());
|
||||
} else {
|
||||
mEditor.getText().replace(start.getLine(), start.getCharacter(), end.getLine(), end.getCharacter(), edit.getNewText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mEditor.postHideCompletionWindow();
|
||||
}
|
||||
|
||||
private Range getIdentifierRange(int end, Content content) {
|
||||
int start = end;
|
||||
while(start > 0 && JavaCharacter.isJavaIdentifierPart(content.charAt(start - 1))) {
|
||||
start--;
|
||||
}
|
||||
|
||||
CharPosition startPos = content.getIndexer().getCharPosition(start);
|
||||
CharPosition endPos = content.getIndexer().getCharPosition(end);
|
||||
return new Range(new Position(startPos.line, startPos.column), new Position(endPos.line, endPos.column));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prefix set
|
||||
@@ -261,7 +269,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
|
||||
* @param results Items of analysis
|
||||
* @param requestTime The time that this thread starts
|
||||
*/
|
||||
public void displayResults(final List<Either<SuggestItem, CompletionItem>> results, long requestTime) {
|
||||
public void displayResults(final List<CompletionItem> results, long requestTime) {
|
||||
mEditor.post(() -> {
|
||||
setLoading(false);
|
||||
if (results == null || results.isEmpty()) {
|
||||
@@ -287,23 +295,29 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
|
||||
private class MatchThread extends Thread {
|
||||
private final long mTime;
|
||||
private final String mPrefix;
|
||||
private final String mFileUri;
|
||||
private final boolean mInner;
|
||||
private final TextAnalyzeResult mColors;
|
||||
private final int mIndex;
|
||||
private final int mLine;
|
||||
private final int mColumn;
|
||||
private final AutoCompleteProvider mLocalProvider = mProvider;
|
||||
|
||||
public MatchThread(long requestTime, String prefix) {
|
||||
mTime = requestTime;
|
||||
mPrefix = prefix;
|
||||
mFileUri = mEditor.getFile() != null ? mEditor.getFile().toURI().toString() : null;
|
||||
mColors = mEditor.getTextAnalyzeResult();
|
||||
mIndex = mEditor.getCursor().getLeft();
|
||||
mLine = mEditor.getCursor().getLeftLine();
|
||||
mColumn = mEditor.getCursor().getLeftColumn();
|
||||
mInner = (!mEditor.isHighlightCurrentBlock()) || (mEditor.getBlockIndex() != -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
displayResults(mLocalProvider.getAutoCompleteItems(mEditor, mPrefix, mInner, mColors, mLine), mTime);
|
||||
displayResults(mLocalProvider.getAutoCompleteItems(mFileUri, mPrefix, mInner, mColors, mIndex, mLine, mColumn), mTime);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
displayResults(new ArrayList<>(), mTime);
|
||||
|
||||
@@ -20,9 +20,8 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import com.itsaky.androidide.models.SuggestItem;
|
||||
import com.itsaky.androidide.utils.Either;
|
||||
import io.github.rosemoe.editor.struct.CompletionItem;
|
||||
import java.util.List;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
|
||||
/**
|
||||
* A class to make custom adapter for auto-completion window
|
||||
@@ -32,7 +31,7 @@ import java.util.List;
|
||||
public abstract class EditorCompletionAdapter extends BaseAdapter {
|
||||
|
||||
private EditorAutoCompleteWindow mWindow;
|
||||
private List<Either<SuggestItem, CompletionItem>> mItems;
|
||||
private List<CompletionItem> mItems;
|
||||
|
||||
public void clear() {
|
||||
if(mItems != null)
|
||||
@@ -42,13 +41,13 @@ public abstract class EditorCompletionAdapter extends BaseAdapter {
|
||||
/**
|
||||
* Called by {@link EditorAutoCompleteWindow} to attach some arguments
|
||||
*/
|
||||
public void attachAttributes(EditorAutoCompleteWindow window, List<Either<SuggestItem, CompletionItem>> items) {
|
||||
public void attachAttributes(EditorAutoCompleteWindow window, List<CompletionItem> items) {
|
||||
mWindow = window;
|
||||
mItems = items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Either<SuggestItem, CompletionItem> getItem(int position) {
|
||||
public CompletionItem getItem(int position) {
|
||||
return mItems.get(position);
|
||||
}
|
||||
|
||||
|
||||
+21
-16
@@ -54,8 +54,6 @@ class EditorTextActionModeStarter implements CodeEditor.EditorTextActionPresente
|
||||
}
|
||||
mActionMode = ((AppCompatActivity) mEditor.getContext()).startSupportActionMode(new ActionMode.Callback() {
|
||||
|
||||
private List<CodeAction> actions;
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
|
||||
mEditor.mStartedActionMode = CodeEditor.ACTION_MODE_SELECT_TEXT;
|
||||
@@ -71,10 +69,7 @@ class EditorTextActionModeStarter implements CodeEditor.EditorTextActionPresente
|
||||
android.R.attr.actionModePasteDrawable,
|
||||
});
|
||||
|
||||
// TODO: Request code actions on selection change
|
||||
actions = null;
|
||||
|
||||
if(actions != null && actions.size() > 0) {
|
||||
if(mEditor.isCodeActionsEnabled()) {
|
||||
menu.add(0, 8, 0, mEditor.getContext().getString(com.itsaky.androidide.R.string.msg_code_actions))
|
||||
.setIcon(createDrawable(com.itsaky.androidide.R.drawable.ic_quickfix))
|
||||
.setShowAsActionFlags(1);
|
||||
@@ -100,13 +95,17 @@ class EditorTextActionModeStarter implements CodeEditor.EditorTextActionPresente
|
||||
.setIcon(array.getDrawable(3));
|
||||
}
|
||||
|
||||
menu.add(0, 4, 0, mEditor.getContext().getString(com.itsaky.androidide.R.string.menu_navigate_definition))
|
||||
.setShowAsActionFlags(1)
|
||||
.setIcon(createDrawable(com.itsaky.androidide.R.drawable.ic_goto_definition));
|
||||
if(mEditor.isGotoDefinitionEnabled()) {
|
||||
menu.add(0, 4, 0, mEditor.getContext().getString(com.itsaky.androidide.R.string.menu_navigate_definition))
|
||||
.setShowAsActionFlags(1)
|
||||
.setIcon(createDrawable(com.itsaky.androidide.R.drawable.ic_goto_definition));
|
||||
}
|
||||
|
||||
menu.add(0, 5, 0, mEditor.getContext().getString(com.itsaky.androidide.R.string.menu_navigate_references))
|
||||
.setShowAsActionFlags(1)
|
||||
.setIcon(createDrawable( com.itsaky.androidide.R.drawable.ic_find_references));
|
||||
if(mEditor.isFindReferencesEnabled()) {
|
||||
menu.add(0, 5, 0, mEditor.getContext().getString(com.itsaky.androidide.R.string.menu_navigate_references))
|
||||
.setShowAsActionFlags(1)
|
||||
.setIcon(createDrawable( com.itsaky.androidide.R.drawable.ic_find_references));
|
||||
}
|
||||
|
||||
menu.add(0, 6, 0, mEditor.getContext().getString(com.itsaky.androidide.R.string.menu_comment_line))
|
||||
.setShowAsActionFlags(1)
|
||||
@@ -139,19 +138,25 @@ class EditorTextActionModeStarter implements CodeEditor.EditorTextActionPresente
|
||||
final MenuItem uncomment = menu.findItem(7);
|
||||
final MenuItem codeActions = menu.findItem(8);
|
||||
|
||||
def.setEnabled(isJava).getIcon().setAlpha(isJava ? 255 : 76);
|
||||
ref.setEnabled(isJava).getIcon().setAlpha(isJava ? 255 : 76);
|
||||
comment.setEnabled(isJava || isXml).getIcon().setAlpha(isJava || isXml ? 255 : 76);
|
||||
uncomment.setEnabled(isJava || isXml).getIcon().setAlpha(isJava || isXml ? 255 : 76);
|
||||
|
||||
/**
|
||||
* CodeActions menu item may, or may not be added
|
||||
* Thesw menu items may, or may not be added
|
||||
* So we need to check if its null or not
|
||||
* before we perform any further actions
|
||||
*/
|
||||
if(codeActions != null)
|
||||
codeActions.setEnabled(isJava).getIcon().setAlpha(isJava ? 255 : 76);
|
||||
|
||||
if(def != null) {
|
||||
def.setEnabled(isJava).getIcon().setAlpha(isJava ? 255 : 76);
|
||||
}
|
||||
|
||||
if(ref != null) {
|
||||
ref.setEnabled(isJava).getIcon().setAlpha(isJava ? 255 : 76);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -183,7 +188,7 @@ class EditorTextActionModeStarter implements CodeEditor.EditorTextActionPresente
|
||||
mEditor.uncommentLine();
|
||||
break;
|
||||
case 8:
|
||||
mEditor.performCodeActions(actions);
|
||||
mEditor.codeAction();
|
||||
break;
|
||||
}
|
||||
if(menuItem.getItemId() != 0) {
|
||||
|
||||
+5
-7
@@ -1,15 +1,14 @@
|
||||
buildscript {
|
||||
ext {
|
||||
build_gradle_version = '4.0.1'
|
||||
androidide_plugin_version = '1.0.6'
|
||||
build_gradle_version = '7.0.1'
|
||||
}
|
||||
project.ext {
|
||||
compileSdk = 30
|
||||
buildTools = "30.0.2"
|
||||
compileSdk = 31
|
||||
buildTools = "31.0.0"
|
||||
minSdk = 21
|
||||
targetSdk = 28
|
||||
versionCode = 102210808
|
||||
versionName = "1.0.2.1-alpha"
|
||||
versionCode = 103210928
|
||||
versionName = "1.0.3-alpha"
|
||||
packageName = "com.itsaky.androidide"
|
||||
}
|
||||
|
||||
@@ -27,7 +26,6 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:$build_gradle_version"
|
||||
classpath "com.itsaky.androidide:gradle:$androidide_plugin_version"
|
||||
classpath 'com.github.megatronking.stringfog:gradle-plugin:2.2.1'
|
||||
classpath 'com.github.megatronking.stringfog:xor:1.1.0'
|
||||
classpath 'com.google.gms:google-services:4.3.8'
|
||||
|
||||
Reference in New Issue
Block a user