diff --git a/app/src/main/assets/editor/theme/dark_plus.json b/app/src/main/assets/editor/theme/dark_plus.json index 554b658f..8de4718c 100644 --- a/app/src/main/assets/editor/theme/dark_plus.json +++ b/app/src/main/assets/editor/theme/dark_plus.json @@ -11,7 +11,18 @@ }, "tokenColors": [ { - "scope": [ + "scope": "bracket.matched", + "settings": { + "foreground": "#76ff03" + } + }, + { + "scope": "error", + "settings": { + "foreground": "#f44336" + } + }, + { "scope": [ "meta.embedded", "source.groovy.embedded" ], diff --git a/app/src/main/assets/editor/theme/light_plus.json b/app/src/main/assets/editor/theme/light_plus.json index ab3ac9eb..e2f62835 100644 --- a/app/src/main/assets/editor/theme/light_plus.json +++ b/app/src/main/assets/editor/theme/light_plus.json @@ -7,6 +7,18 @@ "foreground": "#333333" } }, + { + "scope": "bracket.matched", + "settings": { + "foreground": "#76ff03" + } + }, + { + "scope": "error", + "settings": { + "foreground": "#f44336" + } + }, { "scope": [ "meta.embedded", @@ -487,7 +499,6 @@ "editor.background": "#F5F5F5", "editor.foreground": "#000000", "editor.lineHighlightBackground": "#E4F6D4", - "focusBorder": "#A6B39B", "pickerGroup.foreground": "#A6B39B", "pickerGroup.border": "#749351", diff --git a/app/src/main/java/com/stardust/scriptdroid/model/editor/TokenColor.java b/app/src/main/java/com/stardust/scriptdroid/model/editor/TokenColor.java index d50768c7..4b8ce50a 100644 --- a/app/src/main/java/com/stardust/scriptdroid/model/editor/TokenColor.java +++ b/app/src/main/java/com/stardust/scriptdroid/model/editor/TokenColor.java @@ -46,4 +46,12 @@ public class TokenColor { public void setSettings(TokenColorSettings settings) { mSettings = settings; } + + @Override + public String toString() { + return "TokenColor{" + + "scope=" + mScope + + ", settings=" + mSettings.getForeground() + + '}'; + } } diff --git a/app/src/main/java/com/stardust/scriptdroid/model/editor/TokenColorSettings.java b/app/src/main/java/com/stardust/scriptdroid/model/editor/TokenColorSettings.java index dce633a2..47d9f81a 100644 --- a/app/src/main/java/com/stardust/scriptdroid/model/editor/TokenColorSettings.java +++ b/app/src/main/java/com/stardust/scriptdroid/model/editor/TokenColorSettings.java @@ -32,4 +32,6 @@ public class TokenColorSettings { public void setFontStyle(String fontStyle) { mFontStyle = fontStyle; } + + } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor/BracketMatching.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor/BracketMatching.java new file mode 100644 index 00000000..0757d45e --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor/BracketMatching.java @@ -0,0 +1,63 @@ +package com.stardust.scriptdroid.ui.edit.editor; + +/** + * Created by Stardust on 2018/2/25. + */ + +public class BracketMatching { + + public static int UNMATCHED_BRACKET = -2; + public static int BRACKET_NOT_FOUND = -1; + + + private static final char[] PAIR_LEFT = {'(', '{', '['}; + private static final char[] PAIR_RIGHT = {')', '}', ')'}; + + + public static int bracketMatching(CharSequence text, int index) { + char ch = text.charAt(index); + for (int i = 0; i < PAIR_LEFT.length; i++) { + if (PAIR_LEFT[i] == ch) { + return findRightBracket(text, index + 1, PAIR_LEFT[i], PAIR_RIGHT[i]); + } + } + for (int i = 0; i < PAIR_RIGHT.length; i++) { + if (PAIR_RIGHT[i] == ch) { + return findLeftBracket(text, index - 1, PAIR_LEFT[i], PAIR_RIGHT[i]); + } + } + return BRACKET_NOT_FOUND; + } + + public static int findLeftBracket(CharSequence text, int index, char left, char right) { + int rightBracketCount = 0; + for (int i = index; i >= 0; i--) { + char ch = text.charAt(i); + if (ch == left) { + if (rightBracketCount == 0) { + return i; + } + rightBracketCount--; + } else if (ch == right) { + rightBracketCount++; + } + } + return UNMATCHED_BRACKET; + } + + public static int findRightBracket(CharSequence text, int index, char left, char right) { + int leftBracketCount = 0; + for (int i = index; i < text.length(); i++) { + char ch = text.charAt(i); + if (ch == left) { + leftBracketCount++; + } else if (ch == right) { + if (leftBracketCount == 0) { + return i; + } + leftBracketCount--; + } + } + return BRACKET_NOT_FOUND; + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor/CodeEditText.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor/CodeEditText.java index 99362d61..294d9b2b 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor/CodeEditText.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor/CodeEditText.java @@ -22,7 +22,6 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; -import android.speech.tts.TextToSpeech; import android.support.v7.widget.AppCompatEditText; import android.text.Layout; import android.util.AttributeSet; @@ -32,10 +31,12 @@ import android.view.Gravity; import com.stardust.scriptdroid.BuildConfig; import com.stardust.scriptdroid.ui.edit.theme.Theme; +import com.stardust.scriptdroid.ui.edit.theme.TokenMapping; import com.stardust.util.TextUtils; -import java.lang.reflect.Array; -import java.util.Arrays; +import org.mozilla.javascript.Token; + +import static com.stardust.scriptdroid.ui.edit.editor.BracketMatching.UNMATCHED_BRACKET; /** * Created by Administrator on 2018/2/11. @@ -56,6 +57,8 @@ public class CodeEditText extends AppCompatEditText { private TimingLogger mLogger = new TimingLogger(LOG_TAG, "draw"); private Paint mLineHighlightPaint = new Paint(); private int mFirstLineForDraw = -1, mLastLineForDraw; + private int[] mMatchingBrackets = {-1, -1}; + private int mUnmatchedBracket = -1; public CodeEditText(Context context) { super(context); @@ -181,23 +184,31 @@ public class CodeEditText extends AppCompatEditText { int visibleCharStart = getVisibleCharIndex(paint, scrollX, lineStart, lineEnd); int visibleCharEnd = getVisibleCharIndex(paint, scrollX + mParentScrollView.getWidth(), lineStart, lineEnd) + 1; int previousColorPos = visibleCharStart; - int previousColor = mHighlightTokens.getCharColor(previousColorPos); + int previousColor = getCharColor(previousColorPos); if (DEBUG) Log.d(LOG_TAG, "draw line " + line + ": " + (visibleCharEnd - visibleCharStart)); - for (int i = visibleCharStart; i < visibleCharEnd && i < lineEnd; i++) { + int i; + for (i = visibleCharStart; i < visibleCharEnd; i++) { fontCount++; - int color = mHighlightTokens.getCharColor(i); + int color = getCharColor(i); if (previousColor != color) { - drawText(canvas, paint, paddingLeft, lineBaseline, lineStart, previousColorPos, previousColorPos + fontCount, previousColor); + drawText(canvas, paint, paddingLeft, lineBaseline, lineStart, previousColorPos, i, previousColor); previousColor = color; previousColorPos = i; fontCount = 1; } - if (i == visibleCharEnd - 1) { - drawText(canvas, paint, paddingLeft, lineBaseline, lineStart, previousColorPos, previousColorPos + fontCount, previousColor); - } } + drawText(canvas, paint, paddingLeft, lineBaseline, lineStart, previousColorPos, visibleCharEnd, previousColor); + } + private int getCharColor(int i) { + if (i == mUnmatchedBracket) { + return mTheme.getColorForToken(Token.ERROR); + } + if (i == mMatchingBrackets[0] || i == mMatchingBrackets[1]) { + return mTheme.getColorForToken(TokenMapping.TOKEN_MATCHED_BRACKET); + } + return mHighlightTokens.getCharColor(i); } @@ -264,11 +275,39 @@ public class CodeEditText extends AppCompatEditText { return; } callCursorChangeCallback(getText(), selStart); - checkParenthesesPairs(getText(), selStart); + matchesBracket(getText(), selStart); } - private void checkParenthesesPairs(CharSequence text, int sel) { - // TODO: 2018/2/24 + private void matchesBracket(CharSequence text, int cursor) { + if (checkBracketMatchingAt(text, cursor)) { + return; + } + if (checkBracketMatchingAt(text, cursor - 1)) { + return; + } + mMatchingBrackets[0] = -1; + mMatchingBrackets[1] = -1; + mUnmatchedBracket = -1; + } + + private boolean checkBracketMatchingAt(CharSequence text, int cursor) { + if (cursor < 0 || cursor >= text.length()) { + return false; + } + int i = BracketMatching.bracketMatching(text, cursor); + if (i >= 0) { + mMatchingBrackets[0] = cursor; + mMatchingBrackets[1] = i; + mUnmatchedBracket = -1; + return true; + } else if (i == UNMATCHED_BRACKET) { + mUnmatchedBracket = cursor; + mMatchingBrackets[0] = -1; + mMatchingBrackets[1] = -1; + return true; + } + return false; + } private void callCursorChangeCallback(CharSequence text, int sel) { diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/theme/Theme.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/theme/Theme.java index 156bfe28..c999d4d8 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/theme/Theme.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/theme/Theme.java @@ -75,7 +75,7 @@ public class Theme { } public static Theme getDefault(android.content.Context context) { - return fromAssetsJson(context, "editor/theme/dark_plus.json"); + return fromAssetsJson(context, "editor/theme/light_plus.json"); } public static Theme fromJson(String json) { @@ -140,4 +140,5 @@ public class Theme { return getName(); } + } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/theme/TokenMapping.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/theme/TokenMapping.java index a8cb4e08..61b1a02d 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/theme/TokenMapping.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/theme/TokenMapping.java @@ -15,6 +15,8 @@ import java.util.List; public class TokenMapping { + public static final int TOKEN_MATCHED_BRACKET = Token.LAST_TOKEN + 1; + private static final List KEYWORD = tokenNamesToTypes(Arrays.asList("return", "new", "delete", "typeof", "null", "this", "false", "true", "throw", "in", "instanceof", "yield", "try", "function", "if", "else", "switch", "case", "default", "while", "do", "for", "break", "continue", "var", "with", "catch", "finally", "void", "let", "const", "debugger")); private static final List KEYWORD_CONTROL = tokenNamesToTypes(Arrays.asList("if", "else", "switch", "case", "break", "continue", "goto", "return", "try", "catch", "throw", "finally")); private static final List KEYWORD_OPERATOR = Collections.emptyList(); @@ -29,13 +31,17 @@ public class TokenMapping { return KEYWORD_CONTROL; default: int token = tokenNameToType(scope); - if (Token.isValidToken(token)) { + if (isValidToken(token)) { return Collections.singletonList(token); } } return Collections.emptyList(); } + public static boolean isValidToken(int token) { + return token >= -1; + } + public static int tokenNameToType(String name) { switch (name) { case "this.self": @@ -46,6 +52,8 @@ public class TokenMapping { return Token.NAME; case "constant.numeric": return Token.NUMBER; + case "bracket.matched": + return TOKEN_MATCHED_BRACKET; } for (int token = Token.ERROR; token < Token.LAST_TOKEN; token++) {