- Draw diagnostics in editor & send pending messages to server once started

This commit is contained in:
itsaky
2021-08-01 13:47:47 +05:30
parent 930e54d7f7
commit 8cbed76564
12 changed files with 326 additions and 158 deletions
Binary file not shown.
@@ -31,6 +31,9 @@ import com.google.android.material.navigation.NavigationView;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.transition.MaterialArcMotion;
import com.google.android.material.transition.MaterialContainerTransform;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.itsaky.androidide.adapters.DiagnosticsAdapter;
import com.itsaky.androidide.adapters.EditorPagerAdapter;
import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder;
@@ -42,11 +45,11 @@ import com.itsaky.androidide.fragments.EditorFragment;
import com.itsaky.androidide.fragments.FileTreeFragment;
import com.itsaky.androidide.fragments.sheets.OptionsListFragment;
import com.itsaky.androidide.fragments.sheets.TextSheetFragment;
import com.itsaky.androidide.interfaces.DexResultListener;
import com.itsaky.androidide.interfaces.DiagnosticClickListener;
import com.itsaky.androidide.interfaces.JLSRequestor;
import com.itsaky.androidide.language.java.server.JavaLanguageServer;
import com.itsaky.androidide.language.logs.LogLanguageImpl;
import com.itsaky.androidide.models.AndroidProject;
import com.itsaky.androidide.models.ConstantsBridge;
import com.itsaky.androidide.models.DiagnosticGroup;
import com.itsaky.androidide.models.LogLine;
import com.itsaky.androidide.models.SheetOption;
@@ -62,8 +65,15 @@ import com.itsaky.androidide.utils.Symbols;
import com.itsaky.androidide.utils.TypefaceUtils;
import com.itsaky.androidide.utils.VersionedFileManager;
import com.itsaky.androidide.views.MaterialBanner;
import com.itsaky.lsp.Diagnostic;
import com.itsaky.lsp.DidCloseTextDocumentParams;
import com.itsaky.lsp.DidOpenTextDocumentParams;
import com.itsaky.lsp.JavaReportProgressParams;
import com.itsaky.lsp.JavaStartProgressParams;
import com.itsaky.lsp.LanguageClient;
import com.itsaky.lsp.Message;
import com.itsaky.lsp.PublishDiagnosticsParams;
import com.itsaky.lsp.ShowMessageParams;
import com.itsaky.lsp.TextDocumentItem;
import com.itsaky.toaster.Toaster;
import com.unnamed.b.atv.model.TreeNode;
@@ -75,15 +85,13 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Pattern;
import me.piruin.quickaction.ActionItem;
import me.piruin.quickaction.QuickAction;
import com.itsaky.lsp.JavaReportProgressParams;
import com.itsaky.lsp.PublishDiagnosticsParams;
import com.itsaky.lsp.JavaStartProgressParams;
import com.itsaky.lsp.ShowMessageParams;
import com.google.gson.JsonElement;
import com.itsaky.lsp.Diagnostic;
import com.itsaky.lsp.DidSaveTextDocumentParams;
import com.itsaky.lsp.DidChangeTextDocumentParams;
import com.itsaky.lsp.TextDocumentIdentifier;
public class EditorActivity extends StudioActivity implements FileTreeFragment.FileActionListener,
IDEService.BuildListener,
@@ -91,7 +99,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
NavigationView.OnNavigationItemSelectedListener,
DiagnosticClickListener,
EditorFragment.FileOpenListener,
LanguageClient, CodeEditor.CursorChangeListener {
LanguageClient, CodeEditor.CursorChangeListener,
JLSRequestor {
private ActivityEditorBinding mBinding;
private EditorPagerAdapter mPagerAdapter;
@@ -113,13 +122,15 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
private boolean recreateCompletionServices = false;
private final Map<File, List<Diagnostic>> diagnostics = new HashMap<>();
private final Stack<Message> pendingMessages = new Stack<>();
private final String RES_PATH_REGEX = "/.*/src/.*/res";
private final String LAYOUTRES_PATH_REGEX = "/.*/src/.*/res/layout";
private final String MENURES_PATH_REGEX = "/.*/src/.*/res/menu";
private final String DRAWABLERES_PATH_REGEX = "/.*/src/.*/res/drawable";
private final String JAVA_PATH_REGEX = "/.*/src/.*/java";
private static final Gson gson = new Gson();
private final IDEService.BuildListener mBuildListener = this;
private static final String TAG_FILE_OPTIONS_FRAGMENT = "file_options_fragment";
public static final String TAG = "EditorActivity";
@@ -652,21 +663,6 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
.setCancelable(false)
.create().show();
}
private void provideDiagnosticsToEditors(Map<File, List<Diagnostic>> diagnostics) {
if(mPagerAdapter != null && mPagerAdapter.getFragments() != null) {
List<Fragment> editors = mPagerAdapter.getFragments();
if(editors.size() <= 0) return;
for(int i=0;i<editors.size();i++) {
try {
EditorFragment editor = mPagerAdapter.getFrag(i);
if(diagnostics.containsKey(editor.getFile())) {
editor.setDiagnostics(diagnostics.get(editor.getFile()));
}
} catch (Throwable th) {}
}
}
}
@Override
public void onGroupClick(DiagnosticGroup group) {
@@ -701,8 +697,12 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
File file = new File(params.uri);
if(!(file.exists() && file.isFile())) return;
diagnostics.put(file, params.diagnostics);
getDiagnosticsList().setAdapter(new DiagnosticsAdapter(mapAsGroup(diagnostics), this));
EditorFragment editor = null;
if(mPagerAdapter != null && (editor = mPagerAdapter.findEditorByFile(new File(params.uri))) != null) {
editor.setDiagnostics(params.diagnostics);
}
}
@Override
@@ -716,6 +716,15 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
@Override
public void customNotification(String method, JsonElement params) {
}
@Override
public void onServerStarted(int currentId) {
final JavaLanguageServer server = getApp().getJavaLanguageServer();
while(server != null && !pendingMessages.isEmpty()) {
Message msg = pendingMessages.pop();
server.send(msg);
}
}
@Override
public void onCursorPositionChange(int leftLine, int leftColumn, int rightLine, int rightColumn) {
@@ -725,6 +734,50 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
mBinding.editorToolbar.setSubtitle(String.format("[%d, %d] | [%d, %d]", leftLine, leftColumn, rightLine, rightColumn));
}
}
@Override
public void didOpen(DidOpenTextDocumentParams p) {
final JavaLanguageServer server = getApp().getJavaLanguageServer();
if(server != null)
server.didOpen(p);
else addPendingMessage(createMessage(JavaLanguageServer.Method.DID_OPEN, gson.toJson(p)));
}
@Override
public void didClose(DidCloseTextDocumentParams p) {
final JavaLanguageServer server = getApp().getJavaLanguageServer();
if(server != null)
server.didClose(p);
else addPendingMessage(createMessage(JavaLanguageServer.Method.DID_CLOSE, gson.toJson(p)));
}
@Override
public void didChange(DidChangeTextDocumentParams params) {
final JavaLanguageServer server = getApp().getJavaLanguageServer();
if(server != null)
server.didChange(params);
else addPendingMessage(createMessage(JavaLanguageServer.Method.DID_CHANGE, gson.toJson(params)));
}
@Override
public void didSave(DidSaveTextDocumentParams params) {
final JavaLanguageServer server = getApp().getJavaLanguageServer();
if(server != null)
server.didSave(params);
else addPendingMessage(createMessage(JavaLanguageServer.Method.DID_SAVE, gson.toJson(params)));
}
private void addPendingMessage(Message msg) {
pendingMessages.push(msg);
}
private Message createMessage(String method, String data) {
Message msg = new Message();
msg.jsonrpc = "2.0";
msg.method = method;
msg.params = new JsonParser().parse(data);
return msg;
}
/*****************************
**** Tab Related ************
@@ -907,7 +960,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
@Override
public void openFile(File file) {
int i = mPagerAdapter.openFile(file, this, this);
int i = mPagerAdapter.openFile(file, this, this, this);
if(i >= 0 && !mBinding.tabs.getTabAt(i).isSelected())
mBinding.tabs.getTabAt(i).select();
mBinding.editorDrawerLayout.closeDrawer(GravityCompat.END);
@@ -923,7 +976,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
doc.version = VersionedFileManager.fileOpened(file);
DidOpenTextDocumentParams p = new DidOpenTextDocumentParams();
p.textDocument = doc;
getApp().getJavaLanguageServer().didOpen(p);
didOpen(p);
}
}
@@ -1269,11 +1322,13 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
private void closeFile(int index, boolean selectOther) {
mBinding.tabs.removeOnTabSelectedListener(this);
int pos = index;
final List<Fragment> frags = mPagerAdapter.getFragments();
final List<File> files = mPagerAdapter.getOpenedFiles();
final File removed = files.get(index);
frags.remove(index);
files.remove(index);
@@ -1297,5 +1352,11 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
mBinding.editorViewPager.setCurrentItem(pos, false);
}
}
TextDocumentIdentifier id = new TextDocumentIdentifier();
id.uri = removed.toURI();
DidCloseTextDocumentParams p = new DidCloseTextDocumentParams();
p.textDocument = id;
didClose(p);
}
}
@@ -9,6 +9,7 @@ import io.github.rosemoe.editor.widget.CodeEditor;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import com.itsaky.androidide.interfaces.JLSRequestor;
public class EditorPagerAdapter extends FragmentStatePagerAdapter
{
@@ -34,8 +35,22 @@ public class EditorPagerAdapter extends FragmentStatePagerAdapter
mOpenedFiles.addAll(files);
mFragments.addAll(fragments);
}
public EditorFragment findEditorByFile(File file) {
for(int i=0;i<mFragments.size();i++) {
Fragment frag = mFragments.get(i);
if(!(frag instanceof EditorFragment))
continue;
EditorFragment editor = (EditorFragment) frag;
if(editor.getFile() != null && editor.getFile().getAbsolutePath().equals(file.getAbsolutePath()))
return editor;
}
return null;
}
public int openFile(File file, EditorFragment.FileOpenListener listener, CodeEditor.CursorChangeListener cursorListener)
public int openFile(File file, EditorFragment.FileOpenListener listener, CodeEditor.CursorChangeListener cursorListener, JLSRequestor jlsRequestor)
{
int openedFileIndex = -1;
for(int i=0;i<mOpenedFiles.size();i++)
@@ -50,7 +65,11 @@ public class EditorPagerAdapter extends FragmentStatePagerAdapter
if(openedFileIndex == -1)
{
mFragments.add(EditorFragment.newInstance(file, project).setFileOpenListener(listener).setCursorChangeListener(cursorListener));
mFragments.add(EditorFragment
.newInstance(file, project)
.setFileOpenListener(listener)
.setCursorChangeListener(cursorListener)
.setJLSRequestor(jlsRequestor));
mOpenedFiles.add(file);
notifyDataSetChanged();
return mFragments.size() - 1;
@@ -4,13 +4,13 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.blankj.utilcode.util.FileIOUtils;
import com.itsaky.androidide.adapters.CompletionListAdapter;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.databinding.FragmentEditorBinding;
import com.itsaky.androidide.fragments.preferences.EditorPreferences;
import com.itsaky.androidide.interfaces.JLSRequestor;
import com.itsaky.androidide.language.groovy.GroovyLanguage;
import com.itsaky.androidide.language.java.JavaLanguage;
import com.itsaky.androidide.language.java.server.JavaLanguageServer;
import com.itsaky.androidide.language.xml.XMLLanguage;
import com.itsaky.androidide.language.xml.lexer.XMLLexer;
import com.itsaky.androidide.models.AndroidProject;
@@ -18,13 +18,15 @@ import com.itsaky.androidide.models.ConstantsBridge;
import com.itsaky.androidide.syntax.colorschemes.SchemeWombat;
import com.itsaky.androidide.tasks.TaskExecutor;
import com.itsaky.androidide.tasks.callables.ReadFileTask;
import com.itsaky.androidide.utils.FileUtil;
import com.itsaky.androidide.utils.PreferenceManager;
import com.itsaky.androidide.utils.TypefaceUtils;
import com.itsaky.androidide.utils.VersionedFileManager;
import com.itsaky.lsp.Diagnostic;
import com.itsaky.lsp.DidChangeTextDocumentParams;
import com.itsaky.lsp.DidSaveTextDocumentParams;
import com.itsaky.lsp.Range;
import com.itsaky.lsp.TextDocumentContentChangeEvent;
import com.itsaky.lsp.TextDocumentIdentifier;
import com.itsaky.lsp.VersionedTextDocumentIdentifier;
import io.github.rosemoe.editor.interfaces.EditorEventListener;
import io.github.rosemoe.editor.langs.EmptyLanguage;
@@ -34,7 +36,9 @@ import io.github.rosemoe.editor.widget.schemes.SchemeVS2019;
import java.io.File;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token;
@@ -48,6 +52,7 @@ public class EditorFragment extends BaseFragment implements EditorEventListener,
private CodeEditor.CursorChangeListener listener;
private FileOpenListener openListener;
private JLSRequestor jlsRequestor;
private static AndroidProject project;
public static final String KEY_FILE_PATH = "file_path";
@@ -65,6 +70,11 @@ public class EditorFragment extends BaseFragment implements EditorEventListener,
return this;
}
public EditorFragment setJLSRequestor(JLSRequestor requestor) {
this.jlsRequestor = requestor;
return this;
}
public static EditorFragment newInstance(File file, AndroidProject project) {
Bundle bundle = new Bundle();
bundle.putString(KEY_FILE_PATH, file.getAbsolutePath());
@@ -124,8 +134,14 @@ public class EditorFragment extends BaseFragment implements EditorEventListener,
}
public void setDiagnostics(List<Diagnostic> diags) {
if(binding.editorCodeEditor != null) {
binding.editorCodeEditor.setDiagnostics(diags);
if(binding.editorCodeEditor != null && diags != null) {
Map<Range, Diagnostic> map = new HashMap<>();
for(int i=0;i<diags.size();i++) {
final Diagnostic d = diags.get(i);
if(d == null) continue;
map.put(d.range, d);
}
binding.editorCodeEditor.setDiagnostics(map);
}
}
@@ -186,10 +202,12 @@ public class EditorFragment extends BaseFragment implements EditorEventListener,
}
public void save() {
FileUtil.writeFile(mFile.getAbsolutePath(), binding.editorCodeEditor.getText().toString());
final String text = binding.editorCodeEditor.getText().toString();
final boolean wrote = FileIOUtils.writeFileFromString(mFile, text);
notifySaved(wrote, text);
isModified = false;
}
public File getFile() {
return mFile;
}
@@ -275,50 +293,19 @@ public class EditorFragment extends BaseFragment implements EditorEventListener,
@Override
public void onNewTextSet(CodeEditor editor) {
isModified = true;
notifyChanged();
}
@Override
public void afterDelete(CodeEditor editor, CharSequence content, int startLine, int startColumn, int endLine, int endColumn, CharSequence deletedContent) {
isModified = true;
JavaLanguageServer server = StudioApp.getInstance().getJavaLanguageServer();
if(server != null) {
try {
TextDocumentContentChangeEvent event = new TextDocumentContentChangeEvent();
VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier();
DidChangeTextDocumentParams p = new DidChangeTextDocumentParams();
List<TextDocumentContentChangeEvent> changes = new ArrayList<>();
event.text = getText();
event.range = null;
changes.add(event);
id.uri = getFile().toURI();
id.version = VersionedFileManager.incrementVersion(getFile());
p.textDocument = id;
p.contentChanges = changes;
server.didChange(p);
} catch (Throwable th) {}
}
notifyChanged();
}
@Override
public void afterInsert(CodeEditor editor, CharSequence content, int startLine, int startColumn, int endLine, int endColumn, CharSequence insertedContent) {
isModified = true;
JavaLanguageServer server = StudioApp.getInstance().getJavaLanguageServer();
if(server != null) {
try {
TextDocumentContentChangeEvent event = new TextDocumentContentChangeEvent();
VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier();
DidChangeTextDocumentParams p = new DidChangeTextDocumentParams();
List<TextDocumentContentChangeEvent> changes = new ArrayList<>();
event.text = getText();
event.range = null;
changes.add(event);
id.uri = getFile().toURI();
id.version = VersionedFileManager.incrementVersion(getFile());
p.textDocument = id;
p.contentChanges = changes;
server.didChange(p);
} catch (Throwable th) {}
}
notifyChanged();
boolean isOpen = false;
try {
@@ -333,6 +320,36 @@ public class EditorFragment extends BaseFragment implements EditorEventListener,
public void beforeReplace(CodeEditor editor, CharSequence content) {
}
private void notifyChanged() {
if (getFile() != null && getFile().getName().endsWith(EXT_JAVA) && jlsRequestor != null) {
try {
TextDocumentContentChangeEvent event = new TextDocumentContentChangeEvent();
VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier();
DidChangeTextDocumentParams p = new DidChangeTextDocumentParams();
List<TextDocumentContentChangeEvent> changes = new ArrayList<>();
event.text = getText();
event.range = null;
changes.add(event);
id.uri = getFile().toURI();
id.version = VersionedFileManager.incrementVersion(getFile());
p.textDocument = id;
p.contentChanges = changes;
jlsRequestor.didChange(p);
} catch (Throwable th) {}
}
}
private void notifySaved(boolean wrote, String text) {
if (wrote && jlsRequestor != null) {
TextDocumentIdentifier id = new TextDocumentIdentifier();
id.uri = mFile.toURI();
DidSaveTextDocumentParams p = new DidSaveTextDocumentParams();
p.text = text;
p.textDocument = id;
jlsRequestor.didSave(p);
}
}
public static interface FileOpenListener {
public void onOpenSuccessful(File file, String text);
}
@@ -0,0 +1,13 @@
package com.itsaky.androidide.interfaces;
import com.itsaky.lsp.DidChangeTextDocumentParams;
import com.itsaky.lsp.DidCloseTextDocumentParams;
import com.itsaky.lsp.DidOpenTextDocumentParams;
import com.itsaky.lsp.DidSaveTextDocumentParams;
public interface JLSRequestor {
void didOpen(DidOpenTextDocumentParams params);
void didClose(DidCloseTextDocumentParams params);
void didChange(DidChangeTextDocumentParams params);
void didSave(DidSaveTextDocumentParams params);
}
@@ -16,7 +16,9 @@ import com.itsaky.lsp.CancelParams;
import com.itsaky.lsp.CompletionList;
import com.itsaky.lsp.DidChangeConfigurationParams;
import com.itsaky.lsp.DidChangeTextDocumentParams;
import com.itsaky.lsp.DidCloseTextDocumentParams;
import com.itsaky.lsp.DidOpenTextDocumentParams;
import com.itsaky.lsp.DidSaveTextDocumentParams;
import com.itsaky.lsp.InitializeParams;
import com.itsaky.lsp.JavaReportProgressParams;
import com.itsaky.lsp.JavaStartProgressParams;
@@ -128,6 +130,11 @@ public class JavaLanguageServer implements ShellServer.Callback {
DidChangeConfigurationParams p2 = new DidChangeConfigurationParams();
p2.settings = settings;
configurationChanged(p2);
ThreadUtils.runOnUiThread(() -> {
if(client != null)
client.onServerStarted(UNIVERSION_ID);
});
}
public void initialize(InitializeParams p) {
@@ -146,10 +153,18 @@ public class JavaLanguageServer implements ShellServer.Callback {
write(Method.DID_OPEN, gson.toJson(p));
}
public void didClose(DidCloseTextDocumentParams p) {
write(Method.DID_CLOSE, gson.toJson(p));
}
public void didChange(DidChangeTextDocumentParams p) {
write(Method.DID_CHANGE, gson.toJson(p));
}
public void didSave(DidSaveTextDocumentParams p) {
write(Method.DID_SAVE, gson.toJson(p));
}
public Pair<Integer, CompletionList> completion(TextDocumentPositionParams p) {
int id = write(Method.COMPLETION, gson.toJson(p));
completionRequests.put(id, id);
@@ -166,19 +181,15 @@ public class JavaLanguageServer implements ShellServer.Callback {
int id = write(Method.CANCEL_REQUEST, gson.toJson(p));
}
private int write(String method, String data) {
public int send(Message msg) {
final int id = UNIVERSION_ID++;
Message msg = new Message();
msg.id = id;
msg.jsonrpc = "2.0";
msg.method = method;
msg.params = new JsonParser().parse(data);
if(this.send != null) {
final String body = gson.toJson(msg);
final String head = String.format("Content-Length: %d\r\n\r\n", body.length());
new Thread(() -> {
try {
logger.i(body);
send.write(head.concat(body).getBytes(StandardCharsets.UTF_8));
} catch (Throwable e) {
logger.e("Error writing to server: " + ThrowableUtils.getFullStackTrace(e));
@@ -189,6 +200,14 @@ public class JavaLanguageServer implements ShellServer.Callback {
return id;
}
private int write(String method, String data) {
Message msg = new Message();
msg.jsonrpc = "2.0";
msg.method = method;
msg.params = new JsonParser().parse(data);
return send(msg);
}
private void logEx(Throwable th) {
logger.e(ThrowableUtils.getFullStackTrace(th));
}
@@ -204,6 +223,7 @@ public class JavaLanguageServer implements ShellServer.Callback {
private void onServerOut(String line) {
if(line == null || line.trim().length() <= 0) return;
if(line.contains(CONTENT_LENGTH)) line = line.substring(0, line.lastIndexOf(CONTENT_LENGTH));
logger.v(line);
if(this.client == null) return;
try {
JsonObject obj = new JsonParser().parse(line.trim()).getAsJsonObject();
@@ -233,7 +253,7 @@ public class JavaLanguageServer implements ShellServer.Callback {
}
}
private class Method {
public static class Method {
public static final String INITIALIZE = "initialize";
public static final String INITIALIZED = "initialized";
public static final String SHUTDOWN = "shutdown";
@@ -270,7 +290,7 @@ public class JavaLanguageServer implements ShellServer.Callback {
public static final String JAVA_COLORS = "java/colors";
}
private class Key {
public static class Key {
public static final String METHOD = "method";
public static final String PARAMS = "params";
public static final String ID = "id";
@@ -58,9 +58,11 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.core.content.ContextCompat;
import com.itsaky.androidide.R;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.utils.Logger;
import com.itsaky.lsp.Diagnostic;
import com.itsaky.lsp.DiagnosticSeverity;
import com.itsaky.lsp.Position;
import com.itsaky.lsp.Range;
import io.github.rosemoe.editor.interfaces.EditorEventListener;
@@ -88,6 +90,9 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.itsaky.lsp.DiagnosticSeverity;
/**
* CodeEditor is a editor that can highlight text regions by doing basic syntax analyzing
@@ -251,8 +256,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
private CharPosition mLockedSelection;
KeyMetaStates mKeyMetaStates = new KeyMetaStates(this);
private HashMap<HexColor, Integer> mLineColors;
private HashMap<Range, List<Diagnostic>> mDiagnostics;
private Map<HexColor, Integer> mLineColors;
private Map<Range, Diagnostic> mDiagnostics;
private File currentFile;
@@ -524,54 +529,66 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
return mPinLineNumber;
}
public void showOrHideDiagnosticWindow() {
private Diagnostic findDiagnosticInCursorRange() {
if(mDiagnostics == null)
return null;
int leftLine = getCursor().getLeftLine();
int leftCol = getCursor().getLeftColumn();
int rightLine = getCursor().getRightLine();
int rightCol = getCursor().getRightColumn();
final Range thisRange = new Range(new Position(leftLine, leftCol), new Position(rightLine, rightCol));
boolean hide = false;
boolean has = mDiagnostics.containsKey(thisRange);
if(has) {
List<Diagnostic> diags = mDiagnostics.get(thisRange);
if(diags != null && diags.size() > 0) {
Diagnostic d = diags.get(0);
mDiagnosticWindow.setDiagnostic(d);
if(!mDiagnosticWindow.isShowing())
mDiagnosticWindow.show();
else updateDiagnosticWindowPosition();
} else hide = true;
} else hide = true;
final Range range = new 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.character <= range.start.character
&& r.end.line >= range.end.line
&& r.end.character >= range.end.character) {
found = r;
break;
}
}
if(found != null) {
return mDiagnostics.get(found);
}
return null;
}
private List<Diagnostic> findDiagnosticsContainingLine(int line) {
final List<Diagnostic> diags = new ArrayList<>();
final Set<Range> keys = mDiagnostics.keySet();
for(Range r : keys) {
if(r == null) continue;
if(r.start.line <= line && line <= r.end.line)
diags.add(mDiagnostics.get(r));
}
return diags;
}
public void showOrHideDiagnosticWindow() {
if(mDiagnostics == null) return;
boolean hide = true;
Diagnostic d = findDiagnosticInCursorRange();
if(d != null) {
hide = false;
mDiagnosticWindow.setDiagnostic(d);
if(!mDiagnosticWindow.isShowing())
mDiagnosticWindow.show();
else updateDiagnosticWindowPosition();
}
if(hide && mDiagnosticWindow.isShowing()) {
mDiagnosticWindow.hide();
}
}
public void setDiagnostics(List<Diagnostic> diags) {
this.mDiagnostics = new HashMap<>();
mapDiagnostics(diags);
}
private void mapDiagnostics(List<Diagnostic> diags) {
public void setDiagnostics(Map<Range, Diagnostic> diags) {
if(diags == null)
return;
for(int i=0;i<diags.size();i++) {
Diagnostic d = diags.get(i);
if(d == null)
continue;
if(mDiagnostics.containsKey(d.range)) {
List<Diagnostic> ds = mDiagnostics.get(d.range);
if(ds == null)
ds = new ArrayList<>();
if(!ds.contains(d))
ds.add(d);
mDiagnostics.put(d.range, ds);
} else {
List<Diagnostic> ds = new ArrayList<>();
ds.add(d);
mDiagnostics.put(d.range, ds);
}
}
diags = new HashMap<>();
this.mDiagnostics = diags;
}
public CodeEditor setLineColors(HashMap<HexColor, Integer> map) {
@@ -1318,30 +1335,27 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
}
}
// Draw errors and warnings
int leftLine = getCursor().getLeftLine();
int leftCol = getCursor().getLeftColumn();
int rightLine = getCursor().getRightLine();
int rightCol = getCursor().getRightColumn();
final Range thisRange = new Range(new Position(leftLine, leftCol), new Position(rightLine, rightCol));
if(mDiagnostics != null && mDiagnostics.containsKey(thisRange)) {
List<Diagnostic> diags = mDiagnostics.get(thisRange);
if(diags != null && diags.size() > 0) {
for(int i=0;i<diags.size();i++) {
Diagnostic d = diags.get(i);
if(d == null) continue;
final int paintStart = Math.max(firstVisibleChar, d.range.start.character);
final int paintEnd = Math.min(lastVisibleChar, d.range.end.character);
final float width = measureText(mBuffer, paintStart, paintEnd - paintStart);
final RectF r = new RectF();
r.bottom = getRowBottom(line) - getOffsetY() - mDpUnit * 1;
r.top = r.bottom - getRowHeight() * 0.2f;
r.left = paintingOffset + measureText(mBuffer, firstVisibleChar, paintStart - firstVisibleChar);
r.right = r.left + width;
mPaint.setColor(getDiagnosticIconId(d));
drawZigzagUnderline(r, canvas, mPaint);
}
}
// Draw diagnostics
final List<Diagnostic> diags = findDiagnosticsContainingLine(line);
if(diags.size() > 0) {
for(int i=0;i<diags.size();i++) {
Diagnostic d = diags.get(i);
if(d == null) continue;
int startCol = d.range.start.character;
int endCol = d.range.end.character;
if(line > d.range.start.line) startCol = 0;
if(line > d.range.end.line) endCol = 0;
final int paintStart = Math.max(firstVisibleChar, startCol);
final int paintEnd = Math.min(lastVisibleChar, endCol);
final float width = measureText(mBuffer, paintStart, paintEnd - paintStart);
final RectF r = new RectF();
r.bottom = getRowBottom(line) - getOffsetY() - mDpUnit * 1;
r.top = r.bottom - getRowHeight() * 0.2f;
r.left = paintingOffset + measureText(mBuffer, firstVisibleChar, paintStart - firstVisibleChar);
r.right = r.left + width;
mPaint.setColor(diagnosticColor(d.severity));
drawZigzagUnderline(r, canvas, mPaint);
}
}
// Draw cursors
@@ -1452,10 +1466,19 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
}
}
private int getDiagnosticIconId(Diagnostic diagnostic) {
if(diagnostic.severity == DiagnosticSeverity.Error)
return R.drawable.ic_compilation_error;
return R.drawable.ic_info;
private int diagnosticColor(int severity) {
int id = R.color.diagnostic_warning;
switch(id) {
case DiagnosticSeverity.Error:
id = R.color.diagnostic_error;
break;
default:
id = R.color.diagnostic_warning;
break;
}
final Context ctx = StudioApp.getInstance();
return ContextCompat.getColor(ctx, id);
}
protected void showTextActionPopup() {
@@ -46,10 +46,10 @@ public class DiagnosticWindow extends EditorBasePopupWindow {
binding.errorPosition.setText(
editor.getContext().getString(
com.itsaky.androidide.R.string.diagnostic_position,
""+ diagnostic.range.start.line,
""+ diagnostic.range.start.character,
""+ diagnostic.range.end.line,
""+ diagnostic.range.end.character
""+ diagnostic.range.start.line + 1,
""+ diagnostic.range.start.character + 1,
""+ diagnostic.range.end.line + 1,
""+ diagnostic.range.end.character + 1
)
);
@@ -74,8 +74,10 @@ public class DiagnosticWindow extends EditorBasePopupWindow {
return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_error);
} else if(diagnostic.severity == DiagnosticSeverity.Warning) {
return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_warning);
} else if(diagnostic.severity == DiagnosticSeverity.Hint) {
return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_hint);
}
return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_note);
return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_info);
}
}
+2
View File
@@ -35,6 +35,8 @@
<color name="log_normal">@color/primaryTextColor</color>
<color name="diagnostic_error">@color/secondaryDarkColor</color>
<color name="diagnostic_warning">@color/log_warning</color>
<color name="diagnostic_info">@color/log_info</color>
<color name="diagnostic_hint">@color/primaryTextColor</color>
<color name="content_background">#212121</color>
<color name="bg_green">#4CAF50</color>
<color name="transform_scrim">#70000000</color>
+2 -1
View File
@@ -3,7 +3,8 @@
<string name="diagnostic_position">Start: %s, %s End: %s, %s</string>
<string name="diagnostic_error">ERROR</string>
<string name="diagnostic_warning">WARNING</string>
<string name="diagnostic_note">NOTE</string>
<string name="diagnostic_hint">INFO</string>
<string name="diagnostic_info">HINT</string>
<string name="msg_project_deleted">Project deleted</string>
<string name="msg_project_delete_failed">Failed to delete project!</string>
<string name="msg_failed_load_icon_path">Failed to load icon path!</string>
@@ -1,5 +1,13 @@
package com.itsaky.lsp;
import android.R;
import android.content.Context;
public class DiagnosticSeverity {
public static final int Error = 1, Warning = 2, Information = 3, Hint = 4;
public static final int
Error = 1,
Warning = 2,
Information = 3,
Hint = 4;
}
@@ -10,4 +10,6 @@ public interface LanguageClient {
void showMessage(ShowMessageParams params);
void registerCapability(String method, JsonElement options);
void customNotification(String method, JsonElement params);
void onServerStarted(int currentId);
}