├── .gitignore ├── .idea ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── vic797 │ │ └── edittextutils │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── java.json │ ├── java │ │ └── com │ │ │ └── vic797 │ │ │ └── edittextutils │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── menu │ │ └── main_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── vic797 │ └── edittextutils │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── nativesyntax ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── vic797 │ │ └── syntaxhighlight │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── vic797 │ │ │ └── syntaxhighlight │ │ │ ├── DelayedTextWatcher.java │ │ │ ├── LineCountLayout.java │ │ │ ├── SpanStyle.java │ │ │ ├── Syntax.java │ │ │ ├── SyntaxHighlighter.java │ │ │ └── SyntaxListener.java │ └── res │ │ └── values │ │ ├── attrs.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── vic797 │ └── syntaxhighlight │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 22 | 34 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Native Syntax Highlighter for Android 2 | 3 | [![](https://jitpack.io/v/vic797/android_native_code_view.svg)](https://jitpack.io/#vic797/prowebview) 4 | 5 | This library allows you to highlight syntax in an Android Native way using `Spannable`. 6 | A different way than other syntax highlighters that use a `WebView` with a Javascript library. 7 | 8 | Features: 9 | * Optimized: set the spans only in the current line, in a region or in the entire view. 10 | * Personalization: Add your own rules using JSON. They can be loaded from a JSON string or a JSON file in assets or in the external storage. 11 | * Read Only: use it as a `TextView` 12 | * Any number of rules: add all the rules that you want (could affect the performance) 13 | 14 | By default this library does not include any syntax; you can create your own syntax files using JSON or by 15 | adding the `Syntax` rules by code. 16 | 17 | ## Something extra! 18 | 19 | This library includes the `LineCountLayout` witch extends from `ScrollView` and allows you to add line numbers to any `EditText`. 20 | 21 | ## Add to your project 22 | 23 | Add this to your main gradle: 24 | 25 | ``` 26 | allprojects { 27 | repositories { 28 | ... 29 | maven { url 'https://jitpack.io' } 30 | } 31 | } 32 | ``` 33 | 34 | And add the reference to your app-level gradle: 35 | 36 | ``` 37 | dependencies { 38 | compile 'com.github.vic797:android_native_code_view:VERSION' 39 | } 40 | ``` 41 | 42 | ## Usage 43 | 44 | See the wiki to know how to use it. 45 | 46 | ## Licence 47 | 48 | ``` 49 | Copyright 2017 Victor Campos 50 | 51 | Licensed under the Apache License, Version 2.0 (the "License"); 52 | you may not use this file except in compliance with the License. 53 | You may obtain a copy of the License at 54 | 55 | http://www.apache.org/licenses/LICENSE-2.0 56 | 57 | Unless required by applicable law or agreed to in writing, software 58 | distributed under the License is distributed on an "AS IS" BASIS, 59 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 60 | See the License for the specific language governing permissions and 61 | limitations under the License. 62 | ``` -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | defaultConfig { 6 | applicationId "com.vic797.edittextutils" 7 | minSdkVersion 16 8 | targetSdkVersion 26 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(include: ['*.jar'], dir: 'libs') 23 | implementation 'com.android.support:appcompat-v7:26.1.0' 24 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 28 | implementation project(':nativesyntax') 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/vic797/edittextutils/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.vic797.edittextutils; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.vic797.edittextutils", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/assets/java.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_single": { 3 | "begin":"//", 4 | "color":"#757575" 5 | }, 6 | "comment_multiple": { 7 | "begin":"/*", 8 | "end":"*/", 9 | "color":"#757575" 10 | }, 11 | "rules":5, 12 | "syntax_1":{ 13 | "regex":"\\b(a(bstract|ssert)|c(ontinue|a(tch|se)|har|lass|onst)|f(or|inal(ly)?|loat)|n(ew|ative)|s(witch|ort|t(atic|rictfp)|uper|ynchronized)|d(efault|ouble)|goto|p(ackage|r(ivate|otected)|ublic)|b(oolean|reak|yte)|do|i(f|m(plements|port)|n(t(erface)?|stanceof))|t(h(is)|hrows?|r(ansient|y))|e(lse|num|xtends)|return|vo(id|latile)|long|while)\\b", 14 | "color":"#0000ff" 15 | }, 16 | "syntax_2":{ 17 | "regex":"\\(|\\)|\\[|\\]|:|;|\\.|\\||;|\\&|\\{|\\}", 18 | "color":"#b71c1c" 19 | }, 20 | "syntax_3":{ 21 | "regex":"@\\w+", 22 | "color":"#b87333" 23 | }, 24 | "syntax_4":{ 25 | "regex":"\\b\\d+[\\.]?\\d*([eE]\\-?\\d+)?[lLdDfF]?\\b|\\b0x[a-fA-F\\d]+\\b", 26 | "color":"#f4511e", 27 | "style":"italic" 28 | }, 29 | "syntax_5":{ 30 | "regex":"(\\\"(.*)\\\"|\\\'(.*)\\\')", 31 | "color":"#ff0000" 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vic797/edittextutils/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.vic797.edittextutils; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.text.Editable; 6 | import android.widget.Toast; 7 | 8 | import com.vic797.syntaxhighlight.LineCountLayout; 9 | import com.vic797.syntaxhighlight.SyntaxHighlighter; 10 | import com.vic797.syntaxhighlight.SyntaxListener; 11 | 12 | public class MainActivity extends AppCompatActivity implements SyntaxListener{ 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_main); 18 | LineCountLayout layout = findViewById(R.id.line_layout); 19 | SyntaxHighlighter editText = findViewById(R.id.edit_text); 20 | editText.addSyntax(getAssets(), "java.json"); 21 | layout.attachEditText(editText); 22 | editText.startHighlight(false); 23 | } 24 | 25 | @SuppressWarnings("unused") 26 | private String longJavaCode() { 27 | return "import android.content.Context;\n" + 28 | "import android.content.res.TypedArray;\n" + 29 | "import android.graphics.Canvas;\n" + 30 | "import android.graphics.Color;\n" + 31 | "import android.graphics.Paint;\n" + 32 | "import android.graphics.Rect;\n" + 33 | "import android.os.Build;\n" + 34 | "import android.support.annotation.ColorInt;\n" + 35 | "import android.support.annotation.NonNull;\n" + 36 | "import android.support.annotation.Nullable;\n" + 37 | "import android.text.Editable;\n" + 38 | "import android.text.Layout;\n" + 39 | "import android.text.TextWatcher;\n" + 40 | "import android.util.AttributeSet;\n" + 41 | "import android.widget.EditText;\n" + 42 | "import android.widget.ScrollView;\n" + 43 | "\n" + 44 | "public class LineCountLayout extends ScrollView {\n" + 45 | "\n" + 46 | " private EditText editText;\n" + 47 | " private Paint paint, numberLine;\n" + 48 | " private Rect rect;\n" + 49 | " private @ColorInt int textColor, stripColor;\n" + 50 | " private float textSize;\n" + 51 | "\n" + 52 | " private TextWatcher textWatcher = new TextWatcher() {\n" + 53 | " @Override\n" + 54 | " public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {\n" + 55 | "\n" + 56 | " }\n" + 57 | "\n" + 58 | " @Override\n" + 59 | " public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {\n" + 60 | "\n" + 61 | " }\n" + 62 | "\n" + 63 | " @Override\n" + 64 | " public void afterTextChanged(Editable editable) {\n" + 65 | " invalidate();\n" + 66 | " }\n" + 67 | " };\n" + 68 | "\n" + 69 | " public LineCountLayout(Context context) {\n" + 70 | " this(context, null);\n" + 71 | " }\n" + 72 | "\n" + 73 | " public LineCountLayout(Context context, @Nullable AttributeSet attrs) {\n" + 74 | " this(context, attrs, 0);\n" + 75 | " }\n" + 76 | "\n" + 77 | " public LineCountLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n" + 78 | " super(context, attrs, defStyleAttr);\n" + 79 | " TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LineCountLayout);\n" + 80 | " try {\n" + 81 | " textColor = array.getColor(R.styleable.LineCountLayout_numberColor, Color.BLACK);\n" + 82 | " stripColor = array.getColor(R.styleable.LineCountLayout_numberColor, Color.parseColor(\"#e0e0e0\"));\n" + 83 | " textSize = array.getDimension(R.styleable.LineCountLayout_numberSize, 12.0f);\n" + 84 | " } finally {\n" + 85 | " array.recycle();\n" + 86 | " }\n" + 87 | " paint = new Paint();\n" + 88 | " rect = new Rect();\n" + 89 | "\n" + 90 | " paint.setStyle(Paint.Style.FILL);\n" + 91 | " paint.setTextSize(textSize);\n" + 92 | " paint.setColor(textColor);\n" + 93 | "\n" + 94 | " numberLine = new Paint();\n" + 95 | " numberLine.setStyle(Paint.Style.FILL);\n" + 96 | " numberLine.setColor(stripColor);\n" + 97 | "\n" + 98 | " update();\n" + 99 | " }\n" + 100 | "\n" + 101 | " /**\n" + 102 | " * Set the color of the text\n" + 103 | " * @param color a color\n" + 104 | " */\n" + 105 | " public void setTextColor(@ColorInt int color) {\n" + 106 | " this.textColor = color;\n" + 107 | " paint.setColor(textColor);\n" + 108 | " invalidate();\n" + 109 | " }\n" + 110 | "\n" + 111 | " public void setStripColor(@ColorInt int color) {\n" + 112 | " this.stripColor = color;\n" + 113 | " numberLine.setColor(stripColor);\n" + 114 | " invalidate();\n" + 115 | " }\n" + 116 | "\n" + 117 | " /**\n" + 118 | " * Sets the size of the text\n" + 119 | " * @param size size of the text\n" + 120 | " */\n" + 121 | " public void setTextSize(float size) {\n" + 122 | " this.textSize = size;\n" + 123 | " paint.setTextSize(textSize);\n" + 124 | " invalidate();\n" + 125 | " }\n" + 126 | "\n" + 127 | " /**\n" + 128 | " * Returns the background color of the line numbers\n" + 129 | " */\n" + 130 | " @ColorInt\n" + 131 | " public int getStripColor() {\n" + 132 | " return stripColor;\n" + 133 | " }\n" + 134 | "\n" + 135 | " /**\n" + 136 | " * Returns the text color of the line numbers\n" + 137 | " */\n" + 138 | " @ColorInt\n" + 139 | " public int getTextColor() {\n" + 140 | " return textColor;\n" + 141 | " }\n" + 142 | "\n" + 143 | " /**\n" + 144 | " * Returns the size of the text\n" + 145 | " */\n" + 146 | " public float getTextSize() {\n" + 147 | " return textSize;\n" + 148 | " }\n" + 149 | "\n" + 150 | " /**\n" + 151 | " * Attach a {@link EditText} to the layout\n" + 152 | " * @param edit an {@link EditText} contained in the layout\n" + 153 | " */\n" + 154 | " public void attachEditText(@NonNull EditText edit) {\n" + 155 | " editText = edit;\n" + 156 | " editText.addTextChangedListener(textWatcher);\n" + 157 | " update();\n" + 158 | " }\n" + 159 | "\n" + 160 | " /**\n" + 161 | " * Update the layout\n" + 162 | " */\n" + 163 | " public void update() {\n" + 164 | " invalidate();\n" + 165 | " if (paint != null) {\n" + 166 | " float width = paint.measureText(\" \" + (editText == null ? \"1\" : editText.getLineCount()));\n" + 167 | " if (Build.VERSION.SDK_INT < 17)\n" + 168 | " setPadding((int) width, 0, 0, 0);\n" + 169 | " else\n" + 170 | " setPaddingRelative((int) width, 0, 0, 0);\n" + 171 | " }\n" + 172 | " }\n" + 173 | "\n" + 174 | " @Override\n" + 175 | " protected void onDraw(Canvas canvas) {\n" + 176 | " float width = paint.measureText(\" \" + (editText == null ? \"1\" : editText.getLineCount()));\n" + 177 | " canvas.drawRect(0, 0, (int) width, editText.getBottom(), numberLine);\n" + 178 | " if (editText != null) {\n" + 179 | " int line = editText.getBaseline();\n" + 180 | " int[] lines = getVisibleRegion();\n" + 181 | " for (int i = lines[0]; i <= lines[1]; i++) {\n" + 182 | " canvas.drawText(\"\" + (i + 1), (rect.left + 5), line, paint);\n" + 183 | " line += editText.getLineHeight();\n" + 184 | " }\n" + 185 | " update();\n" + 186 | " }\n" + 187 | " super.onDraw(canvas);\n" + 188 | " }\n" + 189 | "\n" + 190 | " @NonNull\n" + 191 | " private int[] getVisibleRegion() {\n" + 192 | " int height = editText.getHeight();\n" + 193 | " int scrollY = editText.getScrollY();\n" + 194 | " Layout layout = editText.getLayout();\n" + 195 | " int firstVisibleLineNumber = layout.getLineForVertical(scrollY);\n" + 196 | " int lastVisibleLineNumber = layout.getLineForVertical(scrollY+height);\n" + 197 | " return new int[] {firstVisibleLineNumber, lastVisibleLineNumber};\n" + 198 | " }\n" + 199 | "}\n"; 200 | } 201 | 202 | @Override 203 | public void onLineClick(Editable editable, String text, int line) { 204 | //Capture click in a line 205 | } 206 | 207 | @Override 208 | public void onHighlightStart(Editable editable) { 209 | //When the highlight process starts 210 | } 211 | 212 | @Override 213 | public void onHighlightEnd(Editable editable) { 214 | //When the highlight process ends 215 | } 216 | 217 | @Override 218 | public void onError(Exception e) { 219 | //When an Exception occurs 220 | e.printStackTrace(); 221 | Toast.makeText(this, "An error has occurred!", Toast.LENGTH_SHORT).show(); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vic797/android_native_code_view/d7c9aa6da8a106d1852a2cbb08e4432225f97410/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vic797/android_native_code_view/d7c9aa6da8a106d1852a2cbb08e4432225f97410/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vic797/android_native_code_view/d7c9aa6da8a106d1852a2cbb08e4432225f97410/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vic797/android_native_code_view/d7c9aa6da8a106d1852a2cbb08e4432225f97410/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vic797/android_native_code_view/d7c9aa6da8a106d1852a2cbb08e4432225f97410/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vic797/android_native_code_view/d7c9aa6da8a106d1852a2cbb08e4432225f97410/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vic797/android_native_code_view/d7c9aa6da8a106d1852a2cbb08e4432225f97410/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vic797/android_native_code_view/d7c9aa6da8a106d1852a2cbb08e4432225f97410/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vic797/android_native_code_view/d7c9aa6da8a106d1852a2cbb08e4432225f97410/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vic797/android_native_code_view/d7c9aa6da8a106d1852a2cbb08e4432225f97410/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | EditTextUtils 3 | Read only 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/vic797/edittextutils/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.vic797.edittextutils; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.0.1' 11 | 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vic797/android_native_code_view/d7c9aa6da8a106d1852a2cbb08e4432225f97410/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Apr 13 12:53:48 CDT 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /nativesyntax/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /nativesyntax/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 26 5 | 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 16 10 | targetSdkVersion 26 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | implementation 'com.android.support:appcompat-v7:26.1.0' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 32 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 33 | } 34 | -------------------------------------------------------------------------------- /nativesyntax/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /nativesyntax/src/androidTest/java/com/vic797/syntaxhighlight/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.vic797.syntaxhighlight; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.vic797.edittextutilslib.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /nativesyntax/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /nativesyntax/src/main/java/com/vic797/syntaxhighlight/DelayedTextWatcher.java: -------------------------------------------------------------------------------- 1 | package com.vic797.syntaxhighlight; 2 | 3 | import android.text.Editable; 4 | import android.text.TextWatcher; 5 | 6 | import java.util.Timer; 7 | import java.util.TimerTask; 8 | 9 | /** 10 | * This class helps to optimize the highlighting process of {@link SyntaxHighlighter} 11 | * by delaying the {@link TextWatcher#afterTextChanged(Editable)} by a specific time 12 | * (by default 1 second (1000 milliseconds)) and raising an event. This class uses a {@link Timer} that 13 | * is restarted every time that {@link TextWatcher#afterTextChanged(Editable)} is called 14 | * to prevent lag. 15 | */ 16 | public abstract class DelayedTextWatcher implements TextWatcher { 17 | 18 | private long millisAfter; 19 | private Timer timer; 20 | 21 | /** 22 | * This method is called after the {@link Timer} ends 23 | * @see TextWatcher#afterTextChanged(Editable) 24 | */ 25 | public abstract void textChanged(Editable editable); 26 | 27 | /** 28 | * Creates a new instance setting the delay to 1 second (1000 milliseconds) 29 | */ 30 | public DelayedTextWatcher() { 31 | this(1000); 32 | } 33 | 34 | /** 35 | * Creates a new instance with a specific delay 36 | * @param after a delay 37 | */ 38 | public DelayedTextWatcher(long after) { 39 | this.millisAfter = after; 40 | } 41 | 42 | @Override 43 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 44 | 45 | } 46 | 47 | @Override 48 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 49 | 50 | } 51 | 52 | @Override 53 | public void afterTextChanged(final Editable editable) { 54 | if (timer != null) 55 | timer.cancel(); 56 | timer = new Timer(); 57 | timer.schedule(new TimerTask() { 58 | @Override 59 | public void run() { 60 | textChanged(editable); 61 | } 62 | }, millisAfter); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /nativesyntax/src/main/java/com/vic797/syntaxhighlight/LineCountLayout.java: -------------------------------------------------------------------------------- 1 | package com.vic797.syntaxhighlight; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Rect; 9 | import android.os.Build; 10 | import android.support.annotation.ColorInt; 11 | import android.support.annotation.NonNull; 12 | import android.support.annotation.Nullable; 13 | import android.text.Editable; 14 | import android.text.TextWatcher; 15 | import android.util.AttributeSet; 16 | import android.widget.EditText; 17 | import android.widget.ScrollView; 18 | 19 | 20 | public class LineCountLayout extends ScrollView { 21 | 22 | private EditText editText; 23 | private Paint paint, numberLine; 24 | private Rect rect; 25 | private @ColorInt int textColor, stripColor; 26 | private float textSize; 27 | 28 | private TextWatcher textWatcher = new TextWatcher() { 29 | @Override 30 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 31 | 32 | } 33 | 34 | @Override 35 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 36 | 37 | } 38 | 39 | @Override 40 | public void afterTextChanged(Editable editable) { 41 | invalidate(); 42 | } 43 | }; 44 | 45 | public LineCountLayout(Context context) { 46 | this(context, null); 47 | } 48 | 49 | public LineCountLayout(Context context, @Nullable AttributeSet attrs) { 50 | this(context, attrs, 0); 51 | } 52 | 53 | public LineCountLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 54 | super(context, attrs, defStyleAttr); 55 | TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LineCountLayout); 56 | try { 57 | textColor = array.getColor(R.styleable.LineCountLayout_numberColor, Color.BLACK); 58 | stripColor = array.getColor(R.styleable.LineCountLayout_stripColor, Color.parseColor("#e0e0e0")); 59 | textSize = array.getDimension(R.styleable.LineCountLayout_numberSize, 12.0f); 60 | } finally { 61 | array.recycle(); 62 | } 63 | paint = new Paint(); 64 | rect = new Rect(); 65 | 66 | paint.setStyle(Paint.Style.FILL); 67 | paint.setTextSize(textSize); 68 | paint.setColor(textColor); 69 | 70 | numberLine = new Paint(); 71 | numberLine.setStyle(Paint.Style.FILL); 72 | numberLine.setColor(stripColor); 73 | 74 | update(); 75 | } 76 | 77 | /** 78 | * Set the color of the text 79 | * @param color a color 80 | */ 81 | public void setTextColor(@ColorInt int color) { 82 | this.textColor = color; 83 | paint.setColor(textColor); 84 | invalidate(); 85 | } 86 | 87 | /** 88 | * Set the background color of the strip line 89 | * @param color a color 90 | */ 91 | public void setStripColor(@ColorInt int color) { 92 | this.stripColor = color; 93 | numberLine.setColor(stripColor); 94 | invalidate(); 95 | } 96 | 97 | /** 98 | * Sets the size of the text 99 | * @param size size of the text 100 | */ 101 | public void setTextSize(float size) { 102 | this.textSize = size; 103 | paint.setTextSize(textSize); 104 | invalidate(); 105 | } 106 | 107 | /** 108 | * Returns the background color of the line numbers 109 | */ 110 | @ColorInt 111 | public int getStripColor() { 112 | return stripColor; 113 | } 114 | 115 | /** 116 | * Returns the text color of the line numbers 117 | */ 118 | @ColorInt 119 | public int getTextColor() { 120 | return textColor; 121 | } 122 | 123 | /** 124 | * Returns the size of the text 125 | */ 126 | public float getTextSize() { 127 | return textSize; 128 | } 129 | 130 | /** 131 | * Attach a {@link EditText} to the layout 132 | * @param edit an {@link EditText} contained in the layout 133 | */ 134 | public void attachEditText(@NonNull EditText edit) { 135 | editText = edit; 136 | editText.addTextChangedListener(textWatcher); 137 | update(); 138 | } 139 | 140 | /** 141 | * Update the layout 142 | */ 143 | public void update() { 144 | invalidate(); 145 | if (paint != null) { 146 | float width = paint.measureText(" " + (editText == null ? "1" : editText.getLineCount())); 147 | if (Build.VERSION.SDK_INT < 17) 148 | setPadding((int) width, 0, 0, 0); 149 | else 150 | setPaddingRelative((int) width, 0, 0, 0); 151 | } 152 | } 153 | 154 | @Override 155 | protected void onDraw(Canvas canvas) { 156 | float width = paint.measureText(" " + (editText == null ? "1" : editText.getLineCount())); 157 | canvas.drawRect(0, 0, (int) width, editText.getBottom(), numberLine); 158 | if (editText != null) { 159 | int line = editText.getBaseline(); 160 | int lineCount = editText.getLineCount(); 161 | for (int i = 0; i < lineCount; i++) { 162 | canvas.drawText("" + (i + 1), (rect.left + 5), line, paint); 163 | line += editText.getLineHeight(); 164 | } 165 | update(); 166 | } 167 | super.onDraw(canvas); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /nativesyntax/src/main/java/com/vic797/syntaxhighlight/SpanStyle.java: -------------------------------------------------------------------------------- 1 | package com.vic797.syntaxhighlight; 2 | 3 | import android.graphics.Typeface; 4 | import android.support.annotation.IntDef; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | 9 | @IntDef({ 10 | Typeface.NORMAL, 11 | Typeface.BOLD, 12 | Typeface.ITALIC, 13 | Typeface.BOLD_ITALIC 14 | }) 15 | @Retention(RetentionPolicy.SOURCE) 16 | public @interface SpanStyle {} -------------------------------------------------------------------------------- /nativesyntax/src/main/java/com/vic797/syntaxhighlight/Syntax.java: -------------------------------------------------------------------------------- 1 | package com.vic797.syntaxhighlight; 2 | 3 | import android.graphics.Color; 4 | import android.graphics.Typeface; 5 | import android.support.annotation.NonNull; 6 | 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * Syntax rules for {@link SyntaxHighlighter} 12 | */ 13 | public final class Syntax { 14 | 15 | private int color; 16 | private int backColor; 17 | private String formatFlags; 18 | 19 | private Pattern pattern; 20 | 21 | /** 22 | * Creates a new syntax rule 23 | * @param color color of the span 24 | * @param regex regular expression 25 | */ 26 | public Syntax(int color, String regex) { 27 | this.color = color; 28 | backColor = -1; 29 | pattern = Pattern.compile(regex); 30 | formatFlags = "normal"; 31 | } 32 | 33 | /** 34 | * Optional: set a background color for the span (default is {@link Color#WHITE}) 35 | * @param color background color 36 | */ 37 | public Syntax setBackgroundColor( int color) { 38 | this.backColor = color; 39 | return this; 40 | } 41 | 42 | /** 43 | * Returns the background color 44 | */ 45 | public int getBackgroundColor() { 46 | return this.backColor; 47 | } 48 | 49 | /** 50 | * Returns the foreground color 51 | */ 52 | public int getColor() { 53 | return color; 54 | } 55 | 56 | /** 57 | * Returns a matcher for a string 58 | * @param text string to match 59 | * @return a matcher from a {@link Pattern} 60 | */ 61 | @NonNull 62 | public Matcher getMatcher(String text) { 63 | return pattern.matcher(text); 64 | } 65 | 66 | /** 67 | * Set format flag 68 | * @param formatFlags a string flags divided by 69 | */ 70 | public Syntax setFormatFlag(String formatFlags) { 71 | this.formatFlags = ((formatFlags == null) || formatFlags.equals("")) ? "normal" : formatFlags; 72 | return this; 73 | } 74 | 75 | /** 76 | * Set format flag 77 | */ 78 | public Syntax setFormatFlag(@SpanStyle int style) { 79 | switch (style) { 80 | case Typeface.NORMAL: 81 | return setFormatFlag("normal"); 82 | case Typeface.BOLD: 83 | return setFormatFlag("bold"); 84 | case Typeface.ITALIC: 85 | return setFormatFlag("italic"); 86 | case Typeface.BOLD_ITALIC: 87 | return setFormatFlag("bold|italic"); 88 | default: 89 | return setFormatFlag("normal"); 90 | } 91 | } 92 | 93 | /** 94 | * Get the current format fla 95 | * @return any of {@link SpanStyle} 96 | */ 97 | @SpanStyle 98 | public int getFormatFlags() { 99 | switch (formatFlags) { 100 | case "normal": 101 | return Typeface.NORMAL; 102 | case "bold": 103 | return Typeface.BOLD; 104 | case "italic": 105 | return Typeface.ITALIC; 106 | case "bold|italic": 107 | case "italic|bold": 108 | return Typeface.BOLD_ITALIC; 109 | default: 110 | return Typeface.NORMAL; 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /nativesyntax/src/main/java/com/vic797/syntaxhighlight/SyntaxHighlighter.java: -------------------------------------------------------------------------------- 1 | package com.vic797.syntaxhighlight; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.content.res.AssetManager; 6 | import android.content.res.TypedArray; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.Rect; 11 | import android.graphics.Typeface; 12 | import android.support.annotation.ColorInt; 13 | import android.support.annotation.NonNull; 14 | import android.support.annotation.Nullable; 15 | import android.support.v7.widget.AppCompatEditText; 16 | import android.text.Editable; 17 | import android.text.InputType; 18 | import android.text.Layout; 19 | import android.text.Selection; 20 | import android.text.Spanned; 21 | import android.text.style.BackgroundColorSpan; 22 | import android.text.style.ForegroundColorSpan; 23 | import android.text.style.StyleSpan; 24 | import android.text.util.Linkify; 25 | import android.util.AttributeSet; 26 | import android.view.MotionEvent; 27 | import android.view.View; 28 | import android.view.ViewTreeObserver; 29 | import android.widget.EditText; 30 | import android.widget.ScrollView; 31 | 32 | import org.json.JSONObject; 33 | 34 | import java.io.BufferedReader; 35 | import java.io.File; 36 | import java.io.FileReader; 37 | import java.io.IOException; 38 | import java.io.InputStreamReader; 39 | import java.util.ArrayList; 40 | import java.util.Arrays; 41 | import java.util.regex.Matcher; 42 | 43 | /** 44 | * This class allows you to highlight syntax in an Android Native way using {@link android.text.Spannable}. 45 | * 46 | * Features: 47 | * - Optimized: set the spans only in the current line, in a region or in the entire view. 48 | * - Personalization: Add your own rules using JSON. They can be loaded from a JSON string or a JSON file in assets or in the external storage. 49 | * - Read Only: use as a {@link android.widget.TextView} 50 | * - Any number of rules: add all the rules that you want (could affect the performance) 51 | * 52 | * By default this library does not include any syntax; you can create your own syntax files using JSON or by 53 | * adding the {@link Syntax} rules by code. 54 | * 55 | * Known bug 56 | * - Lag while editing with a lot of visible spans 57 | * 58 | */ 59 | 60 | @SuppressWarnings({"unused", "SameParameterValue"}) 61 | public class SyntaxHighlighter extends AppCompatEditText { 62 | 63 | private static final String JSON_RULES = "rules"; 64 | private static final String JSON_SYNTAX = "syntax_"; 65 | private static final String JSON_REGEX = "regex"; 66 | private static final String JSON_COLOR = "color"; 67 | private static final String JSON_BACKCOLOR = "backcolor"; 68 | private static final String JSON_BEGIN = "begin"; 69 | private static final String JSON_END = "end"; 70 | private static final String JSON_COMMENT_SINGLE = "comment_single"; 71 | private static final String JSON_COMMENT_MULTI = "comment_multiple"; 72 | private static final String JSON_STYLE = "style"; 73 | 74 | private static final String TAG = "SyntaxHighlighter"; 75 | 76 | private boolean updateCalledFromInstance = false; 77 | private ArrayList syntaxList; 78 | private SyntaxListener listener; 79 | private View.OnTouchListener onTouchListener; 80 | private boolean links; 81 | private boolean highlighting; 82 | @ColorInt 83 | private int stripColor; 84 | private boolean stripLinesInReadOnly; 85 | private boolean highlightCurrentLine; 86 | private Paint paint; 87 | private boolean readOnly = false; 88 | 89 | /*private boolean disableEvents = false; 90 | private Handler updateHandler = new Handler(); 91 | private Runnable updateRunnable = new Runnable() { 92 | @Override 93 | public void run() { 94 | //updateVisibleRegion(); 95 | highlightLine(getText(), getLine()); 96 | disableEvents = false; 97 | } 98 | };*/ 99 | 100 | /*private TextWatcher textWatcher = new TextWatcher() { 101 | @Override 102 | public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { 103 | 104 | } 105 | 106 | @Override 107 | public void onTextChanged(CharSequence charSequence, int start, int before, int count) { 108 | //onSelectionChanged(start, count); 109 | } 110 | 111 | @Override 112 | public void afterTextChanged(Editable editable) { 113 | if (!disableEvents) { 114 | disableEvents = true; 115 | updateHandler.postDelayed(updateRunnable, 1500); 116 | } 117 | } 118 | };*/ 119 | 120 | private DelayedTextWatcher textWatcher = new DelayedTextWatcher(1000) { 121 | 122 | @Override 123 | public void textChanged(Editable editable) { 124 | highlightLine(getText(), getLine()); 125 | } 126 | }; 127 | 128 | private ViewTreeObserver.OnGlobalLayoutListener layoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { 129 | @Override 130 | public void onGlobalLayout() { 131 | updateZone(); 132 | updateCalledFromInstance = true; 133 | getViewTreeObserver().removeOnGlobalLayoutListener(this); 134 | } 135 | }; 136 | 137 | public SyntaxHighlighter(Context context) { 138 | this(context, null); 139 | } 140 | 141 | public SyntaxHighlighter(Context context, @Nullable AttributeSet attrs) { 142 | this(context, attrs, 0); 143 | } 144 | 145 | public SyntaxHighlighter(Context context, @Nullable AttributeSet attrs, int defStyleRes) { 146 | super(context, attrs, defStyleRes); 147 | TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SyntaxHighlighter); 148 | try { 149 | highlightLinks(array.getBoolean(R.styleable.SyntaxHighlighter_highlightLinks, false)); 150 | setReadonly(array.getBoolean(R.styleable.SyntaxHighlighter_readonly, false)); 151 | setStripLinesInReadOnly(array.getBoolean(R.styleable.SyntaxHighlighter_stripLinesInReadOnlyMode, true)); 152 | setStripColor(array.getColor(R.styleable.SyntaxHighlighter_stripLineColor, Color.parseColor("#e0e0e0"))); 153 | setHighlightCurrentLine(array.getBoolean(R.styleable.SyntaxHighlighter_highlightCurrentLine, true)); 154 | } finally { 155 | array.recycle(); 156 | } 157 | highlighting = false; 158 | syntaxList = new ArrayList<>(); 159 | setFocusable(true); 160 | setFocusableInTouchMode(true); 161 | } 162 | 163 | @Override 164 | public void setOnTouchListener(@Nullable View.OnTouchListener listener) { 165 | this.onTouchListener = listener; 166 | } 167 | 168 | private int lastLine = -1; 169 | @Override 170 | protected void onSelectionChanged(int selStart, int selEnd) { 171 | if (getLayout() != null) { 172 | int currentLine = getLayout().getLineForOffset(selStart); 173 | if (lastLine != currentLine) { 174 | lastLine = currentLine; 175 | highlightLine(getText(), new int[] {getLayout().getLineStart(currentLine), getLayout().getLineEnd(currentLine)}); 176 | } 177 | } 178 | super.onSelectionChanged(selStart, selEnd); 179 | } 180 | 181 | private Rect rect = new Rect(); 182 | @Override 183 | protected void onDraw(Canvas canvas) { 184 | if (paint != null) { 185 | if (readOnly) { 186 | int line = getBaseline(); 187 | int lineCount = getLineCount(); 188 | for (int i = 0; i < lineCount; i++) { 189 | if (i % 2 == 0) { 190 | getLineBounds(i, rect); 191 | rect.left -= getPaddingLeft(); 192 | rect.right += getPaddingRight(); 193 | canvas.drawRect(rect, paint); 194 | } 195 | line += getLineHeight(); 196 | } 197 | } else { 198 | getLineBounds(getCurrentLine(), rect); 199 | canvas.drawRect(rect, paint); 200 | } 201 | } 202 | super.onDraw(canvas); 203 | } 204 | 205 | //Main calls 206 | 207 | /** 208 | * Set a strip or highlight color 209 | */ 210 | public void setStripColor(@ColorInt int color) { 211 | this.stripColor = color; 212 | if (paint == null) 213 | paint = new Paint(); 214 | paint.setStyle(Paint.Style.FILL); 215 | paint.setColor(color); 216 | } 217 | 218 | /** 219 | * The lines will appear in a horizontal strip pattern 220 | * @param sliro should do the pattern 221 | */ 222 | public void setStripLinesInReadOnly(boolean sliro) { 223 | this.stripLinesInReadOnly = sliro; 224 | } 225 | 226 | /** 227 | * Should set a background indocator on the current line? 228 | */ 229 | public void setHighlightCurrentLine(boolean highlighting) { 230 | this.highlightCurrentLine = highlighting; 231 | } 232 | 233 | /** 234 | * Get the current highlight color 235 | */ 236 | @ColorInt 237 | public int getStripColor() { 238 | return stripColor; 239 | } 240 | 241 | /** 242 | * Check if the strip pattern is enabled 243 | */ 244 | public boolean isStripLinesInReadOnly() { 245 | return stripLinesInReadOnly; 246 | } 247 | 248 | public boolean isHighlightCurrentLine() { 249 | return highlightCurrentLine; 250 | } 251 | 252 | /** 253 | * Adds a new syntax rule 254 | * @param syntaxList Syntax objects 255 | */ 256 | public void addSyntax(Syntax... syntaxList) { 257 | if (syntaxList.length == 0) 258 | return; 259 | this.syntaxList.addAll(Arrays.asList(syntaxList)); 260 | } 261 | 262 | /** 263 | * Adds a new syntax rule 264 | * @param color color of the span 265 | * @param regex regex for the span 266 | * @see #addSyntax(Syntax...) 267 | */ 268 | public void addSyntax(@ColorInt int color, String regex) { 269 | addSyntax(new Syntax(color, regex)); 270 | } 271 | 272 | /** 273 | * Generates a list of syntax rules from a JSON string 274 | * @param json JSON string 275 | */ 276 | public void addSyntax(String json) { 277 | try { 278 | JSONObject object = new JSONObject(json); 279 | if (object.has(JSON_RULES)) { 280 | int size = object.getInt(JSON_RULES); 281 | if (size >= 1) { 282 | for (int i = 1; i <= size; i++) { 283 | String name = JSON_SYNTAX + i; 284 | if (!object.has(name)) 285 | if (listener != null) { 286 | listener.onError(new Exception("Count mismatch; the amount of \"syntax_\" tags must start in 1 and end in " + size)); 287 | return; 288 | } 289 | JSONObject syntax = object.getJSONObject(name); 290 | String regex = syntax.getString(JSON_REGEX); 291 | String color = syntax.getString(JSON_COLOR); 292 | Syntax s = new Syntax(Color.parseColor(color), regex); 293 | if (syntax.has(JSON_BACKCOLOR)) { 294 | String backcolor = syntax.getString(JSON_BACKCOLOR); 295 | s.setBackgroundColor(Color.parseColor(backcolor)); 296 | } 297 | if (syntax.has(JSON_STYLE)) 298 | s.setFormatFlag(syntax.getString(JSON_STYLE)); 299 | syntaxList.add(s); 300 | } 301 | } 302 | } 303 | 304 | if (object.has(JSON_COMMENT_SINGLE)) { 305 | String singleStart = object.getJSONObject(JSON_COMMENT_SINGLE).getString(JSON_BEGIN); 306 | String singleColor = object.getJSONObject(JSON_COMMENT_SINGLE).getString(JSON_COLOR); 307 | addSingleLineComment(singleStart, Color.parseColor(singleColor)); 308 | } 309 | 310 | if (object.has(JSON_COMMENT_MULTI)) { 311 | String multiStart = object.getJSONObject(JSON_COMMENT_MULTI).getString(JSON_BEGIN); 312 | String multiEnd = object.getJSONObject(JSON_COMMENT_MULTI).getString(JSON_END); 313 | String multiColor = object.getJSONObject(JSON_COMMENT_MULTI).getString(JSON_COLOR); 314 | addMultiLineComment(multiStart, multiEnd, Color.parseColor(multiColor)); 315 | } 316 | } catch (Exception e) { 317 | if (listener != null) 318 | listener.onError(e); 319 | } 320 | } 321 | 322 | /** 323 | * Generates a list of syntax rules from a JSON file 324 | * @param file JSON file 325 | * @see #addSyntax(String) 326 | */ 327 | public void addSyntax(File file) { 328 | if (file.isDirectory()) 329 | return; 330 | if (!file.getName().endsWith("json")) 331 | return; 332 | BufferedReader reader = null; 333 | try { 334 | reader = new BufferedReader(new FileReader(file)); 335 | StringBuilder builder = new StringBuilder(); 336 | String line; 337 | while ((line = reader.readLine()) != null) { 338 | builder.append(line); 339 | } 340 | addSyntax(builder.toString()); 341 | }catch (IOException e) { 342 | if (listener != null) 343 | listener.onError(e); 344 | } 345 | finally { 346 | try { 347 | if (reader != null) 348 | reader.close(); 349 | } catch (IOException e) { 350 | if (listener != null) 351 | listener.onError(e); 352 | } 353 | } 354 | } 355 | 356 | /** 357 | * Generates a list of syntax rules from a JSON file in the assets folder 358 | * @param manager {@link AssetManager} assets 359 | * @param file name of the file 360 | * @see #addSyntax(File) 361 | * @see #addSyntax(String) 362 | */ 363 | public void addSyntax(AssetManager manager, String file) { 364 | if (!file.endsWith("json")) 365 | return; 366 | BufferedReader reader = null; 367 | try { 368 | reader = new BufferedReader(new InputStreamReader(manager.open(file))); 369 | StringBuilder builder = new StringBuilder(); 370 | String line; 371 | while ((line = reader.readLine()) != null) { 372 | builder.append(line); 373 | } 374 | addSyntax(builder.toString()); 375 | }catch (IOException e) { 376 | if (listener != null) 377 | listener.onError(e); 378 | } 379 | finally { 380 | try { 381 | if (reader != null) 382 | reader.close(); 383 | } catch (IOException e) { 384 | if (listener != null) 385 | listener.onError(e); 386 | } 387 | } 388 | } 389 | 390 | /** 391 | * Remove all the rules 392 | * @param clearSpans if true clears all the spans 393 | */ 394 | public void removeAllRules(boolean clearSpans) { 395 | syntaxList.clear(); 396 | if (clearSpans) 397 | clear(); 398 | } 399 | 400 | /** 401 | * Generates a rule for single line comments 402 | * @param sl how must start a line of a comment 403 | * @param color color of the comment 404 | */ 405 | public void addSingleLineComment(@NonNull String sl, @ColorInt int color) { 406 | if (!sl.equals("")){ 407 | StringBuilder t = new StringBuilder(); 408 | for (char c : sl.toCharArray()) { 409 | t.append("\\").append(String.valueOf(c)); 410 | } 411 | t.append(".*\\n"); 412 | addSyntax(new Syntax(color, t.toString())); 413 | } 414 | } 415 | 416 | /** 417 | * Generates a rule for single line comments 418 | * @param start how must start a line of a comment 419 | * @param end how must end a line of a comment 420 | * @param color color of the comment 421 | */ 422 | public void addMultiLineComment(@NonNull String start, @NonNull String end, @ColorInt int color){ 423 | if ((!start.equals(""))&&(!end.equals(""))) { 424 | StringBuilder t = new StringBuilder(); 425 | for (char c : start.toCharArray()) { 426 | t.append("\\").append(String.valueOf(c)); 427 | } 428 | t.append("\\s*\\S[\\s\\S]*"); 429 | for (char c : end.toCharArray()) { 430 | t.append("\\").append(String.valueOf(c)); 431 | } 432 | addSyntax(new Syntax(color, t.toString())); 433 | } 434 | } 435 | 436 | /** 437 | * Set the horizontal scroll active or inactive 438 | * Important: may cause lag 439 | * @param ehs enabled or not 440 | */ 441 | public void enableHorizontalScroll(boolean ehs) { 442 | setHorizontalFadingEdgeEnabled(ehs); 443 | setHorizontallyScrolling(ehs); 444 | setHorizontalScrollBarEnabled(ehs); 445 | } 446 | 447 | /** 448 | * Enable or disable links 449 | * @param b enabled or disabled 450 | */ 451 | public void highlightLinks(boolean b) { 452 | this.links = b; 453 | } 454 | 455 | /** 456 | * Removes a syntax rule 457 | * @param syntax syntax rule to remove 458 | */ 459 | public void removeSyntax(Syntax syntax) { 460 | syntaxList.remove(syntax); 461 | } 462 | 463 | /** 464 | * Removes a syntax rule 465 | * @param index index of syntax rule to remove 466 | */ 467 | public void removeSyntax(int index) { 468 | syntaxList.remove(index); 469 | } 470 | 471 | /** 472 | * Sets the {@link EditText} to read only 473 | * Important: can not be undone 474 | */ 475 | public void setReadonly(boolean readonly) { 476 | setFocusable(!readonly); 477 | this.readOnly = readonly; 478 | } 479 | 480 | /** 481 | * Set a {@link SyntaxListener} for the syntax highlight process 482 | * @param listener a listener 483 | */ 484 | public void setListener(@Nullable SyntaxListener listener) { 485 | this.listener = listener; 486 | } 487 | 488 | //Voids 489 | 490 | /** 491 | * Activates the highlighter and this method must be called 492 | * @param all if true highlights all the text 493 | * @see #stopHighlight(boolean, boolean) 494 | */ 495 | public void startHighlight(boolean all){ 496 | if (highlighting) { 497 | if (listener != null) 498 | listener.onError(new Exception("Unnecessary call, highlighting already stated!")); 499 | return; 500 | } 501 | if ((getInputType() & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) 502 | setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 503 | if (syntaxList.isEmpty()) { 504 | if (listener != null) 505 | listener.onError(new IndexOutOfBoundsException("To start the highlighter there must be at least one rule!")); 506 | } else { 507 | if (all) 508 | highlightLine(getText(), new int[] {0, getText().length()}); 509 | else 510 | updateVisibleRegion(); 511 | addTextChangedListener(textWatcher); 512 | super.setOnTouchListener(getTouchListener()); 513 | updateCalledFromInstance = true; 514 | highlighting = true; 515 | } 516 | } 517 | 518 | /** 519 | * Stops the highlighting process 520 | * @param clear should clear the spans? 521 | * @param resetRules should reset the highlight rules? 522 | * @see #startHighlight(boolean) 523 | */ 524 | public void stopHighlight(boolean clear, boolean resetRules) { 525 | if (!highlighting) { 526 | if (listener != null) 527 | listener.onError(new Exception("Unnecessary call, please call #startHighlight(boolean) before")); 528 | return; 529 | } 530 | removeTextChangedListener(textWatcher); 531 | super.setOnTouchListener(onTouchListener); 532 | if (clear) clear(); 533 | if (resetRules) removeAllRules(false); 534 | highlighting = false; 535 | } 536 | 537 | /** 538 | * Clears all the spans 539 | */ 540 | public void clear() { 541 | getText().clearSpans(); 542 | } 543 | 544 | /** 545 | * Updates the spans in a region of lines 546 | * @param l1 fist line 547 | * @param l2 last line 548 | */ 549 | public void updateRegion(int l1, int l2) { 550 | if (getLayout() == null) { 551 | if (listener != null) 552 | listener.onError(new NullPointerException("Layout not initialized!")); 553 | return; 554 | } 555 | for (int i = l1; i <= l2; i++) { 556 | int start = getLayout().getLineStart(i); 557 | int end = getLayout().getLineEnd(i); 558 | int[] se = new int[] {start, end}; 559 | highlightLine(getText(), se); 560 | } 561 | } 562 | 563 | /** 564 | * Updates the visible region 565 | * Must be scrollable or inside a {@link ScrollView} 566 | */ 567 | public void updateVisibleRegion() { 568 | if (!updateCalledFromInstance) { 569 | getViewTreeObserver().addOnGlobalLayoutListener(layoutListener); 570 | } else { 571 | if (getLayout() != null) 572 | updateZone(); 573 | } 574 | } 575 | 576 | /** 577 | * Returns the current line 578 | */ 579 | public int getCurrentLine() { 580 | int current = 0; 581 | int selectionStart = Selection.getSelectionStart(getText()); 582 | Layout layout = getLayout(); 583 | if (!(selectionStart == -1)) 584 | current = layout.getLineForOffset(selectionStart); 585 | return current; 586 | } 587 | 588 | /** 589 | * Check if the highlighting process has stated 590 | * @return true if the highlighting process has stated 591 | */ 592 | public boolean isHighlighting() { 593 | return highlighting; 594 | } 595 | 596 | //Private methods 597 | 598 | @NonNull 599 | private View.OnTouchListener getTouchListener() { 600 | return new View.OnTouchListener() { 601 | @SuppressLint("ClickableViewAccessibility") 602 | @Override 603 | public boolean onTouch(View view, MotionEvent motionEvent) { 604 | EditText editText = SyntaxHighlighter.this; 605 | if ((listener != null) && (motionEvent.getAction() == MotionEvent.ACTION_DOWN)) { 606 | int l = editText.getLayout().getLineForVertical((int) motionEvent.getY()); 607 | int s = editText.getLayout().getLineStart(l); 608 | int e = editText.getLayout().getLineEnd(l); 609 | String text = editText.getText().subSequence(s, e).toString(); 610 | listener.onLineClick(editText.getText(), text, l); 611 | } 612 | if ((editText.getParent() instanceof ScrollView) && (motionEvent.getAction() == MotionEvent.ACTION_UP)) 613 | updateVisibleRegion(); 614 | return onTouchListener != null && onTouchListener.onTouch(view, motionEvent); 615 | } 616 | }; 617 | } 618 | 619 | private void updateZone() { 620 | if (getLayout() == null) 621 | return; 622 | if (getParent() instanceof ScrollView) { 623 | ScrollView scrollView = (ScrollView) getParent(); 624 | Rect rect = new Rect(); 625 | scrollView.getDrawingRect(rect); 626 | Layout layout = getLayout(); 627 | int firstVisibleLineNumber = layout.getLineForVertical(rect.top); 628 | int lastVisibleLineNumber = layout.getLineForVertical(rect.bottom); 629 | updateRegion(firstVisibleLineNumber, lastVisibleLineNumber); 630 | } else { 631 | Layout layout = getLayout(); 632 | int firstVisibleLineNumber = layout.getLineForVertical(getTop()); 633 | int lastVisibleLineNumber = layout.getLineForVertical(getHeight()); 634 | updateRegion(firstVisibleLineNumber, lastVisibleLineNumber); 635 | } 636 | } 637 | 638 | private void highlightLine(Editable editable, int[] line) { 639 | try { 640 | if (syntaxList == null) 641 | return; 642 | if (line.length == 0) 643 | return; 644 | if (listener != null) 645 | listener.onHighlightStart(editable); 646 | String text = editable.subSequence(line[0], line[1]).toString(); 647 | for(Syntax syntax : syntaxList) { 648 | Matcher matcher = syntax.getMatcher(text); 649 | while (matcher.find()) { 650 | int start = matcher.start() + ((matcher.start() == 0) && (line[0] == 0) ? 0 : line[0]); 651 | int end = matcher.end() + (matcher.end() == line[1] ? 0 : line[0]); 652 | editable.setSpan(new ForegroundColorSpan(syntax.getColor()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 653 | if (syntax.getBackgroundColor() != -1) 654 | editable.setSpan(new BackgroundColorSpan(syntax.getBackgroundColor()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 655 | if (syntax.getFormatFlags() != Typeface.NORMAL) 656 | editable.setSpan(new StyleSpan(syntax.getFormatFlags()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 657 | } 658 | } 659 | if (links) 660 | Linkify.addLinks(this, Linkify.ALL); 661 | if (listener != null) 662 | listener.onHighlightEnd(editable); 663 | }catch (Exception e) { 664 | if (listener != null) 665 | listener.onError(e); 666 | } 667 | } 668 | 669 | @NonNull 670 | private int[] getLine() { 671 | int current = getCurrentLine(); 672 | if (current == -1) { 673 | return new int[] {}; 674 | } 675 | int start = getLayout().getLineStart(current); 676 | int end = getLayout().getLineEnd(current); 677 | return new int[] {start, end}; 678 | } 679 | 680 | } 681 | -------------------------------------------------------------------------------- /nativesyntax/src/main/java/com/vic797/syntaxhighlight/SyntaxListener.java: -------------------------------------------------------------------------------- 1 | package com.vic797.syntaxhighlight; 2 | 3 | import android.text.Editable; 4 | 5 | /** 6 | * Main interface to listen the {@link SyntaxHighlighter} events 7 | */ 8 | public interface SyntaxListener { 9 | 10 | void onLineClick(Editable editable, String text, int line); 11 | void onHighlightStart(Editable editable); 12 | void onHighlightEnd(Editable editable); 13 | void onError(Exception e); 14 | 15 | } -------------------------------------------------------------------------------- /nativesyntax/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /nativesyntax/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | edittextutilslib 3 | 4 | -------------------------------------------------------------------------------- /nativesyntax/src/test/java/com/vic797/syntaxhighlight/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.vic797.syntaxhighlight; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':nativesyntax' 2 | --------------------------------------------------------------------------------