From e1dff86090b2d7ac658ca47ac35c6c98acebc4a6 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Wed, 19 Jan 2022 16:10:43 +0530 Subject: [PATCH] Implement semantic highlighting for java sources --- .../language/groovy/GroovyAnalyzer.java | 2 +- .../language/java/HighlightRangeHelper.java | 216 ---------- .../language/java/JavaAnalyzer.java | 380 +++++++++++++++++- .../language/logs/LogLanguageImpl.java | 10 +- .../androidide/language/xml/XMLAnalyzer.java | 5 +- .../androidide/lsp/IDELanguageClientImpl.java | 1 - .../com/itsaky/androidide/models/LogLine.java | 2 +- .../syntax/colorschemes/SchemeAndroidIDE.java | 2 +- .../syntax/colorschemes/SchemeWombat.java | 2 +- .../syntax/highlighters/JavaHighlighter.java | 2 +- .../utils/HighlightRangeHelper.java | 137 +++++++ .../editor/interfaces/CodeAnalyzer.java | 7 +- .../editor/langs/AbstractCodeAnalyzer.java | 3 - .../rosemoe/editor/langs/EmptyLanguage.java | 3 - .../io/github/rosemoe/editor/struct/Span.java | 4 +- .../{widget => syntax}/EditorColorScheme.java | 100 ++--- .../rosemoe/editor/text/SpanMapUpdater.java | 2 +- .../editor/text/TextAnalyzeResult.java | 2 +- .../rosemoe/editor/widget/CodeEditor.java | 6 +- .../widget/EditorAutoCompleteWindow.java | 1 + .../editor/widget/LineBreakLayout.java | 3 + .../editor/widget/schemes/SchemeDarcula.java | 55 --- .../editor/widget/schemes/SchemeEclipse.java | 51 --- .../editor/widget/schemes/SchemeGitHub.java | 46 --- .../widget/schemes/SchemeNotepadXX.java | 51 --- .../editor/widget/schemes/SchemeVS2019.java | 55 --- .../com/itsaky/lsp/models/CodeAnalysis.kt | 2 +- .../com/itsaky/lsp/models/HighlightToken.kt | 59 +++ .../java/com/itsaky/lsp/models/Locations.kt | 20 +- .../itsaky/lsp/models/SemanticHighlights.kt | 45 --- .../com/itsaky/lsp/java/parser/Parser.java | 2 +- .../java/providers/CodeActionProvider.java | 2 +- .../lsp/java/providers/JavaCodeAnalyzer.java | 1 + .../providers/SemanticHighlightProvider.java | 70 ++++ .../java/visitors/SemanticHighlighter.java | 232 +++++++++++ 35 files changed, 931 insertions(+), 650 deletions(-) delete mode 100644 app/src/main/java/com/itsaky/androidide/language/java/HighlightRangeHelper.java create mode 100644 app/src/main/java/com/itsaky/androidide/utils/HighlightRangeHelper.java rename app/src/main/java/io/github/rosemoe/editor/{widget => syntax}/EditorColorScheme.java (78%) mode change 100755 => 100644 delete mode 100755 app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeDarcula.java delete mode 100755 app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeEclipse.java delete mode 100755 app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeGitHub.java delete mode 100755 app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeNotepadXX.java delete mode 100755 app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeVS2019.java create mode 100644 lsp/api/src/main/java/com/itsaky/lsp/models/HighlightToken.kt delete mode 100644 lsp/api/src/main/java/com/itsaky/lsp/models/SemanticHighlights.kt create mode 100644 lsp/java/src/main/java/com/itsaky/lsp/java/providers/SemanticHighlightProvider.java create mode 100644 lsp/java/src/main/java/com/itsaky/lsp/java/visitors/SemanticHighlighter.java diff --git a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java index b70ee6ef7..0648b4cb9 100755 --- a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java +++ b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java @@ -35,7 +35,7 @@ import io.github.rosemoe.editor.struct.BlockLine; import io.github.rosemoe.editor.text.Content; import io.github.rosemoe.editor.text.TextAnalyzeResult; import io.github.rosemoe.editor.text.TextAnalyzer; -import io.github.rosemoe.editor.widget.EditorColorScheme; +import io.github.rosemoe.editor.syntax.EditorColorScheme; public class GroovyAnalyzer extends io.github.rosemoe.editor.langs.AbstractCodeAnalyzer { diff --git a/app/src/main/java/com/itsaky/androidide/language/java/HighlightRangeHelper.java b/app/src/main/java/com/itsaky/androidide/language/java/HighlightRangeHelper.java deleted file mode 100644 index 3bac57c45..000000000 --- a/app/src/main/java/com/itsaky/androidide/language/java/HighlightRangeHelper.java +++ /dev/null @@ -1,216 +0,0 @@ -/************************************************************************************ - * This file is part of AndroidIDE. - * - * AndroidIDE is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AndroidIDE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AndroidIDE. If not, see . - * - **************************************************************************************/ -package com.itsaky.androidide.language.java; - -import androidx.annotation.NonNull; - -import com.itsaky.androidide.utils.LSPUtils; -import com.itsaky.lsp.models.Range; -import com.itsaky.lsp.models.SemanticHighlight; - -import java.util.Comparator; -import java.util.List; - -public class HighlightRangeHelper { - - private final SemanticHighlight highlights; - - public static final int NOT_FOUND = -29291; // Some random integer - - public HighlightRangeHelper (SemanticHighlight highlights) { - this.highlights = highlights; - } - - public void sort () { - sortHighlights (LSPUtils.RANGE_START_COMPARATOR); - } - - public boolean isEnumType (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getEnumTypes (), line, column); - } - - public boolean isAnnotationType (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getAnnotationTypes (), line, column); - } - - public boolean isInterface (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getInterfaces (), line, column); - } - - public boolean isEnum (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getEnums (), line, column); - } - - public boolean isParameter (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getParameters (), line, column); - } - - public boolean isExceptionParam (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getExceptionParams (), line, column); - } - - public boolean isConstructor (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getConstructors (), line, column); - } - - public boolean isStaticInit (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getStaticInits (), line, column); - } - - public boolean isInstanceInit (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getInstanceInits (), line, column); - } - - public boolean isTypeParam (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getTypeParams (), line, column); - } - - public boolean isResourceVariable (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getResourceVariables (), line, column); - } - - public boolean isPackageName (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getPackages (), line, column); - } - - public boolean isClassName (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getClassNames (), line, column); - } - - public boolean isField (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getFields (), line, column); - } - - public boolean isStaticField (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getStatics (), line, column); - } - - public boolean isMethodDeclaration (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getMethodDeclarations (), line, column); - } - - public boolean isMethodInvocation (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getMethodInvocations (), line, column); - } - - public boolean isLocal (int line, int column) { - if (highlights == null) { - return false; - } - return isInRange (highlights.getLocals (), line, column); - } - - public boolean isInRange (List ranges, int line, int column) { - if (ranges != null && ranges.size () > 0) { - for (int i = 0; i < ranges.size (); i++) { - final Range range = ranges.get (i); - if (range == null) { - continue; - } - if (range.getStart ().getLine () == line && range.getStart ().getColumn () == column) { - return true; - } - } - } - return false; - } - - public void sortHighlights (Comparator comparator) { - final SemanticHighlight highlights = this.highlights; - - sortAll (comparator, - - // Semantic highlights - highlights.getPackages (), - highlights.getEnumTypes (), - highlights.getClassNames (), - highlights.getAnnotationTypes (), - highlights.getInterfaces (), - highlights.getEnums (), - highlights.getStatics (), - highlights.getFields (), - highlights.getParameters (), - highlights.getLocals (), - highlights.getExceptionParams (), - highlights.getMethodDeclarations (), - highlights.getMethodInvocations (), - highlights.getConstructors (), - highlights.getStaticInits (), - highlights.getInstanceInits (), - highlights.getTypeParams (), - highlights.getResourceVariables ()); - } - - @SafeVarargs - private void sortAll (Comparator comparator, @NonNull List... lists) { - for (List list : lists) { - list.sort (comparator); - } - } -} diff --git a/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java b/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java index 71fc8c499..acc5913e6 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java @@ -19,29 +19,39 @@ package com.itsaky.androidide.language.java; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.itsaky.androidide.lexers.java.JavaLexer; +import com.itsaky.androidide.models.ConstantsBridge; +import com.itsaky.androidide.utils.HighlightRangeHelper; +import com.itsaky.androidide.utils.LSPUtils; import com.itsaky.androidide.utils.Logger; import com.itsaky.lsp.api.ILanguageServer; import com.itsaky.lsp.models.AnalyzeParams; -import com.itsaky.lsp.models.AnalyzeResult; import com.itsaky.lsp.models.DiagnosticItem; import com.itsaky.lsp.models.Position; import com.itsaky.lsp.models.Range; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Token; + import java.io.File; +import java.io.StringReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Stack; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import io.github.rosemoe.editor.langs.AbstractCodeAnalyzer; +import io.github.rosemoe.editor.struct.BlockLine; +import io.github.rosemoe.editor.syntax.EditorColorScheme; +import io.github.rosemoe.editor.text.CharPosition; import io.github.rosemoe.editor.text.Content; import io.github.rosemoe.editor.text.TextAnalyzeResult; import io.github.rosemoe.editor.text.TextAnalyzer; -// TODO request language server for semantic highlights instead of waiting for it to send - /** * Code analyzer for the Java Language * @@ -51,7 +61,8 @@ public class JavaAnalyzer extends AbstractCodeAnalyzer { private final Map> stringMap = new HashMap<> (); private List diagnostics = new ArrayList<> (); - private CompletableFuture future; + + private HighlightRangeHelper helper; @Override public void analyze (@Nullable ILanguageServer languageServer, @@ -60,23 +71,360 @@ public class JavaAnalyzer extends AbstractCodeAnalyzer { @NonNull TextAnalyzeResult colors, @NonNull TextAnalyzer.AnalyzeThread.Delegate delegate ) throws Exception { - colors.addNormalIfNull (); - if (languageServer == null || file == null) { - return; + if (languageServer != null && file != null) { + CompletableFuture.runAsync (() -> { + final var analyzer = languageServer.getCodeAnalyzer (); + final var result = analyzer.analyze (new AnalyzeParams (file.toPath ())); + + JavaAnalyzer.this.diagnostics = result.getDiagnostics (); + JavaAnalyzer.this.helper = new HighlightRangeHelper (result.getSemanticHighlights ()); + }); } - if (future != null && !future.isDone ()) { - future.cancel (true); + final var stream = CharStreams.fromReader (new StringReader (content.toString ())); + final var lexer = new JavaLexer (stream); + final var stack = new Stack (); + + Token token; + int line, column, lastLine = 1, type, currSwitch = 0, maxSwitch = 0; + boolean isFirst = true; + + this.stringMap.clear (); + + while (delegate.shouldAnalyze ()) { + token = lexer.nextToken (); + + if (token == null) { + break; + } + + type = token.getType (); + line = token.getLine () - 1; + column = token.getCharPositionInLine (); + + if (type == JavaLexer.EOF) { + lastLine = line; + break; + } + + switch (type) { + case JavaLexer.WS: + if (isFirst) { + colors.addNormalIfNull (); + } + break; + case JavaLexer.ABSTRACT: + case JavaLexer.ASSERT: + case JavaLexer.BREAK: + case JavaLexer.CASE: + case JavaLexer.CATCH: + case JavaLexer.CLASS: + case JavaLexer.CONST: + case JavaLexer.CONTINUE: + case JavaLexer.DEFAULT: + case JavaLexer.DO: + case JavaLexer.ELSE: + case JavaLexer.EXTENDS: + case JavaLexer.FINAL: + case JavaLexer.FINALLY: + case JavaLexer.FOR: + case JavaLexer.IF: + case JavaLexer.GOTO: + case JavaLexer.IMPLEMENTS: + case JavaLexer.IMPORT: + case JavaLexer.INSTANCEOF: + case JavaLexer.INTERFACE: + case JavaLexer.NATIVE: + case JavaLexer.NEW: + case JavaLexer.PACKAGE: + case JavaLexer.PRIVATE: + case JavaLexer.PROTECTED: + case JavaLexer.PUBLIC: + case JavaLexer.RETURN: + case JavaLexer.STATIC: + case JavaLexer.STRICTFP: + case JavaLexer.SUPER: + case JavaLexer.SWITCH: + case JavaLexer.SYNCHRONIZED: + case JavaLexer.THIS: + case JavaLexer.THROW: + case JavaLexer.THROWS: + case JavaLexer.TRANSIENT: + case JavaLexer.TRY: + case JavaLexer.VOID: + case JavaLexer.VOLATILE: + case JavaLexer.WHILE: + case JavaLexer.VAR: + colors.addIfNeeded (line, column, EditorColorScheme.KEYWORD); + break; + case JavaLexer.DECIMAL_LITERAL: + case JavaLexer.HEX_LITERAL: + case JavaLexer.OCT_LITERAL: + case JavaLexer.BINARY_LITERAL: + case JavaLexer.FLOAT_LITERAL: + case JavaLexer.HEX_FLOAT_LITERAL: + case JavaLexer.BOOL_LITERAL: + case JavaLexer.CHAR_LITERAL: + case JavaLexer.NULL_LITERAL: + colors.addIfNeeded (line, column, EditorColorScheme.LITERAL); + break; + case JavaLexer.STRING_LITERAL: + checkAndAddHexString (token, EditorColorScheme.LITERAL, colors); + + Position start = new Position (line, column); + Position end = new Position (line, column + token.getText ().length ()); + colors.addStringRange (line, new Range (start, end)); + + { + List ranges = stringMap.get (line); + if (ranges == null) { + ranges = new ArrayList<> (); + } + ranges.add (new Range (start, end)); + stringMap.put (line, ranges); + } + break; + case JavaLexer.LPAREN: + case JavaLexer.RPAREN: + case JavaLexer.LBRACK: + case JavaLexer.RBRACK: + case JavaLexer.SEMI: + case JavaLexer.COMMA: + case JavaLexer.ASSIGN: + case JavaLexer.GT: + case JavaLexer.LT: + case JavaLexer.BANG: + case JavaLexer.TILDE: + case JavaLexer.QUESTION: + case JavaLexer.COLON: + case JavaLexer.EQUAL: + case JavaLexer.GE: + case JavaLexer.LE: + case JavaLexer.NOTEQUAL: + case JavaLexer.AND: + case JavaLexer.OR: + case JavaLexer.INC: + case JavaLexer.DEC: + case JavaLexer.ADD: + case JavaLexer.SUB: + case JavaLexer.MUL: + case JavaLexer.DIV: + case JavaLexer.BITAND: + case JavaLexer.BITOR: + case JavaLexer.CARET: + case JavaLexer.MOD: + case JavaLexer.ADD_ASSIGN: + case JavaLexer.SUB_ASSIGN: + case JavaLexer.MUL_ASSIGN: + case JavaLexer.DIV_ASSIGN: + case JavaLexer.AND_ASSIGN: + case JavaLexer.OR_ASSIGN: + case JavaLexer.XOR_ASSIGN: + case JavaLexer.MOD_ASSIGN: + case JavaLexer.LSHIFT_ASSIGN: + case JavaLexer.RSHIFT_ASSIGN: + case JavaLexer.URSHIFT_ASSIGN: + case JavaLexer.ARROW: + case JavaLexer.COLONCOLON: + case JavaLexer.ELLIPSIS: + case JavaLexer.DOT: + colors.addIfNeeded (line, column, EditorColorScheme.OPERATOR); + break; + case JavaLexer.BOOLEAN: + case JavaLexer.BYTE: + case JavaLexer.CHAR: + case JavaLexer.DOUBLE: + case JavaLexer.ENUM: + case JavaLexer.FLOAT: + case JavaLexer.INT: + case JavaLexer.LONG: + case JavaLexer.SHORT: + colors.addIfNeeded (line, column, EditorColorScheme.TYPE_NAME); + break; + case JavaLexer.BLOCK_COMMENT: + case JavaLexer.LINE_COMMENT: + colors.addIfNeeded (line, column, EditorColorScheme.COMMENT); + + final CharPosition a = content.getIndexer ().getCharPosition (token.getStopIndex ()); + final Position s = new Position (line, column); + final Position e = new Position (a.line, a.column); + colors.addCommentRange (line, new Range (s, e)); + + String lineComment = token.getText (); + if (lineComment != null) { + if (lineComment.startsWith ("//")) { + lineComment = lineComment.substring (2); + } + + // Trim leading whitespaces + int startsFrom = 0; + for (int i = 0; i < lineComment.length (); i++) { + if (Character.isWhitespace (lineComment.charAt (i))) { + startsFrom++; + } else { + break; + } + } + + lineComment = lineComment.substring (startsFrom); + + final String text = new String (lineComment.toCharArray ()); + lineComment = lineComment.toLowerCase (Locale.getDefault ()); + if (lineComment.startsWith ("todo ")) { + diagnostics.add (LSPUtils.newInfoDiagnostic (line, + column, + token.getText ().length (), + text, + token.getText ())); + } else if (lineComment.startsWith (ConstantsBridge.CUSTOM_COMMENT_WARNING_TOKEN)) { + diagnostics.add (LSPUtils.newWarningDiagnostic (line, + column, + token.getText ().length (), + text, + token.getText ())); + } + } + + break; + case JavaLexer.AT: + colors.addIfNeeded (line, column, EditorColorScheme.ANNOTATION); + break; + case JavaLexer.IDENTIFIER: + + if (helper != null) { + if (helper.isPackageName (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.PACKAGE_NAME); + break; + } + + if (helper.isEnumType (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.ENUM_TYPE); + break; + } + + if (helper.isClassName (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.TYPE_NAME); + break; + } + + if (helper.isAnnotationType (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.ANNOTATION); + break; + } + + if (helper.isInterface (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.INTERFACE); + break; + } + + if (helper.isEnum (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.ENUM); + break; + } + + if (helper.isStaticField (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.STATIC_FIELD); + break; + } + + if (helper.isField (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.FIELD); + break; + } + + if (helper.isParameter (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.PARAMETER); + break; + } + + if (helper.isLocal (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.LOCAL_VARIABLE); + break; + } + + if (helper.isExceptionParam (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.EXCEPTION_PARAM); + break; + } + + if (helper.isMethodDeclaration (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.METHOD_DECLARATION); + break; + } + + if (helper.isMethodInvocation (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.METHOD_INVOCATION); + break; + } + + if (helper.isConstructor (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.CONSTRUCTOR); + break; + } + + if (helper.isStaticInit (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.STATIC_INIT); + break; + } + + if (helper.isInstanceInit (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.INSTANCE_INIT); + break; + } + + if (helper.isTypeParam (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.TYPE_PARAM); + break; + } + + if (helper.isResourceVariable (line, column)) { + colors.addIfNeeded (line, column, EditorColorScheme.RESOURCE_VARIABLE); + break; + } + } + + colors.addIfNeeded (line, column, EditorColorScheme.TEXT_NORMAL); + break; + case JavaLexer.LBRACE: + colors.addIfNeeded (line, column, EditorColorScheme.OPERATOR); + if (stack.isEmpty ()) { + if (currSwitch > maxSwitch) { + maxSwitch = currSwitch; + } + currSwitch = 0; + } + currSwitch++; + BlockLine block = colors.obtainNewBlock (); + block.startLine = line; + block.startColumn = column; + stack.push (block); + break; + case JavaLexer.RBRACE: + colors.addIfNeeded (line, column, EditorColorScheme.OPERATOR); + if (!stack.isEmpty ()) { + BlockLine block2 = stack.pop (); + block2.endLine = line; + block2.endColumn = column; + if (block2.startLine != block2.endLine) { + colors.addBlockLine (block2); + } + } + break; + default: + colors.addIfNeeded (line, column, EditorColorScheme.TEXT_NORMAL); + break; + } + + isFirst = false; + } + + if (stack.isEmpty () && currSwitch > maxSwitch) { + maxSwitch = currSwitch; } - future = CompletableFuture.supplyAsync (() -> languageServer - .getCodeAnalyzer () - .analyze (new AnalyzeParams (file.toPath ())) - ); - - final var result = future.get (); - this.diagnostics = result.getDiagnostics (); + colors.determine (lastLine); + colors.setSuppressSwitch (maxSwitch + 10); } @Override diff --git a/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java b/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java index 6c9d9845d..936bc9def 100755 --- a/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java +++ b/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java @@ -21,8 +21,10 @@ import com.itsaky.androidide.language.BaseLanguage; import com.itsaky.androidide.models.LogLine; import com.itsaky.lsp.api.ILanguageServer; import com.itsaky.lsp.models.CompletionItem; -import com.itsaky.lsp.models.DiagnosticItem; -import com.itsaky.lsp.models.SemanticHighlight; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; import io.github.rosemoe.editor.interfaces.AutoCompleteProvider; import io.github.rosemoe.editor.interfaces.CodeAnalyzer; @@ -31,10 +33,6 @@ import io.github.rosemoe.editor.text.Content; import io.github.rosemoe.editor.text.TextAnalyzeResult; import io.github.rosemoe.editor.text.TextAnalyzer; import io.github.rosemoe.editor.widget.SymbolPairMatch; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; public class LogLanguageImpl extends BaseLanguage { diff --git a/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java b/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java index 24854da14..b93f24edd 100755 --- a/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java +++ b/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java @@ -25,8 +25,6 @@ import androidx.annotation.NonNull; import com.itsaky.androidide.lexers.xml.XMLLexer; import com.itsaky.androidide.utils.Logger; import com.itsaky.lsp.api.ILanguageServer; -import com.itsaky.lsp.models.DiagnosticItem; -import com.itsaky.lsp.models.SemanticHighlight; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CodePointCharStream; @@ -35,12 +33,11 @@ import org.antlr.v4.runtime.Token; import java.io.File; import java.io.IOException; import java.io.StringReader; -import java.util.Map; import io.github.rosemoe.editor.text.Content; import io.github.rosemoe.editor.text.TextAnalyzeResult; import io.github.rosemoe.editor.text.TextAnalyzer; -import io.github.rosemoe.editor.widget.EditorColorScheme; +import io.github.rosemoe.editor.syntax.EditorColorScheme; public class XMLAnalyzer extends io.github.rosemoe.editor.langs.AbstractCodeAnalyzer { diff --git a/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java b/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java index 8f8e683e3..17c84cfae 100755 --- a/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java +++ b/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java @@ -53,7 +53,6 @@ import com.itsaky.lsp.models.DiagnosticItem; import com.itsaky.lsp.models.DiagnosticResult; import com.itsaky.lsp.models.Location; import com.itsaky.lsp.models.Range; -import com.itsaky.lsp.models.SemanticHighlight; import com.itsaky.lsp.models.SignatureInformation; import com.itsaky.lsp.models.TextEdit; import com.itsaky.toaster.Toaster; diff --git a/app/src/main/java/com/itsaky/androidide/models/LogLine.java b/app/src/main/java/com/itsaky/androidide/models/LogLine.java index 3079ea2b0..22c86b2bc 100755 --- a/app/src/main/java/com/itsaky/androidide/models/LogLine.java +++ b/app/src/main/java/com/itsaky/androidide/models/LogLine.java @@ -23,7 +23,7 @@ import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import com.itsaky.androidide.R; import java.util.Locale; -import io.github.rosemoe.editor.widget.EditorColorScheme; +import io.github.rosemoe.editor.syntax.EditorColorScheme; public class LogLine { diff --git a/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java b/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java index 0b135cc58..21b9aea2d 100755 --- a/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java @@ -19,7 +19,7 @@ **************************************************************************************/ package com.itsaky.androidide.syntax.colorschemes; -import io.github.rosemoe.editor.widget.EditorColorScheme; +import io.github.rosemoe.editor.syntax.EditorColorScheme; public class SchemeAndroidIDE extends EditorColorScheme { diff --git a/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeWombat.java b/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeWombat.java index a1dd1b7e4..ad176b5f9 100755 --- a/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeWombat.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeWombat.java @@ -21,7 +21,7 @@ package com.itsaky.androidide.syntax.colorschemes; -import io.github.rosemoe.editor.widget.EditorColorScheme; +import io.github.rosemoe.editor.syntax.EditorColorScheme; public class SchemeWombat extends EditorColorScheme { diff --git a/app/src/main/java/com/itsaky/androidide/syntax/highlighters/JavaHighlighter.java b/app/src/main/java/com/itsaky/androidide/syntax/highlighters/JavaHighlighter.java index 33123efa6..519a1b4ff 100644 --- a/app/src/main/java/com/itsaky/androidide/syntax/highlighters/JavaHighlighter.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/highlighters/JavaHighlighter.java @@ -24,7 +24,7 @@ import android.text.style.BackgroundColorSpan; import android.text.style.ForegroundColorSpan; import com.itsaky.androidide.lexers.java.JavaLexer; import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE; -import io.github.rosemoe.editor.widget.EditorColorScheme; +import io.github.rosemoe.editor.syntax.EditorColorScheme; import java.io.StringReader; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/app/src/main/java/com/itsaky/androidide/utils/HighlightRangeHelper.java b/app/src/main/java/com/itsaky/androidide/utils/HighlightRangeHelper.java new file mode 100644 index 000000000..93e386903 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/utils/HighlightRangeHelper.java @@ -0,0 +1,137 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.utils; + +import com.itsaky.lsp.models.HighlightToken; +import com.itsaky.lsp.models.HighlightTokenKind; +import com.itsaky.lsp.models.Position; + +import java.util.List; + +import static com.itsaky.lsp.models.HighlightTokenKind.*; + +public class HighlightRangeHelper { + + private final List highlights; + + public HighlightRangeHelper (List highlights) { + this.highlights = highlights; + } + + public boolean isEnumType (int line, int column) { + return isOfKind (line, column, ENUM_TYPE); + } + + public boolean isAnnotationType (int line, int column) { + return isOfKind (line, column, ANNOTATION); + } + + public boolean isInterface (int line, int column) { + return isOfKind (line, column, INTERFACE); + } + + public boolean isEnum (int line, int column) { + return isOfKind (line, column, ENUM); + } + + public boolean isParameter (int line, int column) { + return isOfKind (line, column, PARAMETER); + } + + public boolean isExceptionParam (int line, int column) { + return isOfKind (line, column, EXCEPTION_PARAMETER); + } + + public boolean isConstructor (int line, int column) { + return isOfKind (line, column, CONSTRUCTOR); + } + + public boolean isStaticInit (int line, int column) { + return isOfKind (line, column, STATIC_INIT); + } + + public boolean isInstanceInit (int line, int column) { + return isOfKind (line, column, INSTANCE_INIT); + } + + public boolean isTypeParam (int line, int column) { + return isOfKind (line, column, TYPE_PARAMETER); + } + + public boolean isResourceVariable (int line, int column) { + return isOfKind (line, column, RESOURCE_VARIABLE); + } + + public boolean isPackageName (int line, int column) { + return isOfKind (line, column, PACKAGE_NAME); + } + + public boolean isClassName (int line, int column) { + return isOfKind (line, column, TYPE_NAME); + } + + public boolean isField (int line, int column) { + return isOfKind (line, column, FIELD); + } + + public boolean isStaticField (int line, int column) { + return isOfKind (line, column, STATIC_FIELD); + } + + public boolean isMethodDeclaration (int line, int column) { + return isOfKind (line, column, METHOD_DECLARATION); + } + + public boolean isMethodInvocation (int line, int column) { + return isOfKind (line, column, METHOD_INVOCATION); + } + + public boolean isLocal (int line, int column) { + return isOfKind (line, column, LOCAL_VARIABLE); + } + + /** + * Check if the token at the given index has the given + * token kind. + * + * @param line The line to check. + * @param column The column to check. + * @param kind The kind to check. + * @return {@code true} if the token at the given position has the given kind, {@code false} otherwise. + */ + public boolean isOfKind (int line, int column, HighlightTokenKind kind) { + final var position = new Position (line, column); + int left = 0; int right = this.highlights.size (); int mid; + while (left < right) { + mid = (left + right) / 2; + var token = this.highlights.get (mid); + var range = token.getRange (); + var pos = range.getStart ().compareTo (position); + if (pos < 0) { + left = mid + 1; + } else if (pos > 0) { + right = mid - 1; + } else { + return token.getKind () == kind; + } + } + return false; + } + + private static final Logger LOG = Logger.instance ("HighlightRangeHelper"); +} \ No newline at end of file diff --git a/app/src/main/java/io/github/rosemoe/editor/interfaces/CodeAnalyzer.java b/app/src/main/java/io/github/rosemoe/editor/interfaces/CodeAnalyzer.java index 8229dc17e..c7573d96f 100755 --- a/app/src/main/java/io/github/rosemoe/editor/interfaces/CodeAnalyzer.java +++ b/app/src/main/java/io/github/rosemoe/editor/interfaces/CodeAnalyzer.java @@ -19,14 +19,13 @@ import androidx.annotation.Nullable; import com.itsaky.lsp.api.ILanguageServer; import com.itsaky.lsp.models.DiagnosticItem; -import com.itsaky.lsp.models.SemanticHighlight; + +import java.io.File; +import java.util.List; import io.github.rosemoe.editor.text.Content; import io.github.rosemoe.editor.text.TextAnalyzeResult; import io.github.rosemoe.editor.text.TextAnalyzer; -import java.io.File; -import java.util.List; -import java.util.Map; /** * Interface for analyzing highlight diff --git a/app/src/main/java/io/github/rosemoe/editor/langs/AbstractCodeAnalyzer.java b/app/src/main/java/io/github/rosemoe/editor/langs/AbstractCodeAnalyzer.java index 602d5c0e5..d33ab9d21 100755 --- a/app/src/main/java/io/github/rosemoe/editor/langs/AbstractCodeAnalyzer.java +++ b/app/src/main/java/io/github/rosemoe/editor/langs/AbstractCodeAnalyzer.java @@ -5,14 +5,11 @@ import android.graphics.Color; import androidx.annotation.NonNull; import com.itsaky.lsp.models.DiagnosticItem; -import com.itsaky.lsp.models.SemanticHighlight; import org.antlr.v4.runtime.Token; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.regex.Pattern; import io.github.rosemoe.editor.interfaces.CodeAnalyzer; diff --git a/app/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java b/app/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java index 47582088a..8721ae425 100755 --- a/app/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java +++ b/app/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java @@ -17,13 +17,10 @@ package io.github.rosemoe.editor.langs; import com.itsaky.lsp.api.ILanguageServer; import com.itsaky.lsp.models.CompletionItem; -import com.itsaky.lsp.models.DiagnosticItem; -import com.itsaky.lsp.models.SemanticHighlight; import java.io.File; import java.util.ArrayList; import java.util.List; -import java.util.Map; import io.github.rosemoe.editor.interfaces.AutoCompleteProvider; import io.github.rosemoe.editor.interfaces.CodeAnalyzer; diff --git a/app/src/main/java/io/github/rosemoe/editor/struct/Span.java b/app/src/main/java/io/github/rosemoe/editor/struct/Span.java index 3b6891ddb..c1793ed91 100755 --- a/app/src/main/java/io/github/rosemoe/editor/struct/Span.java +++ b/app/src/main/java/io/github/rosemoe/editor/struct/Span.java @@ -15,14 +15,14 @@ */ package io.github.rosemoe.editor.struct; -import android.graphics.Typeface; - import androidx.annotation.NonNull; import java.util.Collection; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; +import io.github.rosemoe.editor.syntax.EditorColorScheme; + /** * The span model * diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java b/app/src/main/java/io/github/rosemoe/editor/syntax/EditorColorScheme.java old mode 100755 new mode 100644 similarity index 78% rename from app/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java rename to app/src/main/java/io/github/rosemoe/editor/syntax/EditorColorScheme.java index 24c579857..6569e70bb --- a/app/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java +++ b/app/src/main/java/io/github/rosemoe/editor/syntax/EditorColorScheme.java @@ -1,23 +1,24 @@ /* - * Copyright 2020-2021 Rosemoe + * This file is part of AndroidIDE. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * http://www.apache.org/licenses/LICENSE-2.0 + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . */ -package io.github.rosemoe.editor.widget; +package io.github.rosemoe.editor.syntax; import android.util.SparseIntArray; -import io.github.rosemoe.editor.util.Objects; +import java.util.Objects; /** * This class manages the colors of editor. @@ -43,10 +44,6 @@ import io.github.rosemoe.editor.util.Objects; * Note that new colors can be added in newer version of editor, * it is dangerous not to call super.applyDefault(), which can cause * newer editor works wrongly. - *

- * For more pre-defined color schemes, please turn to package io.github.rosemoe.editor.widget.schemes - *

- * Thanks to liyujiang-gzu (GitHub @liyujiang-gzu) for contribution to color schemes * * @author Rose */ @@ -127,16 +124,6 @@ public class EditorColorScheme { */ protected static int COMMENT_DEFAULT = 0xffa8a8a8; - /** - * Start index of Javadoc related color IDs - */ - public static final int JD_SCHEME_START = 56; - - /** - * End index of Javadoc related color IDs - */ - public static final int JD_SCHEME_END = 102; - /** * Min pre-defined color id */ @@ -150,38 +137,24 @@ public class EditorColorScheme { * Real color saver */ protected final SparseIntArray mColors; - /** - * Host editor object - */ - private CodeEditor mEditor; - /** - * Create a new ColorScheme for the given editor - * - * @param editor Host editor - */ - EditorColorScheme (CodeEditor editor) { - mEditor = editor; + private OnColorUpdateListener listener; + + public void setOnColorUpdateListener (OnColorUpdateListener listener) { + this.listener = Objects.requireNonNull (listener); + } + + public EditorColorScheme (OnColorUpdateListener listener) { + this.listener = listener; mColors = new SparseIntArray (); applyDefault (); } /** - * For sub classes + * Subclasses only */ - public EditorColorScheme () { - mColors = new SparseIntArray (); - applyDefault (); - } - - /** - * Called by editor - */ - void attachEditor (CodeEditor editor) { - if (mEditor != null) { - throw new IllegalStateException ("A editor is already attached to this ColorScheme object"); - } - mEditor = Objects.requireNonNull (editor); + protected EditorColorScheme () { + this (type -> {}); } /** @@ -199,12 +172,6 @@ public class EditorColorScheme { * @param type The type */ private void applyDefault (int type) { - - // By default, all javadoc syntax will be treated as comments - if (type >= JD_SCHEME_START && type <= JD_SCHEME_END) { - setColor (type, COMMENT_DEFAULT); // Comment - } - int color; switch (type) { case LINE_DIVIDER: @@ -216,13 +183,6 @@ public class EditorColorScheme { case LINE_NUMBER_BACKGROUND: color = 0xfff0f0f0; break; - case WHOLE_BACKGROUND: - case LINE_NUMBER_PANEL_TEXT: - case AUTO_COMP_PANEL_BG: - case AUTO_COMP_PANEL_CORNER: - case TEXT_SELECTED: - color = 0xffffffff; - break; case OPERATOR: color = 0xFF0066D6; break; @@ -321,9 +281,6 @@ public class EditorColorScheme { case DIAGNOSTIC_INFO: color = 0xff4CAF50; break; - case DIAGNOSTIC_HINT: - color = 0xffffffff; - break; default: color = 0xffffffff; } @@ -347,10 +304,8 @@ public class EditorColorScheme { mColors.put (type, color); - //Notify the editor - if (mEditor != null) { - mEditor.onColorUpdated (type); - } + //Notify the listener + listener.onColorUpdated (type); } /** @@ -363,4 +318,7 @@ public class EditorColorScheme { return mColors.get (type); } + public interface OnColorUpdateListener { + void onColorUpdated (int type); + } } diff --git a/app/src/main/java/io/github/rosemoe/editor/text/SpanMapUpdater.java b/app/src/main/java/io/github/rosemoe/editor/text/SpanMapUpdater.java index 7eec6117c..c23a35a77 100755 --- a/app/src/main/java/io/github/rosemoe/editor/text/SpanMapUpdater.java +++ b/app/src/main/java/io/github/rosemoe/editor/text/SpanMapUpdater.java @@ -16,7 +16,7 @@ package io.github.rosemoe.editor.text; import io.github.rosemoe.editor.struct.Span; -import io.github.rosemoe.editor.widget.EditorColorScheme; +import io.github.rosemoe.editor.syntax.EditorColorScheme; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java b/app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java index d5674dc87..bf3c0b165 100755 --- a/app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java +++ b/app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java @@ -20,7 +20,7 @@ import com.itsaky.lsp.models.Range; import io.github.rosemoe.editor.struct.BlockLine; import io.github.rosemoe.editor.struct.NavigationItem; import io.github.rosemoe.editor.struct.Span; -import io.github.rosemoe.editor.widget.EditorColorScheme; +import io.github.rosemoe.editor.syntax.EditorColorScheme; import java.util.ArrayList; import java.util.HashMap; diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java b/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java index b732a2586..ade9f7d4e 100755 --- a/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java @@ -85,7 +85,6 @@ import com.itsaky.lsp.models.Position; import com.itsaky.lsp.models.Range; import com.itsaky.lsp.models.ReferenceParams; import com.itsaky.lsp.models.ReferenceResult; -import com.itsaky.lsp.models.SemanticHighlight; import com.itsaky.lsp.models.SignatureHelp; import com.itsaky.lsp.models.SignatureHelpParams; import com.itsaky.toaster.Toaster; @@ -104,6 +103,7 @@ import io.github.rosemoe.editor.interfaces.NewlineHandler; import io.github.rosemoe.editor.langs.EmptyLanguage; import io.github.rosemoe.editor.struct.BlockLine; import io.github.rosemoe.editor.struct.Span; +import io.github.rosemoe.editor.syntax.EditorColorScheme; import io.github.rosemoe.editor.text.CharPosition; import io.github.rosemoe.editor.text.Content; import io.github.rosemoe.editor.text.ContentLine; @@ -505,7 +505,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca mStartedActionMode = ACTION_MODE_NONE; setTextSize (DEFAULT_TEXT_SIZE); setLineInfoTextSize (mPaint.getTextSize ()); - mColors = new EditorColorScheme (this); + mColors = new EditorColorScheme (this::onColorUpdated); mEventHandler = new EditorTouchEventHandler (this); mBasicDetector = new GestureDetector (getContext (), mEventHandler); mBasicDetector.setOnDoubleTapListener (mEventHandler); @@ -3803,7 +3803,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca * @param colors A non-null and free EditorColorScheme */ public void setColorScheme (@NonNull EditorColorScheme colors) { - colors.attachEditor (this); + colors.setOnColorUpdateListener (this::onColorUpdated); mColors = colors; if (mCompletionWindow != null) { mCompletionWindow.applyColorScheme (); diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java index d5c025736..9f30fa11a 100755 --- a/app/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java @@ -29,6 +29,7 @@ import com.itsaky.lsp.models.Range; import com.itsaky.lsp.models.TextEdit; import io.github.rosemoe.editor.interfaces.AutoCompleteProvider; +import io.github.rosemoe.editor.syntax.EditorColorScheme; import io.github.rosemoe.editor.text.CharPosition; import io.github.rosemoe.editor.text.Content; import io.github.rosemoe.editor.text.TextAnalyzeResult; diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/LineBreakLayout.java b/app/src/main/java/io/github/rosemoe/editor/widget/LineBreakLayout.java index 2cb6e1fbc..630fb1c90 100755 --- a/app/src/main/java/io/github/rosemoe/editor/widget/LineBreakLayout.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/LineBreakLayout.java @@ -130,6 +130,9 @@ class LineBreakLayout extends AbstractLayout { @Override public float[] getCharLayoutOffset(int line, int column, float[] dest) { + if (text == null) { + return new float[2]; + } if (dest == null || dest.length < 2) { dest = new float[2]; } diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeDarcula.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeDarcula.java deleted file mode 100755 index 92200009a..000000000 --- a/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeDarcula.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2020-2021 Rosemoe - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.github.rosemoe.editor.widget.schemes; - -import io.github.rosemoe.editor.widget.EditorColorScheme; - -/** - * ColorScheme for editor - * picked from Android Studio - * Thanks to liyujiang-gzu (GitHub @liyujiang-gzu) - */ -public class SchemeDarcula extends EditorColorScheme { - - @Override - public void applyDefault() { - super.applyDefault(); - setColor(ANNOTATION, 0xffbbb529); - setColor(METHOD_DECLARATION, 0xffffffff); - setColor(TYPE_NAME, 0xffffffff); - setColor(LOCAL_VARIABLE, 0xff9876aa); - setColor(LITERAL, 0xff6a8759); - setColor(OPERATOR, 0xffffffff); - setColor(COMMENT, 0xff808080); - setColor(KEYWORD, 0xffcc7832); - setColor(WHOLE_BACKGROUND, 0xff2b2b2b); - setColor(TEXT_NORMAL, 0xffffffff); - setColor(LINE_NUMBER_BACKGROUND, 0xff313335); - setColor(LINE_NUMBER, 0xff606366); - setColor(LINE_DIVIDER, 0xff606366); - setColor(SCROLL_BAR_THUMB, 0xffa6a6a6); - setColor(SCROLL_BAR_THUMB_PRESSED, 0xff565656); - setColor(SELECTED_TEXT_BACKGROUND, 0xff3676b8); - setColor(MATCHED_TEXT_BACKGROUND, 0xff32593d); - setColor(CURRENT_LINE, 0xff323232); - setColor(SELECTION_INSERT, 0xffffffff); - setColor(SELECTION_HANDLE, 0xffffffff); - setColor(BLOCK_LINE, 0xff575757); - setColor(BLOCK_LINE_CURRENT, 0xdd575757); - setColor(NON_PRINTABLE_CHAR, 0xffdddddd); - } - -} diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeEclipse.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeEclipse.java deleted file mode 100755 index 544081298..000000000 --- a/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeEclipse.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2020-2021 Rosemoe - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.github.rosemoe.editor.widget.schemes; - -import io.github.rosemoe.editor.widget.EditorColorScheme; - -/** - * ColorScheme for editor - * picked from Eclipse IDE for Java Developers Version 2019-12 (4.14.0) - * Thanks to liyujiang-gzu (GitHub @liyujiang-gzu) - */ -public final class SchemeEclipse extends EditorColorScheme { - - @Override - public void applyDefault() { - super.applyDefault(); - setColor(ANNOTATION, 0xff646464); - setColor(METHOD_DECLARATION, 0xff000000); - setColor(TYPE_NAME, 0xff000000); - setColor(LOCAL_VARIABLE, 0xffb8633e); - setColor(LITERAL, 0xff2a00ff); - setColor(OPERATOR, 0xff3a0000); - setColor(COMMENT, 0xff3f7f5f); - setColor(KEYWORD, 0xff7f0074); - setColor(WHOLE_BACKGROUND, 0xffffffff); - setColor(TEXT_NORMAL, 0xff000000); - setColor(LINE_NUMBER_BACKGROUND, 0xffffffff); - setColor(LINE_NUMBER, 0xff787878); - setColor(SELECTED_TEXT_BACKGROUND, 0xff3399ff); - setColor(MATCHED_TEXT_BACKGROUND, 0xffd4d4d4); - setColor(CURRENT_LINE, 0xffe8f2fe); - setColor(SELECTION_INSERT, 0xff03ebeb); - setColor(SELECTION_HANDLE, 0xff03ebeb); - setColor(BLOCK_LINE, 0xffd8d8d8); - setColor(BLOCK_LINE_CURRENT, 0); - } - -} diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeGitHub.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeGitHub.java deleted file mode 100755 index 8dae33171..000000000 --- a/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeGitHub.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2020-2021 Rosemoe - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.github.rosemoe.editor.widget.schemes; - -import io.github.rosemoe.editor.widget.EditorColorScheme; - -/** - * ColorScheme for editor - * picked from GitHub site - * Thanks to liyujiang-gzu (GitHub @liyujiang-gzu) - */ -public final class SchemeGitHub extends EditorColorScheme { - - @Override - public void applyDefault() { - super.applyDefault(); - setColor(ANNOTATION, 0xff6f42c1); - setColor(METHOD_DECLARATION, 0xff24292e); - setColor(TYPE_NAME, 0xff24292e); - setColor(LOCAL_VARIABLE, 0xff24292e); - setColor(LITERAL, 0xff032f62); - setColor(OPERATOR, 0xff005cc5); - setColor(COMMENT, 0xff6a737d); - setColor(KEYWORD, 0xffde3a49); - setColor(WHOLE_BACKGROUND, 0xffffffff); - setColor(TEXT_NORMAL, 0xff24292e); - setColor(LINE_NUMBER_BACKGROUND, 0xffffffff); - setColor(LINE_NUMBER, 0xffbec0c1); - setColor(SELECTION_INSERT, 0xffc7edcc); - setColor(SELECTION_HANDLE, 0xffc7edcc); - } - -} diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeNotepadXX.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeNotepadXX.java deleted file mode 100755 index 26caad62c..000000000 --- a/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeNotepadXX.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2020-2021 Rosemoe - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.github.rosemoe.editor.widget.schemes; - -import io.github.rosemoe.editor.widget.EditorColorScheme; - -/** - * ColorScheme for editor - * picked from Notepad++ v7.8.1 - * Thanks to liyujiang-gzu (GitHub @liyujiang-gzu) - */ -public final class SchemeNotepadXX extends EditorColorScheme { - - @Override - public void applyDefault() { - super.applyDefault(); - setColor(ANNOTATION, 0xff0000ff); - setColor(METHOD_DECLARATION, 0xff000000); - setColor(TYPE_NAME, 0xff000000); - setColor(LOCAL_VARIABLE, 0xff000000); - setColor(LITERAL, 0xff808080); - setColor(OPERATOR, 0xff0000ff); - setColor(COMMENT, 0xff008000); - setColor(KEYWORD, 0xff8000ff); - setColor(WHOLE_BACKGROUND, 0xffffffff); - setColor(TEXT_NORMAL, 0xff000000); - setColor(LINE_NUMBER_BACKGROUND, 0xffe4e4e4); - setColor(LINE_NUMBER, 0xff808080); - setColor(SELECTED_TEXT_BACKGROUND, 0xff75d975); - setColor(MATCHED_TEXT_BACKGROUND, 0xffc0c0c0); - setColor(CURRENT_LINE, 0xffe8e8ff); - setColor(SELECTION_INSERT, 0xff8000ff); - setColor(SELECTION_HANDLE, 0xff8000ff); - setColor(BLOCK_LINE, 0xffc0c0c0); - setColor(BLOCK_LINE_CURRENT, 0); - } - -} diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeVS2019.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeVS2019.java deleted file mode 100755 index 9ffc0a2b0..000000000 --- a/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeVS2019.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2020-2021 Rosemoe - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.github.rosemoe.editor.widget.schemes; - -import io.github.rosemoe.editor.widget.EditorColorScheme; - -/** - * ColorScheme for editor - * picked from Visual Studio 2019 - * Thanks to liyujiang-gzu (GitHub @liyujiang-gzu) - */ -public class SchemeVS2019 extends EditorColorScheme { - - @Override - public void applyDefault() { - super.applyDefault(); - setColor(ANNOTATION, 0xff4ec9b0); - setColor(METHOD_DECLARATION, 0xff00BCD4); - setColor(TYPE_NAME, 0xff4ec9b0); - setColor(LOCAL_VARIABLE, 0xffdcdcaa); - setColor(LITERAL, 0xffd69d85); - setColor(OPERATOR, 0xffdcdcdc); - setColor(COMMENT, 0xff57a64a); - setColor(KEYWORD, 0xff569cd6); - setColor(WHOLE_BACKGROUND, 0xff1e1e1e); - setColor(TEXT_NORMAL, 0xffdcdcdc); - setColor(LINE_NUMBER_BACKGROUND, 0xff1e1e1e); - setColor(LINE_NUMBER, 0xff2b9eaf); - setColor(LINE_DIVIDER, 0xff2b9eaf); - setColor(SCROLL_BAR_THUMB, 0xff3e3e42); - setColor(SCROLL_BAR_THUMB_PRESSED, 0xff9e9e9e); - setColor(SELECTED_TEXT_BACKGROUND, 0xff3676b8); - setColor(MATCHED_TEXT_BACKGROUND, 0xff653306); - setColor(CURRENT_LINE, 0xff464646); - setColor(SELECTION_INSERT, 0xffffffff); - setColor(SELECTION_HANDLE, 0xffffffff); - setColor(BLOCK_LINE, 0xff717171); - setColor(BLOCK_LINE_CURRENT, 0); - setColor(NON_PRINTABLE_CHAR, 0xffdddddd); - } - -} diff --git a/lsp/api/src/main/java/com/itsaky/lsp/models/CodeAnalysis.kt b/lsp/api/src/main/java/com/itsaky/lsp/models/CodeAnalysis.kt index e4e645caf..5c2422e93 100644 --- a/lsp/api/src/main/java/com/itsaky/lsp/models/CodeAnalysis.kt +++ b/lsp/api/src/main/java/com/itsaky/lsp/models/CodeAnalysis.kt @@ -20,6 +20,6 @@ package com.itsaky.lsp.models import java.nio.file.Path data class AnalyzeParams(var file: Path) -data class AnalyzeResult (var diagnostics: List, var semanticHighlights: List) { +data class AnalyzeResult (var diagnostics: List, var semanticHighlights: List) { constructor() : this (ArrayList(), ArrayList()) } \ No newline at end of file diff --git a/lsp/api/src/main/java/com/itsaky/lsp/models/HighlightToken.kt b/lsp/api/src/main/java/com/itsaky/lsp/models/HighlightToken.kt new file mode 100644 index 000000000..750d509c3 --- /dev/null +++ b/lsp/api/src/main/java/com/itsaky/lsp/models/HighlightToken.kt @@ -0,0 +1,59 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.lsp.models + +/** + * Model class for a syntax highlighting token. + * @author Akash Yadav + */ +data class HighlightToken (var range: Range, var kind: HighlightTokenKind) +enum class HighlightTokenKind { + + // Common + TEXT_NORMAL, + + // Java and Gradle + KEYWORD, + COMMENT, + OPERATOR, + LITERAL, + LOCAL_VARIABLE, + RESOURCE_VARIABLE, + PARAMETER, + EXCEPTION_PARAMETER, + TYPE_PARAMETER, + FIELD, + STATIC_FIELD, + ENUM, + TYPE_NAME, + ENUM_TYPE, + INTERFACE, + METHOD_DECLARATION, + METHOD_INVOCATION, + CONSTRUCTOR, + ANNOTATION, + STATIC_INIT, + INSTANCE_INIT, + PACKAGE_NAME, + + // XML + TAG_NAME, + NAMESPACE, + ATTRIBUTE_NAME, + ATTRIBUTE_VALUE +} \ No newline at end of file diff --git a/lsp/api/src/main/java/com/itsaky/lsp/models/Locations.kt b/lsp/api/src/main/java/com/itsaky/lsp/models/Locations.kt index 56952b205..e5dd274f1 100644 --- a/lsp/api/src/main/java/com/itsaky/lsp/models/Locations.kt +++ b/lsp/api/src/main/java/com/itsaky/lsp/models/Locations.kt @@ -64,10 +64,10 @@ data class Position(var line: Int, var column: Int) : Comparable { open class Range(var start: Position, var end: Position) : Comparable { - constructor() : this (Position(0, 0), Position(0, 0)); + constructor() : this (Position(0, 0), Position(0, 0)) companion object { - @JvmField val NONE = Range (Position.NONE, Position.NONE); + @JvmField val NONE = Range (Position.NONE, Position.NONE) } /** @@ -114,31 +114,35 @@ open class Range(var start: Position, var end: Position) : Comparable { // The position might appear before this range if (position.line < start.line) { - return -1; + return -1 } // The position might appear after this range if (position.line > end.line) { - return 1; + return 1 } // If start and end lines are same, compare by column indexes if (start.line == end.line) { if (position.column < start.column) { - return -1; + return -1 } if (position.column > end.column) { - return 1; + return 1 } } // This range definitely contains the position. - return 0; + return 0 } fun containsLine (line: Int): Boolean { - return start.line <= line && end.line >= line; + return start.line <= line && end.line >= line + } + + override fun toString(): String { + return "Range(start=$start, end=$end)" } } \ No newline at end of file diff --git a/lsp/api/src/main/java/com/itsaky/lsp/models/SemanticHighlights.kt b/lsp/api/src/main/java/com/itsaky/lsp/models/SemanticHighlights.kt deleted file mode 100644 index 937241d0c..000000000 --- a/lsp/api/src/main/java/com/itsaky/lsp/models/SemanticHighlights.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of AndroidIDE. - * - * AndroidIDE is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AndroidIDE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AndroidIDE. If not, see . - */ - -package com.itsaky.lsp.models - -import java.nio.file.Path - -/** - * @author Akash Yadav - */ -data class SemanticHighlightParams(var file: Path) -class SemanticHighlight (var file: Path) { - val packages: List = ArrayList() - val enumTypes: List = ArrayList() - val classNames: List = ArrayList() - val annotationTypes: List = ArrayList() - val interfaces: List = ArrayList() - val enums: List = ArrayList() - val statics: List = ArrayList() - val fields: List = ArrayList() - val parameters: List = ArrayList() - val locals: List = ArrayList() - val exceptionParams: List = ArrayList() - val methodDeclarations: List = ArrayList() - val methodInvocations: List = ArrayList() - val constructors: List = ArrayList() - val staticInits: List = ArrayList() - val instanceInits: List = ArrayList() - val typeParams: List = ArrayList() - val resourceVariables: List = ArrayList() -} \ No newline at end of file diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/parser/Parser.java b/lsp/java/src/main/java/com/itsaky/lsp/java/parser/Parser.java index 9106efdf6..02b6d54f9 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/parser/Parser.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/parser/Parser.java @@ -477,5 +477,5 @@ public class Parser { return ""; } - private static final Logger LOG = Logger.instance ("Parser"); + private static final Logger LOG = Logger.instance ("JavaParser"); } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/providers/CodeActionProvider.java b/lsp/java/src/main/java/com/itsaky/lsp/java/providers/CodeActionProvider.java index 3117ca982..83f983eea 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/providers/CodeActionProvider.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/providers/CodeActionProvider.java @@ -420,5 +420,5 @@ public class CodeActionProvider implements ICodeActionProvider { return Collections.singletonList (action); } - private static final Logger LOG = Logger.instance ("main"); + private static final Logger LOG = Logger.instance ("JavaCodeActionProvider"); } \ No newline at end of file diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/providers/JavaCodeAnalyzer.java b/lsp/java/src/main/java/com/itsaky/lsp/java/providers/JavaCodeAnalyzer.java index acf0e5dfb..b3f683daa 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/providers/JavaCodeAnalyzer.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/providers/JavaCodeAnalyzer.java @@ -53,6 +53,7 @@ public class JavaCodeAnalyzer implements ICodeAnalyzer { } result.setDiagnostics (DiagnosticsProvider.findDiagnostics (task, file)); + result.setSemanticHighlights (SemanticHighlightProvider.highlight (task, file)); return result; }); } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/providers/SemanticHighlightProvider.java b/lsp/java/src/main/java/com/itsaky/lsp/java/providers/SemanticHighlightProvider.java new file mode 100644 index 000000000..b0c85f3b4 --- /dev/null +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/providers/SemanticHighlightProvider.java @@ -0,0 +1,70 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.lsp.java.providers; + +import androidx.annotation.NonNull; + +import com.itsaky.androidide.utils.Logger; +import com.itsaky.lsp.java.compiler.CompileTask; +import com.itsaky.lsp.java.visitors.SemanticHighlighter; +import com.itsaky.lsp.models.HighlightToken; +import com.sun.source.tree.CompilationUnitTree; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * Provides semantic highlight ranges. + * + * @author Akash Yadav + */ +public class SemanticHighlightProvider { + + private static final Comparator SORT_BY_START = Comparator.comparing (HighlightToken::getRange); + + @NonNull + public static List highlight (@NonNull CompileTask task, Path file) { + final List result = new ArrayList<> (); + CompilationUnitTree root = null; + for (CompilationUnitTree tree : task.roots) { + final Path path = Paths.get (tree.getSourceFile ().toUri ()); + if (path.equals (file)) { + root = tree; + break; + } + } + + if (root == null) { + LOG.warn ("Cannot provide semantic highlights. Cannot find compilation unit for the given file."); + return result; + } + + final SemanticHighlighter highlighter = new SemanticHighlighter (task); + highlighter.scan (root, result); + result.sort (SORT_BY_START); + + LOG.debug ("Java semantic highlights:", result); + + return result; + } + + private static final Logger LOG = Logger.instance ("JavaSemanticHighlightProvider"); +} diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/visitors/SemanticHighlighter.java b/lsp/java/src/main/java/com/itsaky/lsp/java/visitors/SemanticHighlighter.java new file mode 100644 index 000000000..453ad417c --- /dev/null +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/visitors/SemanticHighlighter.java @@ -0,0 +1,232 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.lsp.java.visitors; + +import androidx.annotation.NonNull; + +import com.itsaky.androidide.utils.Logger; +import com.itsaky.lsp.java.FileStore; +import com.itsaky.lsp.java.compiler.CompileTask; +import com.itsaky.lsp.models.HighlightToken; +import com.itsaky.lsp.models.HighlightTokenKind; +import com.itsaky.lsp.models.Position; +import com.itsaky.lsp.models.Range; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.LineMap; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.SourcePositions; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; +import com.sun.source.util.Trees; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; + +public class SemanticHighlighter extends TreePathScanner> { + + private final Trees trees; + private final CompileTask task; + + public SemanticHighlighter(CompileTask task) { + this.task = task; + this.trees = Trees.instance(task.task); + } + + private void putSemantics(Name name, List tokens) { + if (name.contentEquals("this") || name.contentEquals("super") || name.contentEquals("class")) { + return; + } + final TreePath fromPath = getCurrentPath(); + + // Cancel the visit in case a task was cancelled/closed + if(task == null || task.task == null) { + throw new RuntimeException("Task is null"); + } + + Element toEl = trees.getElement(fromPath); + if (toEl == null) { + return; + } + + Range range = find(fromPath, name); + if(range == Range.NONE) + return; + + ElementKind kind = toEl.getKind(); + HighlightTokenKind tokenKind = HighlightTokenKind.TEXT_NORMAL; + + switch (kind) { + case PACKAGE: + tokenKind = HighlightTokenKind.PACKAGE_NAME; + break; + case ENUM: + tokenKind = HighlightTokenKind.ENUM_TYPE; + break; + case CLASS: + tokenKind = HighlightTokenKind.TYPE_NAME; + break; + case ANNOTATION_TYPE: + tokenKind = HighlightTokenKind.ANNOTATION; + break; + case INTERFACE: + tokenKind = HighlightTokenKind.INTERFACE; + break; + case ENUM_CONSTANT: + tokenKind = HighlightTokenKind.ENUM; + break; + case FIELD: + if (toEl.getModifiers ().contains (Modifier.STATIC)) { + tokenKind = HighlightTokenKind.STATIC_FIELD; + } else { + tokenKind = HighlightTokenKind.FIELD; + } + break; + case METHOD: + tokenKind = HighlightTokenKind.METHOD_DECLARATION; + break; + case PARAMETER: + tokenKind = HighlightTokenKind.PARAMETER; + break; + case LOCAL_VARIABLE: + tokenKind = HighlightTokenKind.LOCAL_VARIABLE; + break; + case EXCEPTION_PARAMETER: + tokenKind = HighlightTokenKind.EXCEPTION_PARAMETER; + break; + case CONSTRUCTOR: + tokenKind = HighlightTokenKind.CONSTRUCTOR; + break; + case STATIC_INIT: + tokenKind = HighlightTokenKind.STATIC_INIT; + break; + case INSTANCE_INIT: + tokenKind = HighlightTokenKind.INSTANCE_INIT; + break; + case TYPE_PARAMETER: + tokenKind = HighlightTokenKind.TYPE_PARAMETER; + break; + case RESOURCE_VARIABLE: + tokenKind = HighlightTokenKind.RESOURCE_VARIABLE; + break; + } + + tokens.add (new HighlightToken (range, tokenKind)); + } + + @NonNull + private static Position getPosition (long position, @NonNull LineMap lines) { + // decrement the numbers + // to convert 1-based indexes to 0-based + final int line = (int) lines.getLineNumber (position) - 1; + final int column = (int) lines.getColumnNumber (position) - 1; + return new Position (line, column); + } + + private Range find(TreePath path, Name name) { + // Find region containing name + SourcePositions pos = trees.getSourcePositions(); + CompilationUnitTree root = path.getCompilationUnit(); + Tree leaf = path.getLeaf(); + int start = (int) pos.getStartPosition(root, leaf); + int end = (int) pos.getEndPosition(root, leaf); + // Adjust start to remove LHS of declarations and member selections + if (leaf instanceof MemberSelectTree) { + MemberSelectTree select = (MemberSelectTree) leaf; + start = (int) pos.getEndPosition(root, select.getExpression()); + } else if (leaf instanceof VariableTree) { + VariableTree declaration = (VariableTree) leaf; + start = (int) pos.getEndPosition(root, declaration.getType()); + } + // If no position, give up + if (start == -1 || end == -1) { + return Range.NONE; + } + // Find name inside expression + Path file = Paths.get(root.getSourceFile().toUri()); + String contents = FileStore.contents(file); + String region = contents.substring(start, end); + start += region.indexOf(name.toString()); + end = start + name.length(); + return new Range (getPosition (start, root.getLineMap ()), getPosition (end, root.getLineMap ())); + } + + @Override + public Void visitIdentifier(IdentifierTree t, List colors) { + putSemantics(t.getName(), colors); + return super.visitIdentifier(t, colors); + } + + @Override + public Void visitMemberSelect(MemberSelectTree t, List colors) { + putSemantics(t.getIdentifier(), colors); + return super.visitMemberSelect(t, colors); + } + + @Override + public Void visitVariable(VariableTree t, List colors) { + putSemantics(t.getName(), colors); + return super.visitVariable(t, colors); + } + + @Override + public Void visitClass(ClassTree t, List colors) { + putSemantics(t.getSimpleName(), colors); + return super.visitClass (t, colors); + } + + @Override + public Void visitMethodInvocation (MethodInvocationTree tree, List colors) { + Name name = null; + ExpressionTree select = tree.getMethodSelect(); + if(select instanceof MemberSelectTree) { + name = ((MemberSelectTree) select).getIdentifier(); + } else if(select instanceof IdentifierTree) { + name = ((IdentifierTree) select).getName(); + } + + if(name != null) { + Range range = find(getCurrentPath(), name); + if(range != null) { + colors.add(new HighlightToken (range, HighlightTokenKind.METHOD_INVOCATION)); + } + } + return super.visitMethodInvocation(tree, colors); + + } + + @Override + public Void visitMethod(MethodTree tree, List colors) { + putSemantics(tree.getName(), colors); + return super.visitMethod(tree, colors); + } + + private static final Logger LOG = Logger.instance ("JavaSemanticHighlighter"); +} \ No newline at end of file