-Enhanced implementation of JLS

This commit is contained in:
itsaky
2021-08-03 15:16:00 +05:30
parent e954411903
commit 929258284d
16 changed files with 272 additions and 41 deletions
Binary file not shown.
@@ -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) {
}
@@ -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());
@@ -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;
}
}
@@ -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<Integer, Integer> completionRequests = new ConcurrentHashMap<>();
private final BlockingQueue<CompletionList> responseQueue = new ArrayBlockingQueue<CompletionList>(1);
private final ConcurrentHashMap<Integer, Integer> formattingRequests = new ConcurrentHashMap<>();
private final BlockingQueue<CompletionList> completionResponseQueue = new ArrayBlockingQueue<>(20);
private final BlockingQueue<List<TextEdit>> 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<TextEdit> formatting(DocumentFormattingParams params) {
int id = write(Method.FORMATTING, gson.toJson(params));
formattingRequests.put(id, id);
List<TextEdit> 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<TextEdit> 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";
}
}
@@ -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<TextEdit> edits = item.additionalTextEdits;
if(edits != null && edits.size() > 0) {
for(int i=0;i<edits.size();i++) {
final TextEdit edit = edits.get(i);
if(edit == null || edit.range == null || edit.newText == null) continue;
final Position start = edit.range.start;
final Position end = edit.range.end;
if(start == null || end == null) continue;
if(start.equals(end)) {
editor.getText().insert(start.line, start.character, edit.newText);
} else {
editor.getText().replace(start.line, start.character, end.line, end.character, edit.newText);
}
}
}
} catch (Throwable th) {
Logger.instance("CompletionItemWrapper").e("onSelectThis, Error: ", ThrowableUtils.getFullStackTrace(th));
}
@@ -37,7 +37,6 @@ public class LogReceiver extends BroadcastReceiver {
Matcher m = LOG_HEAD_PATTERN.matcher(line);
LogLine log = null;
final boolean find = line != null && line.trim().length() > 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;
}
@@ -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<Range> packages = javaColors.packages;
if(packages != null && packages.size() > 0) {
for(int i=0;i<packages.size();i++) {
final Range range = packages.get(i);
if(range == null) continue;
if(range.start.line == line && range.start.character == column)
return true;
}
}
return false;
}
private boolean isClassName(int line, int column) {
if(javaColors == null || javaColors.classNames == null) return false;
final List<Range> names = javaColors.classNames;
if(names != null && names.size() > 0) {
for(int i=0;i<names.size();i++) {
final Range range = names.get(i);
if(range == null) continue;
if(range.start.line == line && range.start.character == column)
return true;
}
}
return false;
}
private boolean isFieldOrStatic(int line, int column) {
return isField(line, column) || isStatic(line, column);
}
private boolean isField(int line, int column) {
if(javaColors == null || javaColors.fields == null) return false;
final List<Range> fields = javaColors.fields;
if(fields != null && fields.size() > 0) {
for(int i=0;i<fields.size();i++) {
final Range range = fields.get(i);
if(range == null) continue;
if(range.start.line == line && range.start.character == column)
return true;
}
}
return false;
}
private boolean isStatic(int line, int column) {
if(javaColors == null || javaColors.statics == null) return false;
final List<Range> statics = javaColors.statics;
if(statics != null && statics.size() > 0) {
for(int i=0;i<statics.size();i++) {
final Range range = statics.get(i);
if(range == null) continue;
if(range.start.line == line && range.start.character == column)
return true;
}
}
return false;
}
}
@@ -61,7 +61,6 @@ 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.Position;
import com.itsaky.lsp.Range;
@@ -603,6 +602,12 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
}
return this;
}
public void notifySpansChanged() {
if(mSpanner != null) {
mSpanner.analyze(getText());
}
}
/**
* Define symbol pairs for any language,
@@ -59,6 +59,9 @@ public class EditorColorScheme {
//-----------------Highlight colors-----------
public static final int FIELD = 35;
public static final int STATIC_FIELD = 36;
public static final int PACKAGE_NAME = 37;
public static final int ANNOTATION = 28;
public static final int FUNCTION_NAME = 27;
public static final int IDENTIFIER_NAME = 26;
@@ -107,7 +110,7 @@ public class EditorColorScheme {
/**
* Max pre-defined color id
*/
protected static final int END_COLOR_ID = 34;
protected static final int END_COLOR_ID = 37;
/**
* Real color saver
*/
@@ -116,7 +119,7 @@ public class EditorColorScheme {
* Host editor object
*/
private CodeEditor mEditor;
/**
* Create a new ColorScheme for the given editor
*
@@ -267,6 +270,13 @@ public class EditorColorScheme {
case LOG_INFO :
color = 0xff4caf50;
break;
case FIELD :
case STATIC_FIELD :
color = 0xFFF0BE4B;
break;
case PACKAGE_NAME :
color = 0xffF0BE4B;
break;
default:
throw new IllegalArgumentException("Unexpected type:" + type);
}
@@ -3,4 +3,11 @@ package com.itsaky.lsp;
public class FormattingOptions {
public int tabSize;
public boolean insertSpaces;
public FormattingOptions(){}
public FormattingOptions(int tabSize, boolean insertSpaces) {
this.tabSize = tabSize;
this.insertSpaces = insertSpaces;
}
}
@@ -0,0 +1,22 @@
package com.itsaky.lsp;
import com.google.gson.annotations.SerializedName;
import java.net.URI;
import java.util.List;
public class JavaColors {
@SerializedName("uri")
public URI uri;
@SerializedName("statics")
public List<Range> statics;
@SerializedName("fields")
public List<Range> fields;
@SerializedName("classNames")
public List<Range> classNames;
@SerializedName("packages")
public List<Range> packages;
}
@@ -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);
}
@@ -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
+3 -1
View File
@@ -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
@@ -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, "");