diff --git a/app/src/main/assets/data/jls.zip b/app/src/main/assets/data/jls.zip index 006675914..60716288e 100644 Binary files a/app/src/main/assets/data/jls.zip and b/app/src/main/assets/data/jls.zip differ diff --git a/app/src/main/java/com/itsaky/androidide/EditorActivity.java b/app/src/main/java/com/itsaky/androidide/EditorActivity.java index 0851ee417..6e719e234 100644 --- a/app/src/main/java/com/itsaky/androidide/EditorActivity.java +++ b/app/src/main/java/com/itsaky/androidide/EditorActivity.java @@ -92,6 +92,7 @@ import me.piruin.quickaction.QuickAction; import com.itsaky.lsp.DidSaveTextDocumentParams; import com.itsaky.lsp.DidChangeTextDocumentParams; import com.itsaky.lsp.TextDocumentIdentifier; +import com.itsaky.lsp.JavaColors; public class EditorActivity extends StudioActivity implements FileTreeFragment.FileActionListener, IDEService.BuildListener, @@ -705,6 +706,13 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F } } + @Override + public void javaColors(JavaColors colors) { + final File file = new File(colors.uri); + final EditorFragment editor = mPagerAdapter.findEditorByFile(file); + editor.setJavaColors(colors); + } + @Override public void showMessage(ShowMessageParams params) { } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java index c9d3ac050..92126485a 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java @@ -11,6 +11,7 @@ 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.JavaLanguageAnalyzer; import com.itsaky.androidide.language.xml.XMLLanguage; import com.itsaky.androidide.language.xml.lexer.XMLLexer; import com.itsaky.androidide.models.AndroidProject; @@ -24,6 +25,7 @@ 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.JavaColors; import com.itsaky.lsp.Range; import com.itsaky.lsp.TextDocumentContentChangeEvent; import com.itsaky.lsp.TextDocumentIdentifier; @@ -55,6 +57,8 @@ public class EditorFragment extends BaseFragment implements EditorEventListener, private JLSRequestor jlsRequestor; private static AndroidProject project; + private JavaLanguage mJavaLanguage; + public static final String KEY_FILE_PATH = "file_path"; public static final String KEY_PROJECT = "project"; public static final String EXT_JAVA = ".java"; @@ -75,6 +79,14 @@ public class EditorFragment extends BaseFragment implements EditorEventListener, return this; } + public void setJavaColors(JavaColors colors) { + if(mJavaLanguage != null) { + JavaLanguageAnalyzer analyzer = (JavaLanguageAnalyzer) mJavaLanguage.getAnalyzer(); + analyzer.setJavaColors(colors); + binding.editorCodeEditor.notifySpansChanged(); + } + } + public static EditorFragment newInstance(File file, AndroidProject project) { Bundle bundle = new Bundle(); bundle.putString(KEY_FILE_PATH, file.getAbsolutePath()); @@ -221,7 +233,7 @@ public class EditorFragment extends BaseFragment implements EditorEventListener, private void postRead() { binding.editorCodeEditor.setFile(getFile()); if (mFile.isFile() && mFile.getName().endsWith(EXT_JAVA)) { - binding.editorCodeEditor.setEditorLanguage(new JavaLanguage(project)); + binding.editorCodeEditor.setEditorLanguage(mJavaLanguage = new JavaLanguage(project)); binding.editorCodeEditor.setColorScheme(new SchemeVS2019()); } else if (mFile.isFile() && mFile.getName().endsWith(EXT_XML)) { binding.editorCodeEditor.setEditorLanguage(new XMLLanguage()); diff --git a/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguageAnalyzer.java b/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguageAnalyzer.java index e6ee269aa..9acae5c3d 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguageAnalyzer.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguageAnalyzer.java @@ -1,21 +1,22 @@ package com.itsaky.androidide.language.java; -import com.itsaky.androidide.app.StudioApp; -import com.itsaky.androidide.syntax.lexer.Lexer; import com.itsaky.androidide.syntax.lexer.impls.java.JavaLexerImpl; -import com.itsaky.androidide.syntax.lexer.tokens.Token; +import com.itsaky.lsp.Range; import io.github.rosemoe.editor.interfaces.CodeAnalyzer; import io.github.rosemoe.editor.text.TextAnalyzeResult; import io.github.rosemoe.editor.text.TextAnalyzer; -import java.util.ArrayList; -import com.blankj.utilcode.util.ThrowableUtils; +import java.util.List; +import com.itsaky.lsp.JavaColors; public class JavaLanguageAnalyzer implements CodeAnalyzer { + private JavaColors colors; + @Override public void analyze(CharSequence content, TextAnalyzeResult colors, TextAnalyzer.AnalyzeThread.Delegate delegate) { try { JavaLexerImpl lexer = new JavaLexerImpl(content.toString(), colors); + lexer.setJavaColors(this.colors); lexer.init(); // Adding spans, hex colors, block lines and all is handled by JavaLexerImpl @@ -33,4 +34,8 @@ public class JavaLanguageAnalyzer implements CodeAnalyzer { } catch (Throwable e) { } } + + public void setJavaColors(JavaColors colors) { + this.colors = colors; + } } diff --git a/app/src/main/java/com/itsaky/androidide/language/java/server/JavaLanguageServer.java b/app/src/main/java/com/itsaky/androidide/language/java/server/JavaLanguageServer.java index 3450db959..55493d2d4 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/server/JavaLanguageServer.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/server/JavaLanguageServer.java @@ -7,27 +7,36 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.gson.reflect.TypeToken; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.models.AndroidProject; import com.itsaky.androidide.shell.ShellServer; import com.itsaky.androidide.utils.Environment; +import com.itsaky.androidide.utils.FileUtil; import com.itsaky.androidide.utils.Logger; import com.itsaky.lsp.CancelParams; +import com.itsaky.lsp.CodeActionParams; +import com.itsaky.lsp.CompletionItem; 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.DocumentFormattingParams; import com.itsaky.lsp.InitializeParams; +import com.itsaky.lsp.JavaColors; 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.TextDocumentPositionParams; +import com.itsaky.lsp.TextEdit; +import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; @@ -45,10 +54,13 @@ public class JavaLanguageServer implements ShellServer.Callback { private LanguageClient client; private Socket server; - private OutputStream send; + private BufferedOutputStream send; private final ConcurrentHashMap completionRequests = new ConcurrentHashMap<>(); - private final BlockingQueue responseQueue = new ArrayBlockingQueue(1); + private final ConcurrentHashMap formattingRequests = new ConcurrentHashMap<>(); + + private final BlockingQueue completionResponseQueue = new ArrayBlockingQueue<>(20); + private final BlockingQueue> formattingResponseQueue = new ArrayBlockingQueue<>(20); private static final String START_COMMAND = "java " + "--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED " + @@ -87,10 +99,12 @@ public class JavaLanguageServer implements ShellServer.Callback { String line; while((line = reader.readLine()) != null) { final String temp = line; + if(temp == null || temp.trim().length() <= 0) continue; ThreadUtils.runOnUiThread(() -> { onServerOut(temp); }); } + logger.i("Socket disconnected"); } catch (Throwable e) { logEx(e); } @@ -103,13 +117,13 @@ public class JavaLanguageServer implements ShellServer.Callback { } private Socket connectAndGet() { - int tryCount = 0; while(server == null || send == null || !server.isConnected()) { try { server = new Socket(SOCKET_HOST, SOCKET_PORT); - send = server.getOutputStream(); + OutputStream o = server.getOutputStream(); + if(o != null) + send = new BufferedOutputStream(o); } catch (IOException e) { - tryCount++; try { Thread.sleep(100); } catch (InterruptedException e2) {} @@ -175,11 +189,26 @@ public class JavaLanguageServer implements ShellServer.Callback { completionRequests.put(id, id); CompletionList result = null; try { - result = responseQueue.take(); + result = completionResponseQueue.take(); } catch (InterruptedException e) {} return Pair.create(id, result); } + public void resolveCompletion(CompletionItem item) { + write(Method.RESOLVE_COMPLETION, gson.toJson(item)); + } + + public List formatting(DocumentFormattingParams params) { + int id = write(Method.FORMATTING, gson.toJson(params)); + formattingRequests.put(id, id); + List edits = null; + return edits; + } + + public void codeActions(CodeActionParams p) { + write(Method.CODE_ACTION, gson.toJson(p)); + } + public void cancelRequest(int requestId) { CancelParams p = new CancelParams(); p.id = requestId; @@ -190,12 +219,13 @@ public class JavaLanguageServer implements ShellServer.Callback { final int id = UNIVERSION_ID++; msg.id = id; if(this.send != null) { - final String body = gson.toJson(msg); - final String head = String.format("Content-Length: %d\r\n\r\n", body.length()); + // Message must end with a \n so that server can read it correctly + final String body = gson.toJson(msg) + "\n"; + final byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8); new Thread(() -> { try { - logger.i(body); - send.write(head.concat(body).getBytes(StandardCharsets.UTF_8)); + send.write(bodyBytes); + send.flush(); } catch (Throwable e) { logger.e("Error writing to server: " + ThrowableUtils.getFullStackTrace(e)); } @@ -219,18 +249,31 @@ public class JavaLanguageServer implements ShellServer.Callback { @Override public void output(CharSequence charSequence) { - logger.d(charSequence); + writeOut(charSequence.toString()); + } + + private BufferedOutputStream os; + + private void writeOut(String data) { + try { + if(os == null) { + os = new BufferedOutputStream(new FileOutputStream(new File(FileUtil.getExternalStorageDir(), "ide_xlog/out.txt"), true)); + } + os.write(data.getBytes()); + os.flush(); + } catch (Throwable th) { + logger.e(ThrowableUtils.getFullStackTrace(th)); + } } /** * This method is called on UI Thread, do not perform time consuming or networking tasks */ 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 { + logger.v(line); JsonObject obj = new JsonParser().parse(line.trim()).getAsJsonObject(); if(obj.has(Key.METHOD)) { final String method = obj.get(Key.METHOD).getAsString(); @@ -240,21 +283,29 @@ public class JavaLanguageServer implements ShellServer.Callback { } else if(method.equals(Method.JLS_PROGRESS_START)) { JavaStartProgressParams params = gson.fromJson(obj.get(Key.PARAMS).toString(), JavaStartProgressParams.class); client.javaProgressStart(params); - } if(method.equals(Method.JLS_PROGRESS_REPORT)) { + } else if(method.equals(Method.JLS_PROGRESS_REPORT)) { JavaReportProgressParams params = gson.fromJson(obj.get(Key.PARAMS).toString(), JavaReportProgressParams.class); client.javaProgressReport(params); - } if(method.equals(Method.JLS_PROGRESS_END)) { + } else if(method.equals(Method.JLS_PROGRESS_END)) { client.javaProgressEnd(); + } else if(method.equals(Method.JAVA_COLORS)) { + JavaColors colors = gson.fromJson(obj.get(Key.PARAMS).toString(), JavaColors.class); + client.javaColors(colors); } } else if(obj.has(Key.ID)) { int id = obj.get(Key.ID).getAsInt(); if(completionRequests.containsKey(id)) { final CompletionList list = gson.fromJson(obj.get(Key.RESULT).getAsJsonObject(), CompletionList.class); - responseQueue.put(list); + completionResponseQueue.put(list); + } else if(formattingRequests.containsKey(id)) { + final List list = gson.fromJson(obj.get(Key.RESULT).getAsJsonObject(), TypeToken.getParameterized(List.class, TextEdit.class).getType()); + formattingResponseQueue.put(list); } + } else { + } } catch (Throwable th) { - logger.e("onServerOut", ThrowableUtils.getFullStackTrace(th)); + logger.e("onServerOut", line, ThrowableUtils.getFullStackTrace(th)); } } @@ -283,6 +334,8 @@ public class JavaLanguageServer implements ShellServer.Callback { public static final String PREPARE_RENAME = "textDocument/prepareRename"; public static final String RENAME = "textDocument/rename"; + public static final String FORMATTING = "textDocument/formatting"; + public static final String CODE_ACTION = "textDocument/codeAction"; public static final String CANCEL_REQUEST = "$/cancelRequest"; @@ -300,5 +353,8 @@ public class JavaLanguageServer implements ShellServer.Callback { public static final String PARAMS = "params"; public static final String ID = "id"; public static final String RESULT = "result"; + + public static final String STATICS = "statics"; + public static final String FIELDS = "fields"; } } diff --git a/app/src/main/java/com/itsaky/androidide/models/CompletionItemWrapper.java b/app/src/main/java/com/itsaky/androidide/models/CompletionItemWrapper.java index 422fb6044..9a55b381e 100644 --- a/app/src/main/java/com/itsaky/androidide/models/CompletionItemWrapper.java +++ b/app/src/main/java/com/itsaky/androidide/models/CompletionItemWrapper.java @@ -4,9 +4,11 @@ import com.blankj.utilcode.util.ThrowableUtils; import com.itsaky.androidide.utils.Logger; import com.itsaky.lsp.CompletionItem; import com.itsaky.lsp.CompletionItemKind; -import io.github.rosemoe.editor.text.CharPosition; +import com.itsaky.lsp.Position; +import com.itsaky.lsp.TextEdit; import io.github.rosemoe.editor.text.Cursor; import io.github.rosemoe.editor.widget.CodeEditor; +import java.util.List; public class CompletionItemWrapper implements SuggestItem, Comparable { @@ -62,6 +64,22 @@ public class CompletionItemWrapper implements SuggestItem, Comparable { if(shiftLeft && !atEnd) editor.moveSelectionLeft(); + + final List edits = item.additionalTextEdits; + if(edits != null && edits.size() > 0) { + for(int i=0;i 2 /*if length() == 2 then only [] is available*/ && m.find(); - Logger.instance().i("line -> " + line + "\nlogTag find() -> " + find); if(find) { String date = ""; String time = ""; @@ -83,10 +82,8 @@ public class LogReceiver extends BroadcastReceiver { } else { log = new LogLine("", "", "", "", "", "", line); } - - Logger.instance().i("log = " + log + "\nlogListener = " + listener); + if(log != null && listener != null) { - Logger.instance().i("listener.appendLogLine(...) log ->\n" + log); listener.appendLogLine(log); logTag = null; } diff --git a/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java b/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java index 35198072f..c74175fdc 100644 --- a/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java @@ -1,25 +1,25 @@ package com.itsaky.androidide.syntax.lexer.impls.java; -import android.graphics.Color; import com.itsaky.androidide.language.java.parser.JavaLexer; import com.itsaky.androidide.syntax.lexer.Lexer; import com.itsaky.androidide.syntax.lexer.impls.BaseJavaLexer; import com.itsaky.androidide.syntax.lexer.tokens.Token; import com.itsaky.androidide.syntax.lexer.tokens.TokenType; +import com.itsaky.lsp.Range; import io.github.rosemoe.editor.struct.BlockLine; -import io.github.rosemoe.editor.struct.HexColor; -import io.github.rosemoe.editor.text.CharPosition; import io.github.rosemoe.editor.text.TextAnalyzeResult; import io.github.rosemoe.editor.widget.EditorColorScheme; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.List; import org.antlr.v4.runtime.CharStreams; +import com.itsaky.lsp.JavaColors; public class JavaLexerImpl extends BaseJavaLexer implements Lexer { + private JavaColors javaColors; + public JavaLexerImpl(String content, TextAnalyzeResult colors) { this.content = content; this.colors = colors; @@ -41,7 +41,11 @@ public class JavaLexerImpl extends BaseJavaLexer implements Lexer { builtinTypes.add(JavaLexer.LONG); builtinTypes.add(JavaLexer.SHORT); } - + + public void setJavaColors(JavaColors colors) { + this.javaColors = colors; + } + @Override public void init() throws IOException { lexer = new JavaLexer(CharStreams.fromReader(new StringReader(content))); @@ -60,6 +64,7 @@ public class JavaLexerImpl extends BaseJavaLexer implements Lexer { @Override public int line() { // currentToken's line index starts from 1 + // so we reduce it by 1 int line = currentToken.getLine() - 1; this.lastLine = line; return line; @@ -238,6 +243,21 @@ public class JavaLexerImpl extends BaseJavaLexer implements Lexer { wasClassName = false; break; } + + if(isClassName(line, column)) { + colors.addIfNeeded(line, column, EditorColorScheme.IDENTIFIER_NAME); + break; + } + + if(isFieldOrStatic(line, column)) { + colors.addIfNeeded(line, column, EditorColorScheme.FIELD); + break; + } + + if(isPackageName(line, column)) { + colors.addIfNeeded(line, column, EditorColorScheme.PACKAGE_NAME); + break; + } colors.addIfNeeded(line, column, EditorColorScheme.TEXT_NORMAL); break; @@ -286,4 +306,66 @@ public class JavaLexerImpl extends BaseJavaLexer implements Lexer { return type; } + + private boolean isPackageName(int line, int column) { + if(javaColors == null || javaColors.packages == null) return false; + final List packages = javaColors.packages; + if(packages != null && packages.size() > 0) { + for(int i=0;i names = javaColors.classNames; + if(names != null && names.size() > 0) { + for(int i=0;i fields = javaColors.fields; + if(fields != null && fields.size() > 0) { + for(int i=0;i statics = javaColors.statics; + if(statics != null && statics.size() > 0) { + for(int i=0;i statics; + + @SerializedName("fields") + public List fields; + + @SerializedName("classNames") + public List classNames; + + @SerializedName("packages") + public List packages; +} diff --git a/lsp/src/main/java/com/itsaky/lsp/LanguageClient.java b/lsp/src/main/java/com/itsaky/lsp/LanguageClient.java index 499d5d8f7..e5efa73bd 100644 --- a/lsp/src/main/java/com/itsaky/lsp/LanguageClient.java +++ b/lsp/src/main/java/com/itsaky/lsp/LanguageClient.java @@ -6,10 +6,14 @@ public interface LanguageClient { void javaProgressStart(JavaStartProgressParams params); void javaProgressReport(JavaReportProgressParams params); void javaProgressEnd(); + void publishDiagnostics(PublishDiagnosticsParams params); + void javaColors(JavaColors colors); + void showMessage(ShowMessageParams params); - void registerCapability(String method, JsonElement options); void customNotification(String method, JsonElement params); + void registerCapability(String method, JsonElement options); + void onServerStarted(int currentId); } diff --git a/lsp/src/main/java/com/itsaky/lsp/Position.java b/lsp/src/main/java/com/itsaky/lsp/Position.java index 1b91e909f..aa35b2dee 100644 --- a/lsp/src/main/java/com/itsaky/lsp/Position.java +++ b/lsp/src/main/java/com/itsaky/lsp/Position.java @@ -1,5 +1,7 @@ package com.itsaky.lsp; +import com.google.gson.GsonBuilder; + public class Position { public int line, character; @@ -12,7 +14,7 @@ public class Position { @Override public String toString() { - return line + "," + character; + return new GsonBuilder().setPrettyPrinting().create().toJson(this); } @Override diff --git a/lsp/src/main/java/com/itsaky/lsp/Range.java b/lsp/src/main/java/com/itsaky/lsp/Range.java index 96d587287..293bec80f 100644 --- a/lsp/src/main/java/com/itsaky/lsp/Range.java +++ b/lsp/src/main/java/com/itsaky/lsp/Range.java @@ -1,5 +1,7 @@ package com.itsaky.lsp; +import com.google.gson.GsonBuilder; + public class Range { public Position start, end; @@ -12,7 +14,7 @@ public class Range { @Override public String toString() { - return start + "-" + end; + return new GsonBuilder().setPrettyPrinting().create().toJson(this); } @Override diff --git a/lsp/src/main/java/com/itsaky/lsp/TextEdit.java b/lsp/src/main/java/com/itsaky/lsp/TextEdit.java index bf6eed66a..0ed549cd8 100644 --- a/lsp/src/main/java/com/itsaky/lsp/TextEdit.java +++ b/lsp/src/main/java/com/itsaky/lsp/TextEdit.java @@ -1,4 +1,5 @@ package com.itsaky.lsp; +import com.google.gson.GsonBuilder; public class TextEdit { public Range range; @@ -13,7 +14,7 @@ public class TextEdit { @Override public String toString() { - return range + "/" + newText; + return new GsonBuilder().setPrettyPrinting().create().toJson(this); } public static final TextEdit NONE = new TextEdit(Range.NONE, "");