├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── ve │ │ └── acpp │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── ve │ │ │ ├── acpp │ │ │ └── MainActivity.java │ │ │ ├── lexer │ │ │ ├── Language.java │ │ │ ├── LanguageC.java │ │ │ ├── LanguageLua.java │ │ │ ├── Lexer.java │ │ │ ├── LuaLexer.java │ │ │ └── LuaTokenTypes.java │ │ │ ├── utils │ │ │ ├── AnimUtils.java │ │ │ ├── DisplayUtils.java │ │ │ ├── GraphUtils.java │ │ │ ├── JsonUtils.java │ │ │ ├── LightStatusBarUtils.java │ │ │ ├── LocationUtils.java │ │ │ ├── MD5.java │ │ │ ├── RomUtils.java │ │ │ └── SysUtils.java │ │ │ └── view │ │ │ ├── editor │ │ │ ├── Base.java │ │ │ ├── Caret.java │ │ │ ├── CursorHandlerManager.java │ │ │ ├── Editor.java │ │ │ ├── EditorInputConnection.java │ │ │ ├── EventManager.java │ │ │ ├── ListenManager.java │ │ │ ├── MeasureCache.java │ │ │ ├── Painter.java │ │ │ ├── SpanManager.java │ │ │ ├── TextController.java │ │ │ ├── out │ │ │ │ ├── CaretInterface.java │ │ │ │ ├── Config.java │ │ │ │ ├── DisplayInterface.java │ │ │ │ ├── SelectInterface.java │ │ │ │ ├── Status.java │ │ │ │ └── TextInterface.java │ │ │ └── span │ │ │ │ ├── SpanNode.java │ │ │ │ ├── SpanType.java │ │ │ │ └── TextSpanData.java │ │ │ ├── ext │ │ │ ├── AutoComplete.java │ │ │ ├── AutoCompletePanel.java │ │ │ ├── AutoIndent.java │ │ │ ├── ClipboardPanel.java │ │ │ └── ColorScheme.java │ │ │ ├── listener │ │ │ ├── RowListener.java │ │ │ ├── SelectionListener.java │ │ │ └── TextListener.java │ │ │ ├── text │ │ │ ├── cache │ │ │ │ ├── Entry.java │ │ │ │ └── TextCache.java │ │ │ ├── document │ │ │ │ ├── BaseDocument.java │ │ │ │ ├── CommonLanguage.java │ │ │ │ ├── Document.java │ │ │ │ ├── DocumentEdit.java │ │ │ │ ├── Gap.java │ │ │ │ └── WordWrapable.java │ │ │ └── undo │ │ │ │ ├── Undo.java │ │ │ │ ├── UndoManager.java │ │ │ │ └── Undoable.java │ │ │ └── utils │ │ │ ├── CharRange.java │ │ │ ├── EditorException.java │ │ │ ├── Flag.java │ │ │ ├── LinearSearchStrategy.java │ │ │ ├── Pair.java │ │ │ ├── Rectangle.java │ │ │ ├── SearchStrategy.java │ │ │ └── ZoomChecker.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── cursor.png │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.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 │ └── ve │ └── acpp │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeEditor 2 | 文本操作核心部分来自: https://github.com/brnogz/TextWarriorLibrary 3 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.ve.acpp" 7 | minSdkVersion 21 8 | targetSdkVersion 28 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 | compileOptions { 20 | targetCompatibility 1.8 21 | sourceCompatibility 1.8 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation 'com.android.support:appcompat-v7:28.0.0' 28 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 29 | testImplementation 'junit:junit:4.12' 30 | 31 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 32 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 33 | } 34 | -------------------------------------------------------------------------------- /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/ve/acpp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.ve.acpp; 2 | 3 | import android.content.Context; 4 | import android.graphics.Paint; 5 | import android.support.test.InstrumentationRegistry; 6 | import android.support.test.runner.AndroidJUnit4; 7 | 8 | import com.ve.view.editor.MeasureCache; 9 | 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | 13 | import static org.junit.Assert.*; 14 | 15 | /** 16 | * Instrumented test, which will execute on an Android device. 17 | * 18 | * @see Testing documentation 19 | */ 20 | @RunWith(AndroidJUnit4.class) 21 | public class ExampleInstrumentedTest { 22 | @Test 23 | public void useAppContext() { 24 | // Context of the app under test. 25 | Context appContext = InstrumentationRegistry.getTargetContext(); 26 | 27 | MeasureCache cache=new MeasureCache(new Paint()); 28 | assertEquals(cache.getWidth('s'),cache.getWidth('s'),0.001); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/acpp/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.ve.acpp; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.view.View; 6 | 7 | import com.ve.view.editor.Editor; 8 | import com.ve.view.editor.out.Config; 9 | import com.ve.view.editor.out.TextInterface; 10 | import com.ve.view.ext.ClipboardPanel; 11 | 12 | import java.util.Random; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | 16 | Editor editor; 17 | 18 | ClipboardPanel clipboardPanel; 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | editor = findViewById(R.id.activity_main_editor); 24 | Config config=new Config(); 25 | config.showNonPrinting=true; 26 | config.wordWrap=false; 27 | editor.applyConfig(config); 28 | clipboardPanel=new ClipboardPanel(editor); 29 | editor.getOperator().setSelectionChangedListener((active, selStart, selEnd) -> { 30 | if (active){ 31 | clipboardPanel.show(); 32 | }else { 33 | clipboardPanel.hide(); 34 | } 35 | }); 36 | 37 | test(); 38 | } 39 | 40 | private void test() { 41 | StringBuffer stringBuffer = new StringBuffer(); 42 | Random random=new Random(); 43 | for (int i = 0; i < 20; i++) { 44 | if (random.nextBoolean()) { 45 | stringBuffer.append(" asfgweilfuWEF72QWDFYFregargaergnwderghwaefwsefweg数的rnwd文档发给nueffgeriugiesgier\n"); 46 | } 47 | stringBuffer.append(" asfgweilfuWEF72G\nQWDFYF、nwdwefgGAGDGSDFGHAWERGHWERHERREGRAGEsddnwd、nuef\nefgeriugiesgier\n"); 48 | 49 | } 50 | 51 | TextInterface textInterface = editor.getTextInterface(); 52 | textInterface.paste(stringBuffer.toString()); 53 | textInterface.replaceText(0,textInterface.getLength()-1,"Good!"); 54 | } 55 | 56 | public void onRedo(View view) { 57 | editor.getTextInterface().redo(); 58 | } 59 | 60 | public void onUndo(View view) { 61 | editor.getTextInterface().undo(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/lexer/Language.java: -------------------------------------------------------------------------------- 1 | package com.ve.lexer; 2 | 3 | import com.ve.view.text.document.CommonLanguage; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | 8 | 9 | public abstract class Language extends CommonLanguage { 10 | 11 | 12 | 13 | private final static char[] BASIC_C_OPERATORS = { 14 | '(', ')', '{', '}', '.', ',', ';', '=', '+', '-', 15 | '/', '*', '&', '!', '|', ':', '[', ']', '<', '>', 16 | '?', '~', '%', '^' 17 | }; 18 | 19 | 20 | protected HashMap _keywords = new HashMap(0); 21 | protected HashMap _names = new HashMap(0); 22 | protected HashMap _bases = new HashMap(0); 23 | protected HashMap _users = new HashMap(0); 24 | protected HashMap _operators = generateOperators(BASIC_C_OPERATORS); 25 | 26 | private ArrayList _ueserCache = new ArrayList(); 27 | private String[] _userWords = new String[0]; 28 | private String[] _keyword; 29 | private String[] _name; 30 | 31 | public void updateUserWord() { 32 | String[] uw = new String[_ueserCache.size()]; 33 | _userWords = _ueserCache.toArray(uw); 34 | } 35 | 36 | public String[] getUserWord() { 37 | return _userWords; 38 | } 39 | 40 | public String[] getNames() { 41 | return _name; 42 | } 43 | 44 | public String[] getBasePackage(String name) { 45 | return _bases.get(name); 46 | } 47 | 48 | public String[] getKeywords() { 49 | return _keyword; 50 | } 51 | 52 | public void setKeywords(String[] keywords) { 53 | _keyword = keywords; 54 | _keywords = new HashMap(keywords.length); 55 | for (int i = 0; i < keywords.length; ++i) { 56 | _keywords.put(keywords[i], Lexer.KEYWORD); 57 | } 58 | } 59 | 60 | public void setNames(String[] names) { 61 | _name = names; 62 | ArrayList buf = new ArrayList(); 63 | _names = new HashMap(names.length); 64 | for (int i = 0; i < names.length; ++i) { 65 | if (!buf.contains(names[i])) 66 | buf.add(names[i]); 67 | _names.put(names[i], Lexer.NAME); 68 | } 69 | _name = new String[buf.size()]; 70 | buf.toArray(_name); 71 | } 72 | 73 | public void addBasePackage(String name, String[] names) { 74 | _bases.put(name, names); 75 | } 76 | 77 | public void clearUserWord() { 78 | _ueserCache.clear(); 79 | _users.clear(); 80 | } 81 | 82 | public void addUserWord(String name) { 83 | if (!_ueserCache.contains(name) && !_names.containsKey(name)) 84 | _ueserCache.add(name); 85 | _users.put(name, Lexer.NAME); 86 | } 87 | 88 | protected void setOperators(char[] operators) { 89 | _operators = generateOperators(operators); 90 | } 91 | 92 | private HashMap generateOperators(char[] operators) { 93 | HashMap operatorsMap = new HashMap(operators.length); 94 | for (int i = 0; i < operators.length; ++i) { 95 | operatorsMap.put(operators[i], Lexer.OPERATOR); 96 | } 97 | return operatorsMap; 98 | } 99 | 100 | public final boolean isOperator(char c) { 101 | return _operators.containsKey(Character.valueOf(c)); 102 | } 103 | 104 | public final boolean isKeyword(String s) { 105 | return _keywords.containsKey(s); 106 | } 107 | 108 | public final boolean isName(String s) { 109 | return _names.containsKey(s); 110 | } 111 | 112 | public final boolean isBasePackage(String s) { 113 | return _bases.containsKey(s); 114 | } 115 | 116 | public final boolean isBaseWord(String p, String s) { 117 | String[] pkg = _bases.get(p); 118 | for (String n : pkg) { 119 | if (n.equals(s)) 120 | return true; 121 | } 122 | return false; 123 | } 124 | 125 | public final boolean isUserWord(String s) { 126 | return _users.containsKey(s); 127 | } 128 | 129 | private boolean contains(String[] a, String s) { 130 | for (String n : a) { 131 | if (n.equals(s)) 132 | return true; 133 | } 134 | return false; 135 | } 136 | 137 | private boolean contains(ArrayList a, String s) { 138 | for (String n : a) { 139 | if (n.equals(s)) 140 | return true; 141 | } 142 | return false; 143 | } 144 | 145 | 146 | 147 | public boolean isSentenceTerminator(char c) { 148 | return (c == '.'); 149 | } 150 | 151 | public boolean isEscapeChar(char c) { 152 | return (c == '\\'); 153 | } 154 | 155 | public boolean isProgLang() { 156 | return true; 157 | } 158 | 159 | public boolean isWordStart(char c) { 160 | return false; 161 | } 162 | 163 | public boolean isDelimiterA(char c) { 164 | return (c == '"'); 165 | } 166 | 167 | 168 | public boolean isDelimiterB(char c) { 169 | return (c == '\''); 170 | } 171 | 172 | 173 | public boolean isLineAStart(char c) { 174 | return (c == '#'); 175 | } 176 | 177 | 178 | public boolean isLineBStart(char c) { 179 | return false; 180 | } 181 | 182 | public boolean isLineStart(char c0, char c1) { 183 | return (c0 == '/' && c1 == '/'); 184 | } 185 | 186 | public boolean isMultilineStartDelimiter(char c0, char c1) { 187 | return (c0 == '/' && c1 == '*'); 188 | } 189 | 190 | 191 | public boolean isMultilineEndDelimiter(char c0, char c1) { 192 | return (c0 == '*' && c1 == '/'); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/lexer/LanguageC.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Tah Wei Hoon. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License Version 2.0, 5 | * with full text available at http://www.apache.org/licenses/LICENSE-2.0.html 6 | * 7 | * This software is provided "as is". Use at your own risk. 8 | */ 9 | package com.ve.lexer; 10 | 11 | /** 12 | * Singleton class containing the symbols and operators of the C language 13 | */ 14 | public class LanguageC extends Language{ 15 | private static Language 16 | _theOne = null; 17 | 18 | private final static String[] keywords = { 19 | "char", "double", "float", "int", "long", "short", "void", 20 | "auto", "const", "extern", "register", "static", "volatile", 21 | "signed", "unsigned", "sizeof", "typedef", 22 | "enum", "struct", "union", 23 | "break", "case", "continue", "default", "do", "else", "for", 24 | "goto", "if", "return", "switch", "while" 25 | }; 26 | 27 | public static Language getInstance(){ 28 | if(_theOne == null){ 29 | _theOne = new LanguageC(); 30 | } 31 | return _theOne; 32 | } 33 | 34 | private LanguageC(){ 35 | super.setKeywords(keywords); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/lexer/LanguageLua.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Tah Wei Hoon. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License Version 2.0, 5 | * with full text available at http://www.apache.org/licenses/LICENSE-2.0.html 6 | * 7 | * This software is provided "as is". Use at your own risk. 8 | */ 9 | package com.ve.lexer; 10 | 11 | /** 12 | * Singleton class containing the symbols and operators of the Javascript language 13 | */ 14 | public class LanguageLua extends Language { 15 | private static Language _theOne = null; 16 | 17 | //private final static String functionTarget = "_ENV|_G|_VERSION|assert|collectgarbage|coroutine|create|isyieldable|resume|running|status|wrap|yield|debug|gethook|getinfo|getlocal|getmetatable|getregistry|getupvalue|getuservalue|sethook|setlocal|setmetatable|setupvalue|setuservalue|traceback|upvalueid|upvaluejoin|dofile|error|getfenv|getmetatable|io|close|flush|input|lines|open|output|popen|read|stderr|stdin|stdout|tmpfile|type|write|ipairs|load|loadfile|loadstring|luajava|bindClass|clear|coding|createArray|createProxy|instanceof|loadLib|loaded|luapath|new|newInstance|package|math|abs|acos|asin|atan|atan2|ceil|cos|cosh|deg|exp|floor|fmod|frexp|huge|ldexp|log|log10|max|maxinteger|min|mininteger|modf|pi|pow|rad|random|randomseed|sin|sinh|sqrt|tan|tanh|tointeger|type|ult|module|next|os|clock|date|difftime|execute|exit|getenv|remove|rename|setlocale|time|tmpname|package|config|cpath|loaded|loaders|loadlib|path|preload|searchers|searchpath|seeall|pairs|pcall|print|rawequal|rawget|rawlen|rawset|require|select|setfenv|setmetatable|string|byte|char|dump|find|format|gfind|gmatch|gsub|len|lower|match|pack|packsize|rep|reverse|sub|unpack|upper|table|concat|foreach|foreachi|insert|maxn|move|pack|remove|sort|unpack|tonumber|tostring|type|unpack|char|charpattern|utf8|codepoint|codes|len|offset|xpcall"; 18 | //private final static String functionTarget = "_ENV|_G|_VERSION|assert|collectgarbage|coroutine.create|coroutine.isyieldable|coroutine.resume|coroutine.running|coroutine.status|coroutine.wrap|coroutine.yield|debug.debug|debug.gethook|debug.getinfo|debug.getlocal|debug.getmetatable|debug.getregistry|debug.getupvalue|debug.getuservalue|debug.sethook|debug.setlocal|debug.setmetatable|debug.setupvalue|debug.setuservalue|debug.traceback|debug.upvalueid|debug.upvaluejoin|dofile|error|getfenv|getmetatable|io.close|io.flush|io.input|io.lines|io.open|io.output|io.popen|io.read|io.stderr|io.stdin|io.stdout|io.tmpfile|io.type|io.write|ipairs|load|loadfile|loadstring|luajava.bindClass|luajava.clear|luajava.coding|luajava.createArray|luajava.createProxy|luajava.instanceof|luajava.loadLib|luajava.loaded|luajava.luapath|luajava.new|luajava.newInstance|luajava.package|math.abs|math.acos|math.asin|math.atan|math.atan2|math.ceil|math.cos|math.cosh|math.deg|math.exp|math.floor|math.fmod|math.frexp|math.huge|math.ldexp|math.log|math.log10|math.max|math.maxinteger|math.min|math.mininteger|math.modf|math.pi|math.pow|math.rad|math.random|math.randomseed|math.sin|math.sinh|math.sqrt|math.tan|math.tanh|math.tointeger|math.type|math.ult|module|next|os.clock|os.date|os.difftime|os.execute|os.exit|os.getenv|os.remove|os.rename|os.setlocale|os.time|os.tmpname|package.config|package.cpath|package.loaded|package.loaders|package.loadlib|package.path|package.preload|package.searchers|package.searchpath|package.seeall|pairs|pcall|print|rawequal|rawget|rawlen|rawset|require|select|setfenv|setmetatable|string.byte|string.char|string.dump|string.find|string.format|string.gfind|string.gmatch|string.gsub|string.len|string.lower|string.match|string.pack|string.packsize|string.rep|string.reverse|string.sub|string.unpack|string.upper|table.concat|table.foreach|table.foreachi|table.insert|table.maxn|table.move|table.pack|table.remove|table.sort|table.unpack|tonumber|tostring|type|unpack|utf8.char|utf8.charpattern|utf8.codepoint|utf8.codes|utf8.len|utf8.offset|xpcall"; 19 | 20 | private final static String keywordTarget ="and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while"; 21 | private final static String globalTarget="__add|__band|__bnot|__bor|__bxor|__call|__concat|__div|__eq|__idiv|__index|__le|__len|__lt|__mod|__mul|__newindex|__pow|__shl|__shr|__sub|__unm|_ENV|_G|assert|collectgarbage|dofile|error|findtable|getmetatable|ipairs|load|loadfile|loadstring|module|next|pairs|pcall|print|rawequal|rawget|rawlen|rawset|require|select|self|setmetatable|tointeger|tonumber|tostring|type|unpack|xpcall"; 22 | 23 | private final static String packageName="coroutine|debug|io|luajava|math|os|package|string|table|utf8"; 24 | private final static String package_coroutine = "create|isyieldable|resume|running|status|wrap|yield"; 25 | private final static String package_debug = "debug|gethook|getinfo|getlocal|getmetatable|getregistry|getupvalue|getuservalue|sethook|setlocal|setmetatable|setupvalue|setuservalue|traceback|upvalueid|upvaluejoin"; 26 | private final static String package_io = "close|flush|input|lines|open|output|popen|read|stderr|stdin|stdout|tmpfile|type|write"; 27 | private final static String package_luajava = "astable|bindClass|clear|coding|createArray|createProxy|instanceof|loadLib|loaded|luapath|new|newInstance|package|tostring"; 28 | private final static String package_math = "abs|acos|asin|atan|atan2|ceil|cos|cosh|deg|exp|floor|fmod|frexp|huge|ldexp|log|log10|max|maxinteger|min|mininteger|modf|pi|pow|rad|random|randomseed|sin|sinh|sqrt|tan|tanh|tointeger|type|ult"; 29 | private final static String package_os = "clock|date|difftime|execute|exit|getenv|remove|rename|setlocale|time|tmpname"; 30 | private final static String package_package = "config|cpath|loaded|loaders|loadlib|path|preload|searchers|searchpath|seeall"; 31 | private final static String package_string = "byte|char|dump|find|format|gfind|gmatch|gsub|len|lower|match|pack|packsize|rep|reverse|sub|unpack|upper"; 32 | private final static String package_table = "concat|foreach|foreachi|insert|maxn|move|pack|remove|sort|unpack"; 33 | private final static String package_utf8 = "char|charpattern|codepoint|codes|len|offset"; 34 | private final static String extFunctionTarget="activity|call|compile|dump|each|enum|import|loadbitmap|loadlayout|loadmenu|set|task|thread|timer"; 35 | private final static String functionTarget = globalTarget+"|"+extFunctionTarget+"|"+packageName;; 36 | private final static String[] keywords = keywordTarget.split("\\|"); 37 | 38 | private final static String[] names = functionTarget.split("\\|"); 39 | 40 | private final static char[] LUA_OPERATORS = { 41 | '(', ')', '{', '}', ',', ';', '=', '+', '-', 42 | '/', '*', '&', '!', '|', ':', '[', ']', '<', '>', 43 | '?', '~', '%', '^' 44 | }; 45 | public static Language getInstance(){ 46 | if(_theOne == null){ 47 | _theOne = new LanguageLua(); 48 | } 49 | return _theOne; 50 | } 51 | 52 | private LanguageLua(){ 53 | super.setOperators(LUA_OPERATORS); 54 | super.setKeywords(keywords); 55 | super.setNames(names); 56 | super.addBasePackage("io",package_io.split("\\|")); 57 | super.addBasePackage("string",package_string.split("\\|")); 58 | super.addBasePackage("luajava",package_luajava.split("\\|")); 59 | super.addBasePackage("os",package_os.split("\\|")); 60 | super.addBasePackage("table",package_table.split("\\|")); 61 | super.addBasePackage("math",package_math.split("\\|")); 62 | super.addBasePackage("utf8",package_utf8.split("\\|")); 63 | super.addBasePackage("coroutine",package_coroutine.split("\\|")); 64 | super.addBasePackage("package",package_package.split("\\|")); 65 | super.addBasePackage("debug",package_debug.split("\\|")); 66 | } 67 | 68 | /** 69 | * Whether the word after c is a token 70 | */ 71 | public boolean isWordStart2(char c){ 72 | return (c=='.'); 73 | } 74 | 75 | public boolean isLineAStart(char c){ 76 | return false; 77 | } 78 | 79 | /** 80 | * Whether c0c1L is a token, where L is a sequence of characters until the end of the line 81 | */ 82 | public boolean isLineStart(char c0, char c1){ 83 | return (c0 == '-' && c1 == '-'); 84 | } 85 | 86 | /** 87 | * Whether c0c1 signifies the start of a multi-line token 88 | */ 89 | public boolean isMultilineStartDelimiter(char c0, char c1){ 90 | return (c0 == '[' && c1 == '['); 91 | } 92 | 93 | /** 94 | * Whether c0c1 signifies the end of a multi-line token 95 | */ 96 | public boolean isMultilineEndDelimiter(char c0, char c1){ 97 | return (c0 == ']' && c1 == ']'); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/lexer/LuaTokenTypes.java: -------------------------------------------------------------------------------- 1 | package com.ve.lexer; 2 | 3 | /** 4 | * Created by zy on 2015/8/26. 5 | */ 6 | public enum LuaTokenTypes { 7 | WRONG, 8 | NL_BEFORE_LONGSTRING, 9 | WS, 10 | NEWLINE, 11 | SHEBANG, 12 | LONGCOMMENT, 13 | SHORTCOMMENT, 14 | LUADOC_COMMENT, 15 | LONGCOMMENT_BEGIN, 16 | LONGCOMMENT_END, 17 | NAME, 18 | NUMBER, 19 | STRING, 20 | LONGSTRING, 21 | LONGSTRING_BEGIN, 22 | LONGSTRING_END, 23 | UNTERMINATED_STRING, 24 | DIV, 25 | MULT, 26 | LPAREN, 27 | RPAREN, 28 | LBRACK, 29 | RBRACK, 30 | LCURLY, 31 | RCURLY, 32 | COLON, 33 | COMMA, 34 | DOT, 35 | ASSIGN, 36 | SEMI, 37 | EQ, 38 | NE, 39 | PLUS, 40 | MINUS, 41 | GE, 42 | GT, 43 | EXP, 44 | LE, 45 | LT, 46 | ELLIPSIS, 47 | CONCAT, 48 | GETN, 49 | MOD, 50 | 51 | IF, 52 | ELSE, 53 | ELSEIF, 54 | WHILE, 55 | WITH, 56 | THEN, 57 | FOR, 58 | IN, 59 | RETURN, 60 | BREAK, 61 | CONTINUE, 62 | TRUE, 63 | FALSE, 64 | NIL, 65 | FUNCTION, 66 | DO, 67 | NOT, 68 | AND, 69 | OR, 70 | LOCAL, 71 | REPEAT, 72 | UNTIL, 73 | END 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/utils/AnimUtils.java: -------------------------------------------------------------------------------- 1 | package com.ve.utils; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ValueAnimator; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | public class AnimUtils { 10 | private static final String TAG = AnimUtils.class.getSimpleName(); 11 | public static void fadeOut(View view, boolean gone) { 12 | if (view != null) { 13 | view.animate().alpha(0).setDuration(400).setListener(new AnimatorListenerAdapter() { 14 | @Override 15 | public void onAnimationEnd(Animator animation) { 16 | view.setVisibility(gone ? View.GONE : View.INVISIBLE); 17 | } 18 | }).start(); 19 | } 20 | } 21 | 22 | public static void fadeIn(View view) { 23 | if (view != null) { 24 | view.setVisibility(View.VISIBLE); 25 | view.animate().alpha(0).setDuration(400).start(); 26 | } 27 | } 28 | public static void scaleUpOut(View view, boolean gone) { 29 | if (view != null) { 30 | 31 | ValueAnimator valueAnimator=ValueAnimator.ofInt(view.getHeight(),0); 32 | valueAnimator.addUpdateListener(animation -> { 33 | Integer value= (Integer) animation.getAnimatedValue(); 34 | ViewGroup.LayoutParams mparams=view.getLayoutParams(); 35 | mparams.height= value; 36 | view.setLayoutParams(mparams); 37 | 38 | }); 39 | valueAnimator.addListener(new AnimatorListenerAdapter() { 40 | @Override 41 | public void onAnimationEnd(Animator animation) { 42 | view.setVisibility(gone ? View.GONE : View.INVISIBLE); 43 | } 44 | }); 45 | valueAnimator.setDuration(600); 46 | valueAnimator.start(); 47 | } 48 | } 49 | 50 | public static void scaleUpIn(View view) { 51 | if (view != null) { 52 | view.setVisibility(View.VISIBLE); 53 | ValueAnimator valueAnimator=ValueAnimator.ofInt(0,view.getMeasuredHeight()); 54 | valueAnimator.addUpdateListener(animation -> { 55 | Integer value= (Integer) animation.getAnimatedValue(); 56 | ViewGroup.LayoutParams mparams=view.getLayoutParams(); 57 | mparams.height= value; 58 | view.setLayoutParams(mparams); 59 | 60 | }); 61 | valueAnimator.setDuration(600); 62 | valueAnimator.start(); 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/utils/DisplayUtils.java: -------------------------------------------------------------------------------- 1 | package com.ve.utils; 2 | 3 | import android.content.Context; 4 | 5 | 6 | public class DisplayUtils { 7 | 8 | public static int px2dp(Context context, float pxValue) { 9 | final float scale = context.getResources().getDisplayMetrics().density; 10 | return (int) (pxValue / scale + 0.5f); 11 | } 12 | 13 | public static int dp2px(Context context, float dpValue) { 14 | float scale = context.getResources().getDisplayMetrics().density; 15 | return (int) (dpValue * scale + 0.5f); 16 | } 17 | 18 | public static int px2sp(Context context, float pxValue) { 19 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 20 | return (int) (pxValue / fontScale + 0.5f); 21 | } 22 | 23 | public static int sp2px(Context context, float spValue) { 24 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 25 | return (int) (spValue * fontScale + 0.5f); 26 | } 27 | 28 | /* public static int dp2px(float dpValue) { 29 | float scale = App.getApplication().getResources().getDisplayMetrics().density; 30 | return (int) (dpValue * scale + 0.5f); 31 | 32 | } 33 | public static int sp2px( float spValue) { 34 | final float fontScale = App.getApplication().getResources().getDisplayMetrics().scaledDensity; 35 | return (int) (spValue * fontScale + 0.5f); 36 | }*/ 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ve/utils/GraphUtils.java: -------------------------------------------------------------------------------- 1 | package com.ve.utils; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.graphics.Paint; 6 | import android.graphics.RectF; 7 | 8 | public class GraphUtils { 9 | public static int alphaColor(int alpha,int color){ 10 | return Color.argb(alpha, Color.red(color),Color.green(color),Color.blue(color)); 11 | } 12 | public static int alphaColor(float alpha,int color){ 13 | return Color.argb((int)(alpha* Color.alpha(color)), Color.red(color),Color.green(color),Color.blue(color)); 14 | } 15 | public static void drawCenterText(Canvas canvas, char c, RectF rect, Paint paint) { 16 | Paint.FontMetrics fontMetrics = paint.getFontMetrics(); 17 | float top = fontMetrics.top; 18 | float bottom = fontMetrics.bottom; 19 | int baseLineY = (int) (rect.centerY() - top / 2 - bottom / 2); 20 | canvas.drawText(new char[]{c},0,1, rect.centerX(), baseLineY, paint); 21 | } 22 | public static void drawCenterText(Canvas canvas, String text, RectF rect, Paint paint) { 23 | Paint.FontMetrics fontMetrics = paint.getFontMetrics(); 24 | float top = fontMetrics.top; 25 | float bottom = fontMetrics.bottom; 26 | int baseLineY = (int) (rect.centerY() - top / 2 - bottom / 2); 27 | canvas.drawText(text,0,text.length(), rect.centerX(), baseLineY, paint); 28 | } 29 | 30 | public static int grandColor(int start, int end, float per) { 31 | int red = (int) (Color.red(start) + (Color.red(end) - Color.red(start)) * per); 32 | int blue = (int) (Color.blue(start) + (Color.blue(end) - Color.blue(start)) * per); 33 | int green = (int) (Color.green(start) + (Color.green(end) - Color.green(start)) * per); 34 | return Color.rgb(red, green, blue); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/utils/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package com.ve.utils; 2 | 3 | public class JsonUtils { 4 | 5 | public static String formatJson(String jsonStr) { 6 | if (null == jsonStr || "".equals(jsonStr)) { 7 | return ""; 8 | } 9 | StringBuilder sb = new StringBuilder(); 10 | char last = '\0'; 11 | char current = '\0'; 12 | int indent = 0; 13 | boolean isInQuotationMarks = false; 14 | for (int i = 0; i < jsonStr.length(); i++) { 15 | last = current; 16 | current = jsonStr.charAt(i); 17 | switch (current) { 18 | case '"': 19 | if (last != '\\') { 20 | isInQuotationMarks = !isInQuotationMarks; 21 | } 22 | sb.append(current); 23 | break; 24 | case '{': 25 | case '[': 26 | sb.append(current); 27 | if (!isInQuotationMarks) { 28 | sb.append('\n'); 29 | indent++; 30 | addIndentBlank(sb, indent); 31 | } 32 | break; 33 | case '}': 34 | case ']': 35 | if (!isInQuotationMarks) { 36 | sb.append('\n'); 37 | indent--; 38 | addIndentBlank(sb, indent); 39 | } 40 | sb.append(current); 41 | break; 42 | case ',': 43 | sb.append(current); 44 | if (last != '\\' && !isInQuotationMarks) { 45 | sb.append('\n'); 46 | addIndentBlank(sb, indent); 47 | } 48 | break; 49 | default: 50 | sb.append(current); 51 | } 52 | } 53 | 54 | return sb.toString(); 55 | } 56 | 57 | 58 | private static void addIndentBlank(StringBuilder sb, int indent) { 59 | for (int i = 0; i < indent; i++) { 60 | sb.append('\t'); 61 | } 62 | } 63 | 64 | 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ve/utils/LightStatusBarUtils.java: -------------------------------------------------------------------------------- 1 | package com.ve.utils; 2 | 3 | import android.app.Activity; 4 | import android.view.View; 5 | import android.view.Window; 6 | import android.view.WindowManager; 7 | 8 | import java.lang.reflect.Field; 9 | import java.lang.reflect.Method; 10 | 11 | public class LightStatusBarUtils { 12 | 13 | public static void setLightStatusBar(Activity activity, boolean dark) { 14 | switch (RomUtils.getLightStatausBarAvailableRomType()) { 15 | case RomUtils.AvailableRomType.MIUI: 16 | setMIUILightStatusBar(activity, dark); 17 | break; 18 | 19 | case RomUtils.AvailableRomType.FLYME: 20 | setFlymeLightStatusBar(activity, dark); 21 | break; 22 | 23 | case RomUtils.AvailableRomType.ANDROID_NATIVE: 24 | setAndroidNativeLightStatusBar(activity, dark); 25 | break; 26 | 27 | case RomUtils.AvailableRomType.NA: 28 | // N/A do nothing 29 | break; 30 | } 31 | } 32 | 33 | private static boolean setMIUILightStatusBar(Activity activity, boolean darkmode) { 34 | Class clazz = activity.getWindow().getClass(); 35 | try { 36 | int darkModeFlag = 0; 37 | Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); 38 | Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); 39 | darkModeFlag = field.getInt(layoutParams); 40 | Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); 41 | extraFlagField.invoke(activity.getWindow(), darkmode ? darkModeFlag : 0, darkModeFlag); 42 | return true; 43 | } catch (Exception e) { 44 | e.printStackTrace(); 45 | } 46 | return false; 47 | } 48 | 49 | private static boolean setFlymeLightStatusBar(Activity activity, boolean dark) { 50 | boolean result = false; 51 | if (activity != null) { 52 | try { 53 | WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); 54 | Field darkFlag = WindowManager.LayoutParams.class 55 | .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON"); 56 | Field meizuFlags = WindowManager.LayoutParams.class 57 | .getDeclaredField("meizuFlags"); 58 | darkFlag.setAccessible(true); 59 | meizuFlags.setAccessible(true); 60 | int bit = darkFlag.getInt(null); 61 | int value = meizuFlags.getInt(lp); 62 | if (dark) { 63 | value |= bit; 64 | } else { 65 | value &= ~bit; 66 | } 67 | meizuFlags.setInt(lp, value); 68 | activity.getWindow().setAttributes(lp); 69 | result = true; 70 | } catch (Exception e) { 71 | } 72 | } 73 | return result; 74 | } 75 | 76 | private static void setAndroidNativeLightStatusBar(Activity activity, boolean dark) { 77 | View decor = activity.getWindow().getDecorView(); 78 | if (dark) { 79 | decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); 80 | } else { 81 | // We want to change tint color to white again. 82 | // You can also record the flags in advance so that you can turn UI back completely if 83 | // you have set other flags before, such as translucent or full screen. 84 | decor.setSystemUiVisibility(0); 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/utils/LocationUtils.java: -------------------------------------------------------------------------------- 1 | package com.ve.utils; 2 | 3 | import android.Manifest; 4 | import android.content.Context; 5 | import android.content.pm.PackageManager; 6 | import android.location.Location; 7 | import android.location.LocationListener; 8 | import android.location.LocationManager; 9 | import android.os.Build; 10 | import android.os.Bundle; 11 | import android.support.v4.app.ActivityCompat; 12 | import android.util.Log; 13 | 14 | import java.util.List; 15 | 16 | public class LocationUtils { 17 | private static final String TAG = LocationUtils.class.getSimpleName(); 18 | private volatile static LocationUtils uniqueInstance; 19 | private LocationManager locationManager; 20 | private String locationProvider; 21 | private Location location; 22 | private Context mContext; 23 | 24 | 25 | private LocationUtils(Context context) { 26 | mContext = context; 27 | getLocation(); 28 | } 29 | 30 | public static LocationUtils getInstance(Context context) { 31 | if (uniqueInstance == null) { 32 | synchronized (LocationUtils.class) { 33 | if (uniqueInstance == null) { 34 | uniqueInstance = new LocationUtils(context); 35 | } 36 | } 37 | } 38 | return uniqueInstance; 39 | } 40 | 41 | 42 | private void getLocation() { 43 | if (mContext==null){ 44 | Log.w(TAG, "getLocation: mContext==null" ); 45 | return; 46 | } 47 | locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); 48 | List providers = locationManager.getProviders(true); 49 | if (providers.contains(LocationManager.NETWORK_PROVIDER)) { 50 | Log.d(TAG, "网络定位"); 51 | locationProvider = LocationManager.NETWORK_PROVIDER; 52 | } else if (providers.contains(LocationManager.GPS_PROVIDER)) { 53 | Log.d(TAG, "GPS定位"); 54 | locationProvider = LocationManager.GPS_PROVIDER; 55 | } else { 56 | Log.d(TAG, "没有可用的位置提供器"); 57 | return; 58 | } 59 | if (Build.VERSION.SDK_INT >= 23 && 60 | ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && 61 | ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { 62 | return; 63 | } 64 | if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { 65 | return; 66 | } 67 | Location location = locationManager.getLastKnownLocation(locationProvider); 68 | if (location != null) { 69 | setLocation(location); 70 | } 71 | // 监视地理位置变化,第二个和第三个参数分别为更新的最短时间minTime和最短距离minDistace 72 | locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener); 73 | } 74 | 75 | 76 | private void setLocation(Location location) { 77 | this.location = location; 78 | String address = "纬度:" + location.getLatitude() + "经度:" + location.getLongitude(); 79 | Log.d(TAG, address); 80 | } 81 | 82 | 83 | //获取经纬度 84 | public Location showLocation() { 85 | return location; 86 | } 87 | 88 | public String getLocationText() { 89 | if (location==null){ 90 | return "0,0"; 91 | }else { 92 | return location.getLatitude() + "," + location.getLongitude(); 93 | } 94 | } 95 | 96 | 97 | // 移除定位监听 98 | public void removeLocationUpdatesListener() { 99 | if (Build.VERSION.SDK_INT >= 23 && 100 | ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && 101 | ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { 102 | return; 103 | } 104 | if (locationManager != null) { 105 | uniqueInstance = null; 106 | locationManager.removeUpdates(locationListener); 107 | } 108 | } 109 | 110 | LocationListener locationListener = new LocationListener() { 111 | @Override 112 | public void onStatusChanged(String provider, int status, Bundle arg2) { 113 | 114 | } 115 | 116 | @Override 117 | public void onProviderEnabled(String provider) { 118 | 119 | } 120 | 121 | @Override 122 | public void onProviderDisabled(String provider) { 123 | 124 | } 125 | @Override 126 | public void onLocationChanged(Location location) { 127 | location.getAccuracy(); 128 | setLocation(location); 129 | } 130 | }; 131 | 132 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ve/utils/MD5.java: -------------------------------------------------------------------------------- 1 | package com.ve.utils; 2 | 3 | import java.security.MessageDigest; 4 | 5 | public class MD5 { 6 | 7 | private MD5() {} 8 | 9 | public final static String getMessageDigest(byte[] buffer) { 10 | char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 11 | try { 12 | MessageDigest mdTemp = MessageDigest.getInstance("MD5"); 13 | mdTemp.update(buffer); 14 | byte[] md = mdTemp.digest(); 15 | int j = md.length; 16 | char str[] = new char[j * 2]; 17 | int k = 0; 18 | for (int i = 0; i < j; i++) { 19 | byte byte0 = md[i]; 20 | str[k++] = hexDigits[byte0 >>> 4 & 0xf]; 21 | str[k++] = hexDigits[byte0 & 0xf]; 22 | } 23 | return new String(str); 24 | } catch (Exception e) { 25 | return null; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/utils/RomUtils.java: -------------------------------------------------------------------------------- 1 | package com.ve.utils; 2 | 3 | import android.os.Build; 4 | import android.text.TextUtils; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | 10 | public class RomUtils { 11 | 12 | class AvailableRomType { 13 | public static final int MIUI = 1; 14 | public static final int FLYME = 2; 15 | public static final int ANDROID_NATIVE = 3; 16 | public static final int NA = 4; 17 | } 18 | 19 | public static boolean isLightStatusBarAvailable () { 20 | if (isMIUIV6OrAbove() || isFlymeV4OrAbove() || isAndroidMOrAbove()) { 21 | return true; 22 | } 23 | return false; 24 | } 25 | 26 | public static int getLightStatausBarAvailableRomType() { 27 | if (isMIUIV6OrAbove()) { 28 | return AvailableRomType.MIUI; 29 | } 30 | 31 | if (isFlymeV4OrAbove()) { 32 | return AvailableRomType.FLYME; 33 | } 34 | 35 | if (isAndroidMOrAbove()) { 36 | return AvailableRomType.ANDROID_NATIVE; 37 | } 38 | 39 | return AvailableRomType.NA; 40 | } 41 | 42 | //Flyme V4的displayId格式为 [Flyme OS 4.x.x.xA] 43 | //Flyme V5的displayId格式为 [Flyme 5.x.x.x beta] 44 | private static boolean isFlymeV4OrAbove() { 45 | String displayId = Build.DISPLAY; 46 | if (!TextUtils.isEmpty(displayId) && displayId.contains("Flyme")) { 47 | String[] displayIdArray = displayId.split(" "); 48 | for (String temp : displayIdArray) { 49 | //版本号4以上,形如4.x. 50 | if (temp.matches("^[4-9]\\.(\\d+\\.)+\\S*")) { 51 | return true; 52 | } 53 | } 54 | } 55 | return false; 56 | } 57 | 58 | //MIUI V6对应的versionCode是4 59 | //MIUI V7对应的versionCode是5 60 | private static boolean isMIUIV6OrAbove() { 61 | String miuiVersionCodeStr = getSystemProperty("ro.miui.ui.version.code"); 62 | if (!TextUtils.isEmpty(miuiVersionCodeStr)) { 63 | try { 64 | int miuiVersionCode = Integer.parseInt(miuiVersionCodeStr); 65 | if (miuiVersionCode >= 4) { 66 | return true; 67 | } 68 | } catch (Exception e) {} 69 | } 70 | return false; 71 | } 72 | 73 | //Android Api 23以上 74 | private static boolean isAndroidMOrAbove() { 75 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 76 | return true; 77 | } 78 | return false; 79 | } 80 | 81 | private static String getSystemProperty(String propName) { 82 | String line; 83 | BufferedReader input = null; 84 | try { 85 | Process p = Runtime.getRuntime().exec("getprop " + propName); 86 | input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); 87 | line = input.readLine(); 88 | input.close(); 89 | } catch (IOException ex) { 90 | return null; 91 | } finally { 92 | if (input != null) { 93 | try { 94 | input.close(); 95 | } catch (IOException e) { 96 | } 97 | } 98 | } 99 | return line; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/utils/SysUtils.java: -------------------------------------------------------------------------------- 1 | package com.ve.utils; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.net.ConnectivityManager; 6 | import android.net.NetworkInfo; 7 | import android.view.View; 8 | import android.view.inputmethod.InputMethodManager; 9 | 10 | public class SysUtils { 11 | public static void showInputMethod(View view, boolean show) { 12 | InputMethodManager im = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 13 | if (show) { 14 | im.showSoftInput(view, 0); 15 | im.restartInput(view); 16 | } else { 17 | im.hideSoftInputFromWindow(view.getWindowToken(), 0); 18 | 19 | } 20 | } 21 | 22 | public static boolean isInputMethodShow(View view) { 23 | InputMethodManager im = (InputMethodManager) view.getContext() 24 | .getSystemService(Context.INPUT_METHOD_SERVICE); 25 | return im.isActive(); 26 | } 27 | 28 | public static boolean isNetworkAvailable(Context context) { 29 | ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 30 | 31 | if (connectivityManager == null) { 32 | return false; 33 | } else { 34 | @SuppressLint("MissingPermission") NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo(); 35 | if (networkInfo != null && networkInfo.length > 0) { 36 | for (int i = 0; i < networkInfo.length; i++) { 37 | // 判断当前网络状态是否为连接状态 38 | if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED) { 39 | return true; 40 | } 41 | } 42 | } 43 | } 44 | return false; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/Base.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor; 2 | 3 | import com.ve.view.editor.out.Config; 4 | import com.ve.view.text.document.Document; 5 | 6 | public class Base { 7 | protected Editor editor; 8 | protected TextController controller; 9 | protected EditorInputConnection inputConnection; 10 | protected Document document; 11 | protected Caret caret; 12 | protected Painter painter; 13 | protected ListenManager operator; 14 | protected SpanManager spanManager; 15 | protected Config config; 16 | 17 | public Base(Editor editor){ 18 | this.editor=editor; 19 | } 20 | public void init(){ 21 | document=editor.document; 22 | controller=editor.controller; 23 | inputConnection=editor.inputConnection; 24 | caret=editor.caret; 25 | painter =editor.painter; 26 | operator=editor.operator; 27 | spanManager=editor.spanManager; 28 | config=editor.config; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/Caret.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor; 2 | 3 | import android.view.MotionEvent; 4 | 5 | import com.ve.view.utils.EditorException; 6 | import com.ve.view.editor.out.CaretInterface; 7 | 8 | public class Caret extends Base implements CaretInterface{ 9 | protected final static long SCROLL_PERIOD = 250; 10 | 11 | protected final static int SCROLL_UP = 0; 12 | protected final static int SCROLL_DOWN = 1; 13 | protected final static int SCROLL_LEFT = 2; 14 | protected final static int SCROLL_RIGHT = 3; 15 | 16 | protected static final int SCROLL_EDGE = 10; 17 | 18 | protected int position = 0, row = 0; 19 | protected int x, y; 20 | 21 | protected boolean touching; 22 | 23 | public Caret(Editor editor) { 24 | super(editor); 25 | } 26 | 27 | 28 | public boolean isTouching() { 29 | return touching; 30 | } 31 | 32 | public boolean autoScroll(int aspect) { 33 | boolean scrolled = false; 34 | switch (aspect) { 35 | case SCROLL_UP: 36 | editor.removeCallbacks(scrollUpTask); 37 | if ((!onFirstRow())) { 38 | editor.post(scrollUpTask); 39 | scrolled = true; 40 | } 41 | break; 42 | case SCROLL_DOWN: 43 | editor.removeCallbacks(scrollDownTask); 44 | if (!onLastRow()) { 45 | editor.post(scrollDownTask); 46 | scrolled = true; 47 | } 48 | break; 49 | case SCROLL_LEFT: 50 | editor.removeCallbacks(scrollLeftTask); 51 | if (!onStart() && row == document.gainRowOfPosition(position - 1)) { 52 | editor.post(scrollLeftTask); 53 | scrolled = true; 54 | } 55 | break; 56 | case SCROLL_RIGHT: 57 | editor.removeCallbacks(scrollRightTask); 58 | if (!onEnd() && row == document.gainRowOfPosition(position + 1)) { 59 | editor.post(scrollRightTask); 60 | scrolled = true; 61 | } 62 | break; 63 | default: 64 | EditorException.fail("Invalid scroll direction"); 65 | break; 66 | } 67 | return scrolled; 68 | } 69 | 70 | public void stopAutoScroll() { 71 | editor.removeCallbacks(scrollDownTask); 72 | editor.removeCallbacks(scrollUpTask); 73 | editor.removeCallbacks(scrollLeftTask); 74 | editor.removeCallbacks(scrollRightTask); 75 | } 76 | 77 | public void stopAutoScroll(int aspect) { 78 | switch (aspect) { 79 | case SCROLL_UP: 80 | editor.removeCallbacks(scrollUpTask); 81 | break; 82 | case SCROLL_DOWN: 83 | editor.removeCallbacks(scrollDownTask); 84 | break; 85 | case SCROLL_LEFT: 86 | editor.removeCallbacks(scrollLeftTask); 87 | break; 88 | case SCROLL_RIGHT: 89 | editor.removeCallbacks(scrollRightTask); 90 | break; 91 | default: 92 | EditorException.fail("Invalid scroll direction"); 93 | break; 94 | } 95 | } 96 | 97 | 98 | @Override 99 | public int getRow() { 100 | return row; 101 | } 102 | 103 | @Override 104 | public int getPosition() { 105 | return position; 106 | } 107 | 108 | 109 | @Override 110 | public boolean onFirstRow() { 111 | return (row == 0); 112 | } 113 | 114 | @Override 115 | public boolean onLastRow() { 116 | return (row == (document.getRowCount() - 1)); 117 | } 118 | @Override 119 | public boolean onStart() { 120 | return position == 0; 121 | } 122 | @Override 123 | public boolean onEnd() { 124 | return (position == (document.length() - 1)); 125 | } 126 | 127 | @Override 128 | public void moveLeft() { 129 | moveLeft(false); 130 | } 131 | 132 | @Override 133 | public void moveRight() { 134 | moveRight(false); 135 | } 136 | 137 | 138 | //TODO 待解决 按屏幕坐标移动 139 | 140 | @Override 141 | public void moveDown() { 142 | if (!onLastRow()) { 143 | int currCaret = position; 144 | int currRow = row; 145 | int newRow = currRow + 1; 146 | int currColumn = document.getColumn(currCaret); 147 | int currRowLength = document.gainRowLength(currRow); 148 | int newRowLength = document.gainRowLength(newRow); 149 | 150 | if (currColumn < newRowLength) { 151 | position += currRowLength; 152 | } else { 153 | position += currRowLength - currColumn + newRowLength - 1; 154 | } 155 | ++row; 156 | 157 | controller.updateSelectionRange(currCaret, position); 158 | if (!editor.displayInterface.scrollToPosition(position)) { 159 | editor.invalidateRows(currRow, newRow + 1); 160 | } 161 | operator.onRowChange(newRow); 162 | inputConnection.stopTextComposing(); 163 | } 164 | } 165 | 166 | @Override 167 | public void moveUp() { 168 | if (!onFirstRow()) { 169 | int currCaret = position; 170 | int currRow = row; 171 | int newRow = currRow - 1; 172 | int currColumn = document.getColumn(currCaret); 173 | int newRowLength = document.gainRowLength(newRow); 174 | 175 | if (currColumn < newRowLength) { 176 | position -= newRowLength; 177 | } else { 178 | position -= (currColumn + 1); 179 | } 180 | --row; 181 | 182 | controller.updateSelectionRange(currCaret, position); 183 | if (!editor.displayInterface.scrollToPosition(position)) { 184 | editor.invalidateRows(newRow, currRow + 1); 185 | } 186 | operator.onRowChange(newRow); 187 | inputConnection.stopTextComposing(); 188 | } 189 | } 190 | 191 | @Override 192 | public void moveRight(boolean isTyping) { 193 | if (!onEnd()) { 194 | int originalRow = row; 195 | ++position; 196 | updateRow(); 197 | controller.updateSelectionRange(position - 1, position); 198 | if (!editor.displayInterface.scrollToPosition(position)) { 199 | editor.invalidateRows(originalRow, row + 1); 200 | } 201 | if (!isTyping) { 202 | inputConnection.stopTextComposing(); 203 | } 204 | } 205 | } 206 | 207 | 208 | @Override 209 | public void moveLeft(boolean isTyping) { 210 | if (!onStart()) { 211 | int originalRow = row; 212 | --position; 213 | updateRow(); 214 | controller.updateSelectionRange(position + 1, position); 215 | if (!editor.displayInterface.scrollToPosition(position)) { 216 | editor.invalidateRows(row, originalRow + 1); 217 | } 218 | 219 | if (!isTyping) { 220 | inputConnection.stopTextComposing(); 221 | } 222 | } 223 | } 224 | 225 | public void moveToPosition(int position) { 226 | if (position < 0 || position >= document.length()) { 227 | EditorException.fail("Invalid caret position"); 228 | return; 229 | } 230 | 231 | controller.updateSelectionRange(this.position, position); 232 | updatePosition(position); 233 | } 234 | 235 | 236 | 237 | //-------protected methods------- 238 | 239 | 240 | protected void updateRow() { 241 | int newRow = document.gainRowOfPosition(position); 242 | if (row != newRow) { 243 | row = newRow; 244 | operator.onRowChange(newRow); 245 | } 246 | } 247 | 248 | 249 | protected void reset() { 250 | position = 0; 251 | row = 0; 252 | } 253 | 254 | protected void setCoord(int x, int y) { 255 | this.x = x; 256 | this.y = y; 257 | } 258 | 259 | protected void setTouching(boolean touching) { 260 | this.touching = touching; 261 | } 262 | 263 | 264 | protected void updatePosition(int position) { 265 | this.position = position; 266 | int oldRow = row; 267 | updateRow(); 268 | if (!editor.displayInterface.scrollToPosition(position)) { 269 | editor.invalidateRows(oldRow, oldRow + 1); 270 | editor.invalidateCaretRow(); 271 | } 272 | inputConnection.stopTextComposing(); 273 | } 274 | 275 | protected void onDrag(MotionEvent e) { 276 | 277 | int x = (int) e.getX() - editor.getPaddingLeft(); 278 | int y = (int) e.getY() - editor.getPaddingTop(); 279 | boolean scrolled = false; 280 | 281 | 282 | if (x < SCROLL_EDGE) { 283 | scrolled = caret.autoScroll(Caret.SCROLL_LEFT); 284 | } else if (x >= (editor.getContentWidth() - SCROLL_EDGE)) { 285 | scrolled = caret.autoScroll(Caret.SCROLL_RIGHT); 286 | } else if (y < SCROLL_EDGE) { 287 | scrolled = caret.autoScroll(Caret.SCROLL_UP); 288 | } else if (y >= (editor.getContentHeight() - SCROLL_EDGE)) { 289 | scrolled = caret.autoScroll(Caret.SCROLL_DOWN); 290 | } 291 | 292 | if (!scrolled) { 293 | caret.stopAutoScroll(); 294 | int newPosition = editor.getPainter().getPositionByCoord( 295 | editor.displayInterface.screenToViewX((int) e.getX()), 296 | editor.displayInterface.screenToViewY((int) e.getY()), false 297 | ); 298 | if (newPosition >= 0) { 299 | caret.moveToPosition(newPosition); 300 | } 301 | } 302 | } 303 | 304 | private final Runnable scrollDownTask = new Runnable() { 305 | @Override 306 | public void run() { 307 | moveDown(); 308 | if (!onLastRow()) { 309 | editor.postDelayed(scrollDownTask, SCROLL_PERIOD); 310 | } 311 | } 312 | }; 313 | 314 | private final Runnable scrollUpTask = new Runnable() { 315 | @Override 316 | public void run() { 317 | moveUp(); 318 | if (!onFirstRow()) { 319 | editor.postDelayed(scrollUpTask, SCROLL_PERIOD); 320 | } 321 | } 322 | }; 323 | 324 | 325 | private final Runnable scrollLeftTask = new Runnable() { 326 | @Override 327 | public void run() { 328 | moveLeft(); 329 | if (!onStart() && row == document.gainRowOfPosition(position - 1)) { 330 | editor.postDelayed(scrollLeftTask, SCROLL_PERIOD); 331 | } 332 | } 333 | }; 334 | 335 | private final Runnable scrollRightTask = new Runnable() { 336 | @Override 337 | public void run() { 338 | moveRight(); 339 | if (!onEnd() && row == document.gainRowOfPosition(position + 1)) { 340 | editor.postDelayed(scrollRightTask, SCROLL_PERIOD); 341 | } 342 | } 343 | }; 344 | 345 | } 346 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/CursorHandlerManager.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Rect; 9 | import android.graphics.RectF; 10 | import android.graphics.drawable.Drawable; 11 | import android.view.MotionEvent; 12 | 13 | import com.ve.acpp.R; 14 | import com.ve.utils.DisplayUtils; 15 | import com.ve.view.utils.Rectangle; 16 | 17 | public class CursorHandlerManager { 18 | private Editor editor; 19 | private BaseHandle mid; 20 | private BaseHandle start; 21 | private BaseHandle end; 22 | 23 | private boolean showHandler; 24 | 25 | private int handlerSize; 26 | private Caret caret; 27 | private Drawable handleIcon; 28 | 29 | public CursorHandlerManager(Editor editor) { 30 | this.editor = editor; 31 | 32 | handleIcon = editor.getContext().getResources().getDrawable(R.drawable.cursor ); 33 | 34 | 35 | handlerSize = DisplayUtils.dp2px(editor.getContext(), 24); 36 | mid = new CommonHandle(); 37 | start = new SelectHandle(); 38 | end = new SelectHandle(); 39 | } 40 | 41 | public void init() { 42 | caret = editor.caret; 43 | } 44 | 45 | public void onDown(MotionEvent e) { 46 | 47 | if (!caret.isTouching()) { 48 | int x = (int) e.getX() + editor.getScrollX(); 49 | int y = (int) e.getY() + editor.getScrollY(); 50 | 51 | if (mid.onDown(x, y)) { 52 | showHandler = true; 53 | } else if (start.onDown(x, y)) { 54 | editor.displayInterface.scrollToSelectionStart(); 55 | } else if (end.onDown(x, y)) { 56 | editor.displayInterface.scrollToSelectionEnd(); 57 | } 58 | } 59 | } 60 | 61 | public void onUp(MotionEvent e) { 62 | mid.clearTouch(); 63 | start.clearTouch(); 64 | end.clearTouch(); 65 | } 66 | 67 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 68 | 69 | 70 | if (mid.touched) { 71 | showHandler = true; 72 | mid.moveHandle(e2); 73 | return true; 74 | } 75 | if (start.touched) { 76 | start.moveHandle(e2); 77 | return true; 78 | } 79 | if (end.touched) { 80 | end.moveHandle(e2); 81 | return true; 82 | } 83 | return false; 84 | 85 | } 86 | 87 | 88 | public boolean onSingleTapUp(MotionEvent e) { 89 | int x = (int) e.getX() + editor.getScrollX(); 90 | int y = (int) e.getY() + editor.getScrollY(); 91 | 92 | if (mid.isInHandle(x, y) || start.isInHandle(x, y) || end.isInHandle(x, y)) { 93 | //忽略单次点击事件 94 | //拦截事件 95 | return true; 96 | } else { 97 | showHandler = true; 98 | return false; 99 | } 100 | } 101 | 102 | public boolean onDoubleTap(MotionEvent e) { 103 | int x = (int) e.getX() + editor.getScrollX(); 104 | int y = (int) e.getY() + editor.getScrollY(); 105 | 106 | if (mid.isInHandle(x, y)) { 107 | editor.getSelectInterface().select(true); 108 | return true; 109 | } else { 110 | return false; 111 | } 112 | } 113 | 114 | 115 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 116 | if (mid.isTouched() || start.isTouched() || end.isTouched()) { 117 | return true; 118 | } else { 119 | return false; 120 | } 121 | } 122 | 123 | 124 | public void draw(Canvas canvas) { 125 | 126 | if (!editor.getSelectInterface().isSelecting()) { 127 | 128 | if (!mid.isTouched()) { 129 | mid.attachToPosition(caret.getPosition()); 130 | } 131 | if (showHandler) { 132 | mid.draw(canvas); 133 | } 134 | showHandler = false; 135 | } else { 136 | if (!(start.isTouched() && end.isTouched())) { 137 | start.attachToPosition(editor.getSelectInterface().getSelectionStart()); 138 | end.attachToPosition(editor.getSelectInterface().getSelectionEnd()); 139 | } 140 | start.draw(canvas); 141 | end.draw(canvas); 142 | } 143 | } 144 | 145 | 146 | private class BaseHandle { 147 | public Rectangle body = new Rectangle(0, 0, handlerSize, (int) (1f*handlerSize*handleIcon.getIntrinsicHeight()/handleIcon.getIntrinsicWidth())); 148 | 149 | private int anchorX = 0, anchorY = 0; 150 | 151 | private int touchX = 0, touchY = 0;//相对位置 152 | 153 | private final Paint paint; 154 | 155 | 156 | private boolean touched; 157 | 158 | 159 | public boolean isTouched() { 160 | return touched; 161 | } 162 | 163 | public BaseHandle() { 164 | paint = new Paint(); 165 | paint.setColor(Color.BLUE); 166 | paint.setAntiAlias(true); 167 | } 168 | 169 | public void moveHandle(MotionEvent e) { 170 | 171 | int newPosition = gainNearestPosition((int) e.getX(), (int) e.getY()); 172 | 173 | if (editor.textInterface.isValidPosition(newPosition)) { 174 | caret.moveToPosition(newPosition); 175 | 176 | moveToPosition(newPosition); 177 | } 178 | 179 | } 180 | 181 | 182 | public void draw(Canvas canvas) { 183 | 184 | handleIcon.setBounds(body.toRect()); 185 | handleIcon.draw(canvas); 186 | /*canvas.drawLine(anchorX, anchorY, 187 | body.getCenterX(), body.getCenterY(), paint); 188 | canvas.drawArc(new RectF(anchorX - radius, anchorY - radius / 2 - offsetY(), 189 | body.x + radius * 2, body.y + radius / 2), 60, 60, true, paint); 190 | canvas.drawOval(body.toRectF(), paint);*/ 191 | 192 | 193 | } 194 | 195 | private int offsetY() { 196 | return 0; 197 | } 198 | 199 | 200 | public void moveToPosition(int position) { 201 | 202 | Rect newCaretBounds = editor.painter.getBoundingBox(position); 203 | int newX = newCaretBounds.left + editor.getPaddingLeft(); 204 | int newY = newCaretBounds.bottom + editor.getPaddingTop(); 205 | setRestingCoord(newX, newY); 206 | editor.invalidate(body.toRect()); 207 | } 208 | 209 | 210 | public void setRestingCoord(int x, int y) { 211 | anchorX = x; 212 | anchorY = y; 213 | body.setPosition(x - body.width / 2, y + offsetY()); 214 | 215 | } 216 | 217 | 218 | public int gainNearestPosition(int x, int y) { 219 | int attachedLeft = editor.displayInterface.screenToViewX(x) - touchX + body.width / 2; 220 | int attachedBottom = editor.displayInterface.screenToViewY(y) - touchY - offsetY() - 2; 221 | 222 | return editor.getPainter().getPositionByCoord(attachedLeft, attachedBottom, false); 223 | } 224 | 225 | public void setTouch(int x, int y) { 226 | touchX = x - body.x; 227 | touchY = y - body.y; 228 | } 229 | 230 | public void clearTouch() { 231 | touched = false; 232 | touchX = 0; 233 | touchY = 0; 234 | } 235 | 236 | 237 | public boolean isInHandle(int x, int y) { 238 | return body.contains(x, y); 239 | } 240 | 241 | public boolean onDown(int x, int y) { 242 | touched = isInHandle(x, y); 243 | if (touched) { 244 | setTouch(x, y); 245 | editor.invalidate(body.toRect()); 246 | } 247 | return touched; 248 | } 249 | 250 | public void attachToPosition(int position) { 251 | Rect boundingBox = editor.painter.getBoundingBox(position); 252 | int x = boundingBox.left + editor.getPaddingLeft(); 253 | int y = boundingBox.bottom + editor.getPaddingTop(); 254 | setRestingCoord(x, y); 255 | } 256 | } 257 | 258 | class CommonHandle extends BaseHandle { 259 | @Override 260 | public boolean isInHandle(int x, int y) { 261 | return super.isInHandle(x, y) && !editor.getSelectInterface().isSelecting(); 262 | } 263 | } 264 | 265 | class SelectHandle extends BaseHandle { 266 | @Override 267 | public boolean isInHandle(int x, int y) { 268 | return super.isInHandle(x, y) && editor.getSelectInterface().isSelecting(); 269 | } 270 | } 271 | 272 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/EditorInputConnection.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor; 2 | 3 | import android.content.Context; 4 | import android.view.KeyEvent; 5 | import android.view.inputmethod.BaseInputConnection; 6 | import android.view.inputmethod.InputMethodManager; 7 | 8 | import com.ve.view.utils.EditorException; 9 | import com.ve.view.text.document.Document; 10 | 11 | public class EditorInputConnection extends BaseInputConnection { 12 | 13 | protected int composingLength = 0; 14 | protected Editor editor; 15 | protected Document document; 16 | protected Caret caret; 17 | protected TextController controller; 18 | 19 | public EditorInputConnection(Editor editor) { 20 | super(editor, true); 21 | this.editor = editor; 22 | } 23 | 24 | public void init() { 25 | this.document = editor.document; 26 | this.caret = editor.caret; 27 | this.controller = editor.controller; 28 | } 29 | 30 | public void reset() { 31 | composingLength = 0; 32 | document.endBatchEdit(); 33 | } 34 | 35 | public boolean isComposing() { 36 | return composingLength == 0; 37 | } 38 | 39 | public void stopTextComposing() { 40 | InputMethodManager im = (InputMethodManager) editor.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 41 | im.restartInput(editor); 42 | 43 | if (isComposing()) { 44 | reset(); 45 | } 46 | } 47 | 48 | 49 | @Override 50 | public boolean performContextMenuAction(int id) { 51 | switch (id) { 52 | case android.R.id.copy: 53 | editor.getTextInterface().copy(); 54 | break; 55 | case android.R.id.cut: 56 | editor.getTextInterface().cut(); 57 | break; 58 | case android.R.id.paste: 59 | editor.getTextInterface().paste(); 60 | break; 61 | case android.R.id.startSelectingText: 62 | case android.R.id.stopSelectingText: 63 | case android.R.id.selectAll: 64 | editor.selectInterface.selectAll(); 65 | break; 66 | } 67 | 68 | return false; 69 | } 70 | 71 | @Override 72 | public boolean sendKeyEvent(KeyEvent event) { 73 | switch (event.getKeyCode()) { 74 | case KeyEvent.KEYCODE_SHIFT_LEFT: 75 | 76 | editor.selectInterface.select(!editor.selectInterface.isSelecting()); 77 | break; 78 | case KeyEvent.KEYCODE_DPAD_LEFT: 79 | caret.moveLeft(); 80 | break; 81 | case KeyEvent.KEYCODE_DPAD_UP: 82 | caret.moveUp(); 83 | break; 84 | case KeyEvent.KEYCODE_DPAD_RIGHT: 85 | caret.moveRight(); 86 | break; 87 | case KeyEvent.KEYCODE_DPAD_DOWN: 88 | caret.moveDown(); 89 | break; 90 | case KeyEvent.KEYCODE_MOVE_HOME: 91 | caret.moveToPosition(0); 92 | break; 93 | case KeyEvent.KEYCODE_MOVE_END: 94 | caret.moveToPosition(document.length() - 1); 95 | break; 96 | default: 97 | return super.sendKeyEvent(event); 98 | } 99 | return true; 100 | } 101 | 102 | 103 | @Override 104 | public boolean setComposingText(CharSequence text, int newCursorPosition) { 105 | if (!document.isBatchEdit()) { 106 | document.beginBatchEdit(); 107 | } 108 | 109 | controller.replaceComposingText(caret.getPosition() - composingLength, composingLength, text.toString()); 110 | composingLength = text.length(); 111 | 112 | //TODO 减少重绘 113 | if (newCursorPosition > 1) { 114 | caret.moveToPosition(caret.position + newCursorPosition - 1); 115 | } else if (newCursorPosition <= 0) { 116 | caret.moveToPosition(caret.position - text.length() - newCursorPosition); 117 | } 118 | return true; 119 | } 120 | 121 | @Override 122 | public boolean commitText(CharSequence text, int newCursorPosition) { 123 | controller.replaceComposingText(caret.getPosition() - composingLength, composingLength, text.toString()); 124 | composingLength = 0; 125 | document.endBatchEdit(); 126 | 127 | //TODO 减少重绘 128 | if (newCursorPosition > 1) { 129 | caret.moveToPosition(caret.position + newCursorPosition - 1); 130 | } else if (newCursorPosition <= 0) { 131 | caret.moveToPosition(caret.position - text.length() - newCursorPosition); 132 | } 133 | 134 | return true; 135 | } 136 | 137 | 138 | @Override 139 | public boolean deleteSurroundingText(int leftLength, int rightLength) { 140 | EditorException.logIf(composingLength > 0, "deleteSurroundingText composingLength != 0"); 141 | 142 | controller.deleteAroundComposingText(leftLength, rightLength); 143 | return true; 144 | } 145 | 146 | @Override 147 | public boolean finishComposingText() { 148 | reset(); 149 | return true; 150 | } 151 | 152 | 153 | @Override 154 | public CharSequence getTextAfterCursor(int maxLen, int flags) { 155 | return controller.getTextAfterCursor(maxLen); 156 | } 157 | 158 | @Override 159 | public CharSequence getTextBeforeCursor(int maxLen, int flags) { 160 | return controller.getTextBeforeCursor(maxLen); 161 | } 162 | 163 | @Override 164 | public boolean setSelection(int start, int end) { 165 | if (start == end) { 166 | caret.moveToPosition(start); 167 | } else { 168 | controller.setSelectionRange(start, end - start, false); 169 | } 170 | return true; 171 | } 172 | 173 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/EventManager.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Rect; 5 | import android.view.GestureDetector; 6 | import android.view.HapticFeedbackConstants; 7 | import android.view.MotionEvent; 8 | 9 | import com.ve.utils.SysUtils; 10 | import com.ve.view.text.document.Document; 11 | import com.ve.view.utils.ZoomChecker; 12 | 13 | 14 | public class EventManager { 15 | protected Editor editor; 16 | protected Caret caret; 17 | protected GestureDetector gestureDetector; 18 | protected ZoomChecker zoomChecker; 19 | protected int fling; 20 | protected CursorHandlerManager cursorHandler; 21 | protected BaseListener baseListener; 22 | protected static int TOUCH_SLOP = 12; 23 | 24 | public EventManager(Editor editor) { 25 | this.editor = editor; 26 | baseListener = new BaseListener(); 27 | gestureDetector = new GestureDetector(editor.getContext(), baseListener); 28 | gestureDetector.setIsLongpressEnabled(true); 29 | cursorHandler=new CursorHandlerManager(editor); 30 | zoomChecker = new ZoomChecker(); 31 | zoomChecker.setListener(editor.getPainter()); 32 | } 33 | 34 | public void init() { 35 | caret = editor.caret; 36 | cursorHandler.init(); 37 | } 38 | 39 | 40 | public boolean onTouchEvent(MotionEvent event) { 41 | zoomChecker.checkZoom(event); 42 | boolean handled = gestureDetector.onTouchEvent(event); 43 | if (!handled && (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) { 44 | handled = baseListener.onUp(event); 45 | } 46 | return handled; 47 | } 48 | 49 | 50 | public boolean isNearChar(int x, int y, int charOffset) { 51 | Rect bounds = editor.painter.getBoundingBox(charOffset); 52 | 53 | return (y >= (bounds.top - TOUCH_SLOP) 54 | && y < (bounds.bottom + TOUCH_SLOP) 55 | && x >= (bounds.left - TOUCH_SLOP) 56 | && x < (bounds.right + TOUCH_SLOP) 57 | ); 58 | } 59 | 60 | public void onTextDrawComplete(Canvas canvas) { 61 | cursorHandler.draw(canvas); 62 | } 63 | 64 | 65 | class BaseListener extends GestureDetector.SimpleOnGestureListener { 66 | 67 | public boolean onUp(MotionEvent e) { 68 | caret.stopAutoScroll(); 69 | caret.setTouching(false); 70 | zoomChecker.reset(); 71 | fling = 0; 72 | cursorHandler.onUp(e); 73 | return true; 74 | } 75 | 76 | @Override 77 | public boolean onDown(MotionEvent e) { 78 | int x = editor.displayInterface.screenToViewX((int) e.getX()); 79 | int y = editor.displayInterface.screenToViewY((int) e.getY()); 80 | caret.setTouching(isNearChar(x, y, caret.getPosition())); 81 | 82 | if (editor.displayInterface.isFlingScrolling()) { 83 | editor.displayInterface.stopFlingScrolling(); 84 | } else if (editor.getSelectInterface().isSelecting()) { 85 | if (isNearChar(x, y, editor.getSelectInterface().getSelectionStart())) { 86 | editor.displayInterface.scrollToSelectionStart(); 87 | editor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 88 | 89 | caret.setTouching(true); 90 | } else if (isNearChar(x, y, editor.getSelectInterface().getSelectionEnd())) { 91 | editor.displayInterface.scrollToSelectionEnd(); 92 | editor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 93 | caret.setTouching(true); 94 | } 95 | } 96 | 97 | if (caret.isTouching()) { 98 | editor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 99 | } 100 | cursorHandler.onDown(e); 101 | 102 | return true; 103 | } 104 | 105 | @Override 106 | public boolean onSingleTapUp(MotionEvent e) { 107 | if (cursorHandler.onSingleTapUp(e)){ 108 | return true; 109 | } 110 | 111 | int x = editor.displayInterface.screenToViewX((int) e.getX()); 112 | int y = editor.displayInterface.screenToViewY((int) e.getY()); 113 | int position = editor.getPainter().getPositionByCoord(x, y, false); 114 | 115 | if (editor.getSelectInterface().isSelecting()) { 116 | int strictCharOffset = editor.getPainter().getPositionByCoord(x, y, true); 117 | if (editor.getSelectInterface().inSelectionRange(strictCharOffset) || isNearChar(x, y, editor.getSelectInterface().getSelectionStart()) || isNearChar(x, y, editor.getSelectInterface().getSelectionEnd())) { 118 | // do nothing 119 | } else { 120 | editor.getSelectInterface().cancelSelect(); 121 | if (strictCharOffset >= 0) { 122 | caret.moveToPosition(position); 123 | } 124 | } 125 | } else { 126 | if (position >= 0) { 127 | caret.moveToPosition(position); 128 | } 129 | } 130 | SysUtils.showInputMethod(editor, true); 131 | return true; 132 | } 133 | 134 | 135 | @Override 136 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 137 | if (cursorHandler.onScroll(e1, e2, distanceX, distanceY)){ 138 | return true; 139 | } 140 | 141 | if (caret.isTouching()) { 142 | caret.onDrag(e2); 143 | } else if (e2.getPointerCount() == 1) { 144 | if (fling == 0) 145 | if (Math.abs(distanceX) > Math.abs(distanceY)) 146 | fling = 1; 147 | else 148 | fling = -1; 149 | if (fling == 1) 150 | distanceY = 0; 151 | else if (fling == -1) 152 | distanceX = 0; 153 | 154 | editor.displayInterface.scroll(distanceX, distanceY); 155 | 156 | } 157 | if ((e2.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) { 158 | onUp(e2); 159 | } 160 | return true; 161 | } 162 | 163 | 164 | @Override 165 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 166 | if (cursorHandler.onFling(e1, e2, velocityX, velocityY)){ 167 | return true; 168 | } 169 | 170 | if (!caret.isTouching()) { 171 | if (fling == 1) { 172 | velocityY = 0; 173 | } else if (fling == -1) { 174 | velocityX = 0; 175 | } 176 | editor.displayInterface.flingScroll((int) -velocityX, (int) -velocityY); 177 | } 178 | onUp(e2); 179 | return true; 180 | } 181 | 182 | @Override 183 | public void onLongPress(MotionEvent e) { 184 | onDoubleTap(e); 185 | } 186 | 187 | @Override 188 | public boolean onDoubleTap(MotionEvent e) { 189 | if (cursorHandler.onDoubleTap(e)){ 190 | return true; 191 | } 192 | 193 | caret.setTouching(true); 194 | int x = editor.displayInterface.screenToViewX((int) e.getX()); 195 | int y = editor.displayInterface.screenToViewY((int) e.getY()); 196 | int charOffset = editor.getPainter().getPositionByCoord(x, y, false); 197 | 198 | if (charOffset >= 0) { 199 | caret.moveToPosition(charOffset); 200 | Document document = editor.document; 201 | int start; 202 | int end; 203 | for (start = charOffset; start >= 0; start--) { 204 | char c = document.charAt(start); 205 | if (!Character.isJavaIdentifierPart(c)) 206 | break; 207 | } 208 | if (start != charOffset) 209 | start++; 210 | for (end = charOffset; end >= 0; end++) { 211 | char c = document.charAt(end); 212 | if (!Character.isJavaIdentifierPart(c)) 213 | break; 214 | } 215 | //editor.getSelectInterface().select(true); 216 | editor.getSelectInterface().select(start, end - start); 217 | } 218 | 219 | return true; 220 | } 221 | 222 | } 223 | 224 | 225 | } 226 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/ListenManager.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor; 2 | 3 | import com.ve.view.listener.RowListener; 4 | import com.ve.view.listener.SelectionListener; 5 | import com.ve.view.listener.TextListener; 6 | 7 | public class ListenManager extends Base implements RowListener, SelectionListener, TextListener { 8 | private RowListener rowListener; 9 | private SelectionListener selectionChangedListener; 10 | private TextListener textListener; 11 | 12 | public ListenManager(Editor editor) { 13 | super(editor); 14 | rowListener = new RowListener.RowAdapter(); 15 | selectionChangedListener = new SelectionListener.SelectionAdapter(); 16 | textListener = new TextListener.TextAdapter(); 17 | } 18 | 19 | @Override 20 | public void onRowChange(int row) { 21 | if (rowListener != null) { 22 | rowListener.onRowChange(row); 23 | } 24 | } 25 | 26 | @Override 27 | public void onSelectionChanged(boolean select, int selectionStart, int selectionEnd) { 28 | selectionChangedListener.onSelectionChanged(select,selectionStart,selectionEnd); 29 | } 30 | 31 | 32 | @Override 33 | public void onNewLine( int caretPosition, int p2) { 34 | textListener.onNewLine( caretPosition, p2); 35 | } 36 | 37 | @Override 38 | public void onDelete(int caretPosition, int newCursorPosition) { 39 | textListener.onDelete( caretPosition, newCursorPosition); 40 | } 41 | 42 | @Override 43 | public void onAdd(CharSequence text, int caretPosition, int newCursorPosition) { 44 | textListener.onAdd(text, caretPosition, newCursorPosition); 45 | } 46 | 47 | public void setRowListener(RowListener rowListener) { 48 | this.rowListener = rowListener; 49 | } 50 | 51 | public void setSelectionChangedListener(SelectionListener selectionChangedListener) { 52 | this.selectionChangedListener = selectionChangedListener; 53 | } 54 | 55 | public void setTextListener(TextListener textListener) { 56 | this.textListener = textListener; 57 | } 58 | 59 | 60 | 61 | 62 | 63 | public int getCaretY() { 64 | return caret.y; 65 | } 66 | 67 | public int getCaretX() { 68 | return caret.x; 69 | } 70 | 71 | 72 | 73 | 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/MeasureCache.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor; 2 | 3 | import android.graphics.Paint; 4 | 5 | import com.ve.view.editor.out.Config; 6 | import com.ve.view.text.document.CommonLanguage; 7 | 8 | import java.util.HashMap; 9 | 10 | public class MeasureCache { 11 | private HashMap cache; 12 | private Paint paint; 13 | 14 | private int rowHeight; 15 | 16 | private int spaceWidth; 17 | private int tabWidth; 18 | private int newlineWidth; 19 | private Config config; 20 | 21 | public MeasureCache(Paint paint) { 22 | this.paint = paint; 23 | this.cache = new HashMap<>(); 24 | } 25 | 26 | 27 | public int getRowHeight() { 28 | return rowHeight; 29 | } 30 | 31 | public int getSpaceWidth() { 32 | return spaceWidth; 33 | } 34 | 35 | public int getTabWidth() { 36 | return tabWidth; 37 | } 38 | 39 | public int getNewlineWidth() { 40 | return newlineWidth; 41 | } 42 | 43 | 44 | public void setConfig(Config config) { 45 | this.config = config; 46 | invalidate(); 47 | } 48 | 49 | 50 | private float measureFromCache(char c) { 51 | 52 | Float value = cache.get(c); 53 | if (value == null) { 54 | value = measure(c); 55 | cache.put(c, value); 56 | } 57 | return value; 58 | } 59 | 60 | private float measure(char c) { 61 | return paint.measureText(new char[]{c}, 0, 1); 62 | } 63 | 64 | private float measure(String s) { 65 | return paint.measureText(s, 0, s.length()); 66 | } 67 | 68 | private float measure(char c1, char c2) { 69 | return paint.measureText(new char[]{c1, c2}, 0, 2); 70 | } 71 | 72 | public void invalidate() { 73 | cache.clear(); 74 | reMeasure(); 75 | } 76 | 77 | private void reMeasure() { 78 | if (config.showNonPrinting) { 79 | spaceWidth = (int) measure(CommonLanguage.GLYPH_SPACE); 80 | newlineWidth = (int) measure(CommonLanguage.GLYPH_NEWLINE); 81 | tabWidth = (int) measure(CommonLanguage.GLYPH_TAB); 82 | 83 | } else { 84 | spaceWidth = (int) measure(CommonLanguage.SPACE); 85 | newlineWidth = (int) measure(CommonLanguage.SPACE); 86 | tabWidth = spaceWidth *config. tabLength; 87 | } 88 | rowHeight = (int) (paint.descent()-paint.ascent()); 89 | } 90 | 91 | 92 | public MeasureTool getNewMeasureTool() { 93 | return new MeasureTool(); 94 | } 95 | 96 | class MeasureTool { 97 | private char emojiFlag; 98 | private boolean justEmoji;//上一个是Emoji 99 | 100 | public boolean isJustEmoji() { 101 | return justEmoji; 102 | } 103 | 104 | public MeasureTool() { 105 | reset(); 106 | } 107 | 108 | public void reset() { 109 | emojiFlag = CommonLanguage.NULL_CHAR; 110 | justEmoji = false; 111 | } 112 | 113 | public int measure(char c) { 114 | int width; 115 | 116 | justEmoji = false; 117 | switch (c) { 118 | case CommonLanguage.EMOJI1: 119 | case CommonLanguage.EMOJI2: 120 | width = 0; 121 | emojiFlag = c; 122 | break; 123 | case CommonLanguage.SPACE: 124 | width = spaceWidth; 125 | break; 126 | case CommonLanguage.NEWLINE: 127 | case CommonLanguage.EOF: 128 | width = newlineWidth; 129 | break; 130 | case CommonLanguage.TAB: 131 | width = tabWidth; 132 | break; 133 | default: 134 | if (emojiFlag != CommonLanguage.NULL_CHAR) { 135 | width = (int) MeasureCache.this.measure(emojiFlag, c); 136 | justEmoji = true; 137 | emojiFlag = CommonLanguage.NULL_CHAR; 138 | } else { 139 | if (config.cacheEnable) { 140 | width = (int) MeasureCache.this.measureFromCache(c); 141 | } else { 142 | width = (int) MeasureCache.this.measure(c); 143 | } 144 | 145 | } 146 | break; 147 | } 148 | 149 | return width; 150 | } 151 | 152 | 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/SpanManager.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor; 2 | 3 | import android.graphics.Color; 4 | 5 | import com.ve.lexer.Lexer; 6 | import com.ve.view.utils.Pair; 7 | import com.ve.view.editor.span.SpanType; 8 | import com.ve.view.editor.span.TextSpanData; 9 | 10 | public class SpanManager extends Base{ 11 | protected final Lexer lexer = new Lexer(results -> editor.post(() -> { 12 | TextSpanData textSpanData = new TextSpanData(); 13 | for (Pair result : results) { 14 | //System.err.printf("%d %d\n",result.getFirst(),result.getSecond()); 15 | textSpanData.addSpan(result.getFirst(),new SpanType(result.getSecond()==0? Color.BLACK:Color.RED)); 16 | } 17 | painter.setTextSpanData(textSpanData); 18 | editor.invalidate(); 19 | })); 20 | 21 | public SpanManager(Editor editor) { 22 | super(editor); 23 | } 24 | 25 | public void startSpan() { 26 | lexer.tokenize(document); 27 | } 28 | 29 | public void stopSpan() { 30 | lexer.cancelTokenize(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/out/CaretInterface.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor.out; 2 | 3 | public interface CaretInterface { 4 | 5 | int getRow(); 6 | 7 | int getPosition(); 8 | 9 | boolean onFirstRow(); 10 | 11 | boolean onLastRow(); 12 | 13 | boolean onStart(); 14 | 15 | boolean onEnd(); 16 | 17 | void moveLeft(); 18 | 19 | void moveRight(); 20 | 21 | void moveDown(); 22 | 23 | void moveUp(); 24 | 25 | void moveRight(boolean isTyping); 26 | 27 | void moveLeft(boolean isTyping); 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/out/Config.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor.out; 2 | 3 | import com.ve.view.ext.ColorScheme; 4 | 5 | public class Config { 6 | public boolean wordWrap = true; 7 | public boolean showNonPrinting = true; 8 | public int tabLength=4; 9 | public int textSize; 10 | public boolean cacheEnable = false; 11 | public boolean isAutoIndent = true; 12 | 13 | public int autoIndentWidth = 4; 14 | ColorScheme colorScheme; 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/out/DisplayInterface.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor.out; 2 | 3 | public interface DisplayInterface { 4 | boolean scrollToPosition(int position); 5 | 6 | void scrollToSelectionStart(); 7 | 8 | void scrollToSelectionEnd(); 9 | 10 | void scroll(float dx, float dy); 11 | 12 | void smoothScrollTo(int x, int y); 13 | 14 | void flingScroll(int velocityX, int velocityY); 15 | 16 | boolean isFlingScrolling(); 17 | 18 | void stopFlingScrolling(); 19 | 20 | void smoothScroll(int dx, int dy); 21 | 22 | void scrollToCaret(); 23 | 24 | int screenToViewX(int x); 25 | 26 | int screenToViewY(int y); 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/out/SelectInterface.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor.out; 2 | 3 | public interface SelectInterface { 4 | boolean isSelecting(); 5 | 6 | void cancelSelect(); 7 | 8 | void select(boolean select); 9 | 10 | void selectAll(); 11 | 12 | void select(int position, int count); 13 | 14 | boolean inSelectionRange(int position); 15 | 16 | int getSelectionStart(); 17 | 18 | int getSelectionEnd(); 19 | /* boolean isSelecting(); 20 | int getSelectionStart(); 21 | int getSelectionEnd(); 22 | void setSelection(int start, int end); 23 | String getSelectionText(); 24 | void cancelSelection();*/ 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/out/Status.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor.out; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import com.ve.view.editor.Editor; 7 | 8 | public class Status implements Parcelable { 9 | public int caretPosition; 10 | public int scrollX, scrollY; 11 | public boolean selectMode; 12 | public int selectBegin, selectEnd; 13 | 14 | @Override 15 | public int describeContents() { 16 | return 0; 17 | } 18 | 19 | public Status(Editor editor) { 20 | caretPosition = editor.getCaretInterface().getPosition(); 21 | scrollX = editor.getScrollX(); 22 | scrollY = editor.getScrollY(); 23 | selectMode = editor.getSelectInterface().isSelecting(); 24 | selectBegin = editor.getSelectInterface().getSelectionStart(); 25 | selectEnd = editor.getSelectInterface().getSelectionEnd(); 26 | } 27 | 28 | private Status(Parcel in) { 29 | caretPosition = in.readInt(); 30 | scrollX = in.readInt(); 31 | scrollY = in.readInt(); 32 | selectMode = in.readInt() != 0; 33 | selectBegin = in.readInt(); 34 | selectEnd = in.readInt(); 35 | } 36 | 37 | @Override 38 | public void writeToParcel(Parcel out, int flags) { 39 | out.writeInt(caretPosition); 40 | out.writeInt(scrollX); 41 | out.writeInt(scrollY); 42 | out.writeInt(selectMode ? 1 : 0); 43 | out.writeInt(selectBegin); 44 | out.writeInt(selectEnd); 45 | } 46 | 47 | public static final Creator CREATOR = new Creator() { 48 | @Override 49 | public Status createFromParcel(Parcel in) { 50 | return new Status(in); 51 | } 52 | 53 | @Override 54 | public Status[] newArray(int size) { 55 | return new Status[size]; 56 | } 57 | }; 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/out/TextInterface.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor.out; 2 | 3 | import com.ve.view.text.document.Document; 4 | 5 | public interface TextInterface { 6 | 7 | 8 | void replaceText(int position, int count, String text); 9 | 10 | int getLength(); 11 | 12 | boolean isValidPosition(int position); 13 | 14 | int getRowCount(); 15 | 16 | void setDocument(Document document); 17 | 18 | Document getDocument(); 19 | 20 | void setEdited(boolean set); 21 | 22 | boolean isEdited(); 23 | 24 | void undo(); 25 | 26 | void redo(); 27 | 28 | void cut(); 29 | 30 | void copy(); 31 | 32 | void paste(); 33 | 34 | void paste(String text); 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/span/SpanNode.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor.span; 2 | 3 | public class SpanNode { 4 | int length; 5 | SpanType type; 6 | 7 | public SpanNode(int length, SpanType type) { 8 | this.length = length; 9 | this.type = type; 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/span/SpanType.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor.span; 2 | 3 | import android.graphics.Paint; 4 | import android.graphics.Typeface; 5 | import android.support.annotation.ColorInt; 6 | import android.util.Log; 7 | 8 | 9 | public class SpanType { 10 | public static final Typeface TF_DEFAULT = Typeface.DEFAULT; 11 | public static final Typeface TF_BOLD = Typeface.DEFAULT_BOLD; 12 | public static final Typeface TF_ITALIC = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC); 13 | public static final Typeface TF_BOLD_ITALIC = Typeface.create(Typeface.DEFAULT, Typeface.BOLD_ITALIC); 14 | @ColorInt 15 | private int color; 16 | private Typeface typeface; 17 | 18 | public SpanType(int color) { 19 | this.color = color; 20 | this.typeface = TF_DEFAULT; 21 | } 22 | 23 | public SpanType(int color, Typeface typeface) { 24 | this.color = color; 25 | this.typeface = typeface; 26 | } 27 | 28 | public void onSpan(Paint paint) { 29 | paint.setColor(color); 30 | paint.setTypeface(typeface); 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return String.format("#%h",color); 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/editor/span/TextSpanData.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.editor.span; 2 | 3 | import android.graphics.Color; 4 | import android.graphics.Paint; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class TextSpanData { 10 | 11 | 12 | public static final SpanType NORMAL = new SpanType(Color.BLACK); 13 | 14 | private List spanNodes; 15 | 16 | public TextSpanData() { 17 | spanNodes = new ArrayList<>(); 18 | addSpan(0, NORMAL); 19 | } 20 | 21 | 22 | public void addSpan(int position, SpanType type) { 23 | if (type == null) { 24 | type = NORMAL; 25 | } 26 | spanNodes.add(new SpanNode(position, type)); 27 | 28 | } 29 | 30 | 31 | public void clear() { 32 | spanNodes.clear(); 33 | addSpan(0, NORMAL); 34 | } 35 | 36 | public class SpanSeeker { 37 | private Paint paint; 38 | private SpanNode currentSpan, nextSpan; 39 | private int index = 0; 40 | private int spanPosition = 0; 41 | 42 | public SpanSeeker(Paint paint) { 43 | this.paint = paint; 44 | } 45 | 46 | public void begin(int currentPosition) { 47 | 48 | index = 0; 49 | spanPosition=0; 50 | nextSpan = spanNodes.get(index++); 51 | do { 52 | currentSpan = nextSpan; 53 | spanPosition += currentSpan.length; 54 | if (index < spanNodes.size()) { 55 | nextSpan = spanNodes.get(index++); 56 | } else { 57 | nextSpan = null; 58 | } 59 | } while (nextSpan != null && spanPosition <= currentPosition); 60 | 61 | currentSpan.type.onSpan(paint); 62 | } 63 | 64 | public boolean reachedNextSpan(int currentPosition, SpanNode span) { 65 | return span != null&¤tPosition>=spanPosition; 66 | } 67 | 68 | public void listenSpan(int currentPosition) { 69 | if (reachedNextSpan(currentPosition, nextSpan)) { 70 | currentSpan = nextSpan; 71 | spanPosition += currentSpan.length; 72 | currentSpan.type.onSpan(paint); 73 | 74 | if (index < spanNodes.size()) { 75 | nextSpan = spanNodes.get(index++); 76 | } else { 77 | nextSpan = null; 78 | } 79 | 80 | } 81 | } 82 | 83 | public SpanNode getCurrentSpan() { 84 | return currentSpan; 85 | } 86 | } 87 | 88 | public SpanSeeker getNewSeeker(Paint paint) { 89 | return new SpanSeeker(paint); 90 | } 91 | 92 | 93 | @Override 94 | public String toString() { 95 | StringBuilder stringBuilder = new StringBuilder(); 96 | stringBuilder.append(getClass().getSimpleName() + "(" + spanNodes.size() + ")" + "{\n"); 97 | for (SpanNode spanNode : spanNodes) { 98 | stringBuilder.append(String.format("[%d,%s]\n", spanNode.length, spanNode.type.toString())); 99 | } 100 | 101 | return stringBuilder.append("\n}\n").toString(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/ext/AutoComplete.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.ext; 2 | 3 | import com.ve.lexer.LuaLexer; 4 | import com.ve.lexer.LuaTokenTypes; 5 | 6 | import java.io.IOException; 7 | 8 | public class AutoComplete { 9 | public static int createAutoIndent(CharSequence text) { 10 | LuaLexer lexer = new LuaLexer(text); 11 | int idt = 0; 12 | try { 13 | while (true) { 14 | LuaTokenTypes type = lexer.advance(); 15 | if (type == null) { 16 | break; 17 | } 18 | idt += indent(type); 19 | } 20 | } catch (IOException e) { 21 | e.printStackTrace(); 22 | } 23 | return idt; 24 | } 25 | 26 | 27 | private static int indent(LuaTokenTypes t) { 28 | switch (t) { 29 | case DO: 30 | case FUNCTION: 31 | case THEN: 32 | case REPEAT: 33 | case LCURLY: 34 | return 1; 35 | case UNTIL: 36 | case ELSEIF: 37 | case END: 38 | case RCURLY: 39 | return -1; 40 | default: 41 | return 0; 42 | } 43 | } 44 | 45 | public static CharSequence format(CharSequence text, int width) { 46 | StringBuilder builder = new StringBuilder(); 47 | boolean isNewLine = true; 48 | LuaLexer lexer = new LuaLexer(text); 49 | try { 50 | int idt = 0; 51 | 52 | while (true) { 53 | LuaTokenTypes type = lexer.advance(); 54 | if (type == null) 55 | break; 56 | if (type == LuaTokenTypes.NEWLINE) { 57 | isNewLine = true; 58 | builder.append('\n'); 59 | idt = Math.max(0, idt); 60 | 61 | } else if (isNewLine) { 62 | if (type == LuaTokenTypes.WS) { 63 | 64 | } else if (type == LuaTokenTypes.ELSE) { 65 | idt--; 66 | builder.append(createIntdent(idt * width)); 67 | builder.append(lexer.yytext()); 68 | idt++; 69 | isNewLine = false; 70 | } else if (type == LuaTokenTypes.ELSEIF || type == LuaTokenTypes.END || type == LuaTokenTypes.UNTIL || type == LuaTokenTypes.RCURLY) { 71 | idt--; 72 | builder.append(createIntdent(idt * width)); 73 | builder.append(lexer.yytext()); 74 | 75 | isNewLine = false; 76 | } else { 77 | builder.append(createIntdent(idt * width)); 78 | builder.append(lexer.yytext()); 79 | idt += indent(type); 80 | isNewLine = false; 81 | } 82 | } else if (type == LuaTokenTypes.WS) { 83 | builder.append(' '); 84 | } else { 85 | builder.append(lexer.yytext()); 86 | idt += indent(type); 87 | } 88 | 89 | } 90 | } catch (IOException e) { 91 | e.printStackTrace(); 92 | } 93 | 94 | return builder; 95 | } 96 | 97 | private static char[] createIntdent(int n) { 98 | if (n < 0) 99 | return new char[0]; 100 | char[] idts = new char[n]; 101 | for (int i = 0; i < n; i++) 102 | idts[i] = ' '; 103 | return idts; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/ext/AutoCompletePanel.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.ext; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.drawable.Drawable; 6 | import android.graphics.drawable.GradientDrawable; 7 | import android.util.DisplayMetrics; 8 | import android.util.TypedValue; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.AdapterView; 13 | import android.widget.AdapterView.OnItemClickListener; 14 | import android.widget.ArrayAdapter; 15 | import android.widget.Filter; 16 | import android.widget.Filterable; 17 | import android.widget.ListPopupWindow; 18 | import android.widget.TextView; 19 | 20 | import com.ve.lexer.Language; 21 | import com.ve.lexer.LanguageC; 22 | import com.ve.view.utils.Flag; 23 | import com.ve.view.editor.Editor; 24 | 25 | import java.util.ArrayList; 26 | 27 | public class AutoCompletePanel { 28 | 29 | private Editor editor; 30 | private Context context; 31 | private static Language language = LanguageC.getInstance(); 32 | private ListPopupWindow _autoCompletePanel; 33 | private MyAdapter _adapter; 34 | private Filter _filter; 35 | 36 | private int _verticalOffset; 37 | 38 | private int _height; 39 | 40 | private int _horizontal; 41 | 42 | private CharSequence _constraint; 43 | 44 | private int _backgroundColor; 45 | 46 | private GradientDrawable gd; 47 | 48 | private int _textColor; 49 | 50 | public AutoCompletePanel(Editor textField) { 51 | editor = textField; 52 | context = textField.getContext(); 53 | initAutoCompletePanel(); 54 | 55 | } 56 | 57 | public void setTextColor(int color) { 58 | _textColor = color; 59 | gd.setStroke(1, color); 60 | _autoCompletePanel.setBackgroundDrawable(gd); 61 | } 62 | 63 | 64 | public void setBackgroundColor(int color) { 65 | _backgroundColor = color; 66 | gd.setColor(color); 67 | _autoCompletePanel.setBackgroundDrawable(gd); 68 | } 69 | 70 | public void setBackground(Drawable color) { 71 | _autoCompletePanel.setBackgroundDrawable(color); 72 | } 73 | 74 | private void initAutoCompletePanel() { 75 | _autoCompletePanel = new ListPopupWindow(context); 76 | _autoCompletePanel.setAnchorView(editor); 77 | _adapter = new MyAdapter(context, android.R.layout.simple_list_item_1); 78 | _autoCompletePanel.setAdapter(_adapter); 79 | //autoCompletePanel.setDropDownGravity(Gravity.BOTTOM | Gravity.LEFT); 80 | _filter = _adapter.getFilter(); 81 | setHeight(300); 82 | 83 | TypedArray array = context.getTheme().obtainStyledAttributes(new int[]{ 84 | android.R.attr.colorBackground, 85 | android.R.attr.textColorPrimary, 86 | }); 87 | int backgroundColor = array.getColor(0, 0xFF00FF); 88 | int textColor = array.getColor(1, 0xFF00FF); 89 | array.recycle(); 90 | gd = new GradientDrawable(); 91 | gd.setColor(backgroundColor); 92 | gd.setCornerRadius(4); 93 | gd.setStroke(1, textColor); 94 | setTextColor(textColor); 95 | _autoCompletePanel.setBackgroundDrawable(gd); 96 | _autoCompletePanel.setOnItemClickListener(new OnItemClickListener() { 97 | 98 | @Override 99 | public void onItemClick(AdapterView p1, View p2, int p3, long p4) { 100 | // TODO: Implement this method 101 | //editor.replaceText(editor.getCaret().getPosition() - _constraint.length(), _constraint.length(), ((TextView) p2).getText().toString()); 102 | _adapter.abort(); 103 | dismiss(); 104 | } 105 | }); 106 | 107 | } 108 | 109 | public void setWidth(int width) { 110 | // TODO: Implement this method 111 | _autoCompletePanel.setWidth(width); 112 | } 113 | 114 | private void setHeight(int height) { 115 | // TODO: Implement this method 116 | 117 | if (_height != height) { 118 | _height = height; 119 | _autoCompletePanel.setHeight(height); 120 | } 121 | } 122 | 123 | private void setHorizontalOffset(int horizontal) { 124 | // TODO: Implement this method 125 | horizontal = Math.min(horizontal, editor.getWidth() / 2); 126 | if (_horizontal != horizontal) { 127 | _horizontal = horizontal; 128 | _autoCompletePanel.setHorizontalOffset(horizontal); 129 | } 130 | } 131 | 132 | 133 | private void setVerticalOffset(int verticalOffset) { 134 | // TODO: Implement this method 135 | //verticalOffset=Math.min(verticalOffset,editor.getWidth()/2); 136 | int max = 0 - _autoCompletePanel.getHeight(); 137 | if (verticalOffset > max) { 138 | editor.scrollBy(0, verticalOffset - max); 139 | verticalOffset = max; 140 | } 141 | if (_verticalOffset != verticalOffset) { 142 | _verticalOffset = verticalOffset; 143 | _autoCompletePanel.setVerticalOffset(verticalOffset); 144 | } 145 | } 146 | 147 | public void update(CharSequence constraint) { 148 | _adapter.restart(); 149 | _filter.filter(constraint); 150 | } 151 | 152 | public void show() { 153 | if (!_autoCompletePanel.isShowing()) { 154 | _autoCompletePanel.show(); 155 | } 156 | _autoCompletePanel.getListView().setFadingEdgeLength(0); 157 | } 158 | 159 | public void dismiss() { 160 | if (_autoCompletePanel.isShowing()) { 161 | _autoCompletePanel.dismiss(); 162 | } 163 | } 164 | 165 | synchronized public static void setLanguage(Language lang) { 166 | language = lang; 167 | } 168 | 169 | synchronized public static Language getLanguage() { 170 | return language; 171 | } 172 | 173 | /** 174 | * Adapter定义 175 | */ 176 | class MyAdapter extends ArrayAdapter implements Filterable { 177 | 178 | private int _h; 179 | private Flag _abort; 180 | 181 | private DisplayMetrics dm; 182 | 183 | public MyAdapter(Context context, int resource) { 184 | super(context, resource); 185 | _abort = new Flag(); 186 | setNotifyOnChange(false); 187 | dm = context.getResources().getDisplayMetrics(); 188 | 189 | } 190 | 191 | public void abort() { 192 | _abort.set(); 193 | } 194 | 195 | 196 | private int dp(float n) { 197 | // TODO: Implement this method 198 | return (int) TypedValue.applyDimension(1, n, dm); 199 | } 200 | 201 | @Override 202 | public View getView(int position, View convertView, ViewGroup parent) { 203 | // TODO: Implement this method 204 | TextView view = (TextView) super.getView(position, convertView, parent); 205 | /*TextView view=null; 206 | if(convertView==null){ 207 | view=new TextView(context); 208 | view.setScale(16); 209 | view.setPadding(dp(8),dp(3),dp(8),dp(3)); 210 | } 211 | else{ 212 | view=(TextView) convertView; 213 | } 214 | view.setText(getItem(position));*/ 215 | view.setTextColor(_textColor); 216 | return view; 217 | } 218 | 219 | 220 | public void restart() { 221 | // TODO: Implement this method 222 | _abort.clear(); 223 | } 224 | 225 | public int getItemHeight() { 226 | if (_h != 0) { 227 | return _h; 228 | } 229 | 230 | LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 231 | TextView item = (TextView) inflater.inflate(android.R.layout.simple_list_item_1, null); 232 | item.measure(0, 0); 233 | _h = item.getMeasuredHeight(); 234 | return _h; 235 | } 236 | 237 | /** 238 | * 实现自动完成的过滤算法 239 | */ 240 | @Override 241 | public Filter getFilter() { 242 | Filter filter = new Filter() { 243 | 244 | /** 245 | * 本方法在后台线程执行,定义过滤算法 246 | */ 247 | @Override 248 | protected FilterResults performFiltering(CharSequence constraint) { 249 | /*int l=constraint.length(); 250 | int i=l; 251 | for(;i>0;i--){ 252 | if(constraint.charAt(l-1)=='.') 253 | break; 254 | } 255 | if(i>0){ 256 | constraint=constraint.subSequence(i,l); 257 | }*/ 258 | 259 | // 此处实现过滤 260 | // 过滤后利用FilterResults将过滤结果返回 261 | ArrayList buf = new ArrayList(); 262 | String keyword = String.valueOf(constraint).toLowerCase(); 263 | String[] ss = keyword.split("\\."); 264 | if (ss.length == 2) { 265 | String pkg = ss[0]; 266 | keyword = ss[1]; 267 | if (language.isBasePackage(pkg)) { 268 | String[] keywords = language.getBasePackage(pkg); 269 | for (String k : keywords) { 270 | if (k.toLowerCase().startsWith(keyword)) { 271 | buf.add(k); 272 | } 273 | } 274 | } 275 | } else if (ss.length == 1) { 276 | if (keyword.charAt(keyword.length() - 1) == '.') { 277 | String pkg = keyword.substring(0, keyword.length() - 1); 278 | keyword = ""; 279 | if (language.isBasePackage(pkg)) { 280 | String[] keywords = language.getBasePackage(pkg); 281 | for (String k : keywords) { 282 | buf.add(k); 283 | } 284 | } 285 | } else { 286 | String[] keywords = language.getUserWord(); 287 | for (String k : keywords) { 288 | if (k.toLowerCase().startsWith(keyword)) { 289 | buf.add(k); 290 | } 291 | } 292 | keywords = language.getKeywords(); 293 | for (String k : keywords) { 294 | if (k.indexOf(keyword) == 0) { 295 | buf.add(k); 296 | } 297 | } 298 | keywords = language.getNames(); 299 | for (String k : keywords) { 300 | if (k.toLowerCase().startsWith(keyword)) { 301 | buf.add(k); 302 | } 303 | } 304 | } 305 | } 306 | _constraint = keyword; 307 | FilterResults filterResults = new FilterResults(); 308 | filterResults.values = buf; // results是上面的过滤结果 309 | filterResults.count = buf.size(); // 结果数量 310 | return filterResults; 311 | } 312 | 313 | /** 314 | * 本方法在UI线程执行,用于更新自动完成列表 315 | */ 316 | @Override 317 | protected void publishResults(CharSequence constraint, FilterResults results) { 318 | if (results != null && results.count > 0 && !_abort.isSet()) { 319 | // 有过滤结果,显示自动完成列表 320 | MyAdapter.this.clear(); // 清空旧列表 321 | MyAdapter.this.addAll((ArrayList) results.values); 322 | //int y = editor.getPaintBaseline(editor.getRow()) - editor.getScrollY(); 323 | int y = editor.getOperator().getCaretY() + editor.getPainter().rowHeight() / 2 - editor.getScrollY(); 324 | setHeight(getItemHeight() * Math.min(2, results.count)); 325 | //setHeight((int)(Math.min(editor.getContentHeight()*0.4,getItemHeight() * Math.min(6, results.count)))); 326 | 327 | setHorizontalOffset(editor.getOperator().getCaretX() - editor.getScrollX()); 328 | setVerticalOffset(y - editor.getHeight());//editor.getCaretY()-editor.getScrollY()-editor.getHeight()); 329 | notifyDataSetChanged(); 330 | show(); 331 | } else { 332 | // 无过滤结果,关闭列表 333 | notifyDataSetInvalidated(); 334 | } 335 | } 336 | 337 | }; 338 | return filter; 339 | } 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/ext/AutoIndent.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.ext; 2 | 3 | import com.ve.lexer.LuaLexer; 4 | import com.ve.lexer.LuaTokenTypes; 5 | 6 | import java.io.IOException; 7 | 8 | public class AutoIndent { 9 | public static int createAutoIndent(CharSequence text) { 10 | LuaLexer lexer = new LuaLexer(text); 11 | int idt = 0; 12 | try { 13 | while (true) { 14 | LuaTokenTypes type = lexer.advance(); 15 | if (type == null) { 16 | break; 17 | } 18 | idt += indent(type); 19 | } 20 | } catch (IOException e) { 21 | e.printStackTrace(); 22 | } 23 | return idt; 24 | } 25 | 26 | 27 | private static int indent(LuaTokenTypes t) { 28 | switch (t) { 29 | case DO: 30 | case FUNCTION: 31 | case THEN: 32 | case REPEAT: 33 | case LCURLY: 34 | return 1; 35 | case UNTIL: 36 | case ELSEIF: 37 | case END: 38 | case RCURLY: 39 | return -1; 40 | default: 41 | return 0; 42 | } 43 | } 44 | 45 | public static CharSequence format(CharSequence text, int width) { 46 | StringBuilder builder = new StringBuilder(); 47 | boolean isNewLine = true; 48 | LuaLexer lexer = new LuaLexer(text); 49 | try { 50 | int idt = 0; 51 | 52 | while (true) { 53 | LuaTokenTypes type = lexer.advance(); 54 | if (type == null) 55 | break; 56 | if (type == LuaTokenTypes.NEWLINE) { 57 | isNewLine = true; 58 | builder.append('\n'); 59 | idt = Math.max(0, idt); 60 | 61 | } else if (isNewLine) { 62 | if (type == LuaTokenTypes.WS) { 63 | 64 | } else if (type == LuaTokenTypes.ELSE) { 65 | idt--; 66 | builder.append(createIntdent(idt * width)); 67 | builder.append(lexer.yytext()); 68 | idt++; 69 | isNewLine = false; 70 | } else if (type == LuaTokenTypes.ELSEIF || type == LuaTokenTypes.END || type == LuaTokenTypes.UNTIL || type == LuaTokenTypes.RCURLY) { 71 | idt--; 72 | builder.append(createIntdent(idt * width)); 73 | builder.append(lexer.yytext()); 74 | 75 | isNewLine = false; 76 | } else { 77 | builder.append(createIntdent(idt * width)); 78 | builder.append(lexer.yytext()); 79 | idt += indent(type); 80 | isNewLine = false; 81 | } 82 | } else if (type == LuaTokenTypes.WS) { 83 | builder.append(' '); 84 | } else { 85 | builder.append(lexer.yytext()); 86 | idt += indent(type); 87 | } 88 | 89 | } 90 | } catch (IOException e) { 91 | e.printStackTrace(); 92 | } 93 | 94 | return builder; 95 | } 96 | 97 | private static char[] createIntdent(int n) { 98 | if (n < 0) 99 | return new char[0]; 100 | char[] idts = new char[n]; 101 | for (int i = 0; i < n; i++) 102 | idts[i] = ' '; 103 | return idts; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/ext/ClipboardPanel.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.ext; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.view.ActionMode; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | 9 | import com.ve.view.editor.Editor; 10 | 11 | public class ClipboardPanel { 12 | protected Editor editor; 13 | private Context context; 14 | private ActionMode actionMode; 15 | 16 | public ClipboardPanel(Editor editor) { 17 | this.editor = editor; 18 | context = editor.getContext(); 19 | 20 | 21 | } 22 | 23 | 24 | public void show() { 25 | startClipboardAction(); 26 | } 27 | 28 | public void hide() { 29 | stopClipboardAction(); 30 | } 31 | 32 | public void startClipboardAction() { 33 | // TODO: Implement this method 34 | if (actionMode == null) 35 | editor.startActionMode(new ActionMode.Callback() { 36 | 37 | @Override 38 | public boolean onCreateActionMode(ActionMode mode, Menu menu) { 39 | 40 | 41 | actionMode = mode; 42 | mode.setTitle(android.R.string.selectTextMode); 43 | TypedArray array = context.getTheme().obtainStyledAttributes(new int[]{ 44 | android.R.attr.actionModeSelectAllDrawable, 45 | android.R.attr.actionModeCutDrawable, 46 | android.R.attr.actionModeCopyDrawable, 47 | android.R.attr.actionModePasteDrawable, 48 | }); 49 | menu.add(0, 0, 0, context.getString(android.R.string.selectAll)) 50 | .setShowAsActionFlags(2) 51 | .setAlphabeticShortcut('a') 52 | .setIcon(array.getDrawable(0)); 53 | 54 | menu.add(0, 1, 0, context.getString(android.R.string.cut)) 55 | .setShowAsActionFlags(2) 56 | .setAlphabeticShortcut('x') 57 | .setIcon(array.getDrawable(1)); 58 | 59 | menu.add(0, 2, 0, context.getString(android.R.string.copy)) 60 | .setShowAsActionFlags(2) 61 | .setAlphabeticShortcut('c') 62 | .setIcon(array.getDrawable(2)); 63 | 64 | menu.add(0, 3, 0, context.getString(android.R.string.paste)) 65 | .setShowAsActionFlags(2) 66 | .setAlphabeticShortcut('v') 67 | .setIcon(array.getDrawable(3)); 68 | array.recycle(); 69 | return true; 70 | } 71 | 72 | @Override 73 | public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 74 | // TODO: Implement this method 75 | return false; 76 | } 77 | 78 | @Override 79 | public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 80 | switch (item.getItemId()) { 81 | case 0: 82 | editor.getSelectInterface().selectAll(); 83 | break; 84 | case 1: 85 | editor.getTextInterface().cut(); 86 | mode.finish(); 87 | break; 88 | case 2: 89 | editor.getTextInterface().copy(); 90 | mode.finish(); 91 | break; 92 | case 3: 93 | editor.getTextInterface().paste(); 94 | mode.finish(); 95 | } 96 | return false; 97 | } 98 | 99 | @Override 100 | public void onDestroyActionMode(ActionMode p1) { 101 | editor.getSelectInterface().cancelSelect(); 102 | actionMode = null; 103 | } 104 | }); 105 | 106 | } 107 | 108 | public void stopClipboardAction() { 109 | if (actionMode != null) { 110 | actionMode.finish(); 111 | actionMode = null; 112 | } 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/ext/ColorScheme.java: -------------------------------------------------------------------------------- 1 | 2 | package com.ve.view.ext; 3 | 4 | import com.ve.lexer.Lexer; 5 | import com.ve.view.utils.EditorException; 6 | 7 | import java.util.HashMap; 8 | 9 | public class ColorScheme { 10 | public enum Colorable { 11 | FOREGROUND, BACKGROUND, SELECTION_FOREGROUND, SELECTION_BACKGROUND, 12 | CARET_FOREGROUND, CARET_BACKGROUND, CARET_DISABLED, LINE_HIGHLIGHT, 13 | NON_PRINTING_GLYPH, COMMENT, KEYWORD, NAME, LITERAL, STRING, 14 | SECONDARY 15 | } 16 | 17 | public ColorScheme() { 18 | setColor(Colorable.FOREGROUND, OFF_BLACK); 19 | setColor(Colorable.BACKGROUND, OFF_WHITE); 20 | setColor(Colorable.SELECTION_FOREGROUND, OFF_WHITE); 21 | setColor(Colorable.CARET_FOREGROUND, OFF_WHITE); 22 | } 23 | 24 | private static final int OFF_WHITE = 0xFFF0F0ED; 25 | private static final int OFF_BLACK = 0xFF333333; 26 | 27 | 28 | protected HashMap _colors = generateDefaultColors(); 29 | 30 | public void setColor(Colorable colorable, int color) { 31 | _colors.put(colorable, color); 32 | } 33 | 34 | public int getColor(Colorable colorable) { 35 | Integer color = _colors.get(colorable); 36 | if (color == null) { 37 | EditorException.fail("Color not specified for " + colorable); 38 | return 0; 39 | } 40 | return color.intValue(); 41 | } 42 | 43 | // Currently, color scheme is tightly coupled with semantics of the token types 44 | public int getTokenColor(int tokenType) { 45 | Colorable element; 46 | switch (tokenType) { 47 | case Lexer.NORMAL: 48 | element = Colorable.FOREGROUND; 49 | break; 50 | case Lexer.KEYWORD: 51 | element = Colorable.KEYWORD; 52 | break; 53 | case Lexer.NAME: 54 | element = Colorable.NAME; 55 | break; 56 | case Lexer.DOUBLE_SYMBOL_LINE: //fall-through 57 | case Lexer.DOUBLE_SYMBOL_DELIMITED_MULTILINE: 58 | case Lexer.SINGLE_SYMBOL_LINE_B: 59 | element = Colorable.COMMENT; 60 | break; 61 | case Lexer.SINGLE_SYMBOL_DELIMITED_A: //fall-through 62 | case Lexer.SINGLE_SYMBOL_DELIMITED_B: 63 | element = Colorable.STRING; 64 | break; 65 | case Lexer.LITERAL: 66 | element = Colorable.LITERAL; 67 | break; 68 | case Lexer.SINGLE_SYMBOL_LINE_A: //fall-through 69 | case Lexer.SINGLE_SYMBOL_WORD: 70 | case Lexer.OPERATOR: 71 | element = Colorable.SECONDARY; 72 | break; 73 | default: 74 | EditorException.fail("Invalid token type"); 75 | element = Colorable.FOREGROUND; 76 | break; 77 | } 78 | return getColor(element); 79 | } 80 | 81 | /** 82 | * Whether this color scheme uses a dark background, like black or dark grey. 83 | */ 84 | public boolean isDark() { 85 | return false; 86 | } 87 | 88 | private HashMap generateDefaultColors() { 89 | // High-contrast, black-on-white color scheme 90 | HashMap colors = new HashMap(Colorable.values().length); 91 | colors.put(Colorable.FOREGROUND, BLACK); 92 | colors.put(Colorable.BACKGROUND, WHITE); 93 | colors.put(Colorable.SELECTION_FOREGROUND, WHITE); 94 | colors.put(Colorable.SELECTION_BACKGROUND, 0xFF97C024); 95 | colors.put(Colorable.CARET_FOREGROUND, WHITE); 96 | colors.put(Colorable.CARET_BACKGROUND, LIGHT_BLUE2); 97 | colors.put(Colorable.CARET_DISABLED, GREY); 98 | colors.put(Colorable.LINE_HIGHLIGHT, 0x20888888); 99 | 100 | colors.put(Colorable.NON_PRINTING_GLYPH, LIGHT_GREY); 101 | colors.put(Colorable.COMMENT, OLIVE_GREEN); // Eclipse default color 102 | colors.put(Colorable.KEYWORD, DARK_BLUE); // Eclipse default color 103 | colors.put(Colorable.NAME, INDIGO); // Eclipse default color 104 | colors.put(Colorable.LITERAL, LIGHT_BLUE); // Eclipse default color 105 | colors.put(Colorable.STRING, PURPLE); // Eclipse default color 106 | colors.put(Colorable.SECONDARY, GREY); 107 | return colors; 108 | } 109 | 110 | // In ARGB format: 0xAARRGGBB 111 | private static final int BLACK = 0xFF000000; 112 | private static final int BLUE = 0xFF0000FF; 113 | private static final int DARK_RED = 0xFF8B0000; 114 | private static final int DARK_BLUE = 0xFFD040DD; 115 | private static final int GREY = 0xFF808080; 116 | private static final int LIGHT_GREY = 0xFFAAAAAA; 117 | private static final int MAROON = 0xFF800000; 118 | private static final int INDIGO = 0xFF2A40FF; 119 | private static final int OLIVE_GREEN = 0xFF3F7F5F; 120 | private static final int PURPLE = 0xFFDD4488; 121 | private static final int RED = 0x44FF0000; 122 | private static final int WHITE = 0xFFFFFFE0; 123 | private static final int PURPLE2 = 0xFFFF00FF; 124 | private static final int LIGHT_BLUE = 0xFF6080FF; 125 | private static final int LIGHT_BLUE2 = 0xFF40B0FF; 126 | private static final int GREEN = 0xFF88AA88; 127 | } 128 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/listener/RowListener.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.listener; 2 | 3 | public interface RowListener { 4 | void onRowChange(int newRowIndex); 5 | 6 | class RowAdapter implements RowListener { 7 | @Override 8 | public void onRowChange(int newRowIndex) { 9 | 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/listener/SelectionListener.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.listener; 2 | 3 | public interface SelectionListener { 4 | void onSelectionChanged(boolean active, int selStart, int selEnd); 5 | class SelectionAdapter implements SelectionListener { 6 | @Override 7 | public void onSelectionChanged(boolean active, int selStart, int selEnd) { 8 | 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/listener/TextListener.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.listener; 2 | 3 | public interface TextListener { 4 | void onNewLine(int caretPosition, int p2); 5 | 6 | void onDelete(int caretPosition, int newCursorPosition); 7 | 8 | void onAdd(CharSequence text, int caretPosition, int newCursorPosition); 9 | 10 | class TextAdapter implements TextListener { 11 | @Override 12 | public void onNewLine(int caretPosition, int p2) { 13 | 14 | } 15 | 16 | @Override 17 | public void onDelete(int caretPosition, int newCursorPosition) { 18 | 19 | } 20 | 21 | @Override 22 | public void onAdd(CharSequence text, int caretPosition, int newCursorPosition) { 23 | 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/text/cache/Entry.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.text.cache; 2 | 3 | public class Entry { 4 | private int line,position; 5 | 6 | public Entry(int line, int position) { 7 | this.line = line; 8 | this.position = position; 9 | } 10 | 11 | public int getLine() { 12 | return line; 13 | } 14 | 15 | public void setLine(int line) { 16 | this.line = line; 17 | } 18 | 19 | public int getPosition() { 20 | return position; 21 | } 22 | 23 | public void setPosition(int position) { 24 | this.position = position; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/text/cache/TextCache.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.text.cache; 2 | 3 | public class TextCache { 4 | private static final int CACHE_SIZE = 4; // minimum = 1 5 | private Entry[] entries = new Entry[CACHE_SIZE]; 6 | 7 | public TextCache() { 8 | entries[0] = new Entry(0, 0); 9 | for (int i = 1; i < CACHE_SIZE; ++i) { 10 | entries[i] = new Entry(-1, -1); 11 | } 12 | } 13 | 14 | public Entry getNearestEntryByLine(int line) { 15 | int nearestMatch = 0; 16 | int nearestDistance = Integer.MAX_VALUE; 17 | for (int i = 0; i < CACHE_SIZE; ++i) { 18 | int distance = Math.abs(line - entries[i].getLine()); 19 | if (distance < nearestDistance) { 20 | nearestDistance = distance; 21 | nearestMatch = i; 22 | } 23 | } 24 | 25 | Entry nearestEntry = entries[nearestMatch]; 26 | makeHead(nearestMatch); 27 | return nearestEntry; 28 | } 29 | 30 | public Entry getNearestEntryByPosition(int position) { 31 | int nearestMatch = 0; 32 | int nearestDistance = Integer.MAX_VALUE; 33 | for (int i = 0; i < CACHE_SIZE; ++i) { 34 | int distance = Math.abs(position - entries[i].getPosition()); 35 | if (distance < nearestDistance) { 36 | nearestDistance = distance; 37 | nearestMatch = i; 38 | } 39 | } 40 | 41 | Entry nearestEntry = entries[nearestMatch]; 42 | makeHead(nearestMatch); 43 | return nearestEntry; 44 | } 45 | 46 | 47 | private void makeHead(int newHead) { 48 | if (newHead != 0) { 49 | Entry temp = entries[newHead]; 50 | for (int i = newHead; i > 1; --i) { 51 | entries[i] = entries[i - 1]; 52 | } 53 | entries[1] = temp; 54 | } 55 | 56 | } 57 | 58 | public void updateEntry(int line, int position) { 59 | if (line > 0) { 60 | //update 61 | for (int i = 1; i < CACHE_SIZE; ++i) { 62 | if (entries[i].getLine() == line) { 63 | entries[i].setPosition(position); 64 | return; 65 | } 66 | } 67 | //insert 68 | makeHead(CACHE_SIZE - 1); 69 | entries[1] = new Entry(line, position); 70 | 71 | } 72 | 73 | } 74 | 75 | 76 | public void invalidateEntriesFrom(int position) { 77 | for (int i = 1; i < CACHE_SIZE; ++i) { 78 | if (entries[i].getPosition() >= position) { 79 | entries[i] = new Entry(-1, -1); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/text/document/CommonLanguage.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.text.document; 2 | 3 | public class CommonLanguage { 4 | public final static char EOF = '\uFFFF'; 5 | public final static char NULL_CHAR = '\u0000'; 6 | public final static char NEWLINE = '\n'; 7 | public final static char BACKSPACE = '\b'; 8 | public static final char SPACE = ' '; 9 | public final static char TAB = '\t'; 10 | public final static String GLYPH_NEWLINE = "\u21b5"; 11 | public final static String GLYPH_SPACE = "\u00b7"; 12 | public final static String GLYPH_TAB = "\u00bb"; 13 | public final static char EMOJI1 = 0xd83c; 14 | public final static char EMOJI2 = 0xd83d; 15 | 16 | public static boolean isWhitespace(char c) { 17 | return c == SPACE || c == NEWLINE || c == TAB || c == '\r' || c == '\f' || c == EOF; 18 | } 19 | public static boolean isEmoji(char c) { 20 | return c == EMOJI1 || c == EMOJI2 ; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/text/document/Document.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.text.document; 2 | 3 | import com.ve.view.utils.EditorException; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class Document extends BaseDocument { 8 | private boolean isWordWrapEnable = false; 9 | 10 | private WordWrapable wordWrapable; 11 | 12 | private ArrayList rowTable; 13 | 14 | public Document() { 15 | super(); 16 | resetRowTable(); 17 | } 18 | 19 | 20 | private void resetRowTable() { 21 | ArrayList newRowTable = new ArrayList(); 22 | newRowTable.add(0); 23 | rowTable = newRowTable; 24 | } 25 | 26 | public void setWordWrapable(WordWrapable wordWrapable) { 27 | this.wordWrapable = wordWrapable; 28 | } 29 | 30 | public void setWordWrapEnable(boolean enable) { 31 | if (enable && !isWordWrapEnable) { 32 | isWordWrapEnable = true; 33 | analyzeWordWrap(); 34 | } else if (!enable && isWordWrapEnable) { 35 | isWordWrapEnable = false; 36 | analyzeWordWrap(); 37 | } 38 | } 39 | 40 | public boolean isWordWrapEnable() { 41 | return isWordWrapEnable; 42 | } 43 | 44 | 45 | @Override 46 | public synchronized void delete(int charOffset, int totalChars, long timestamp, boolean undoable) { 47 | super.delete(charOffset, totalChars, timestamp, undoable); 48 | 49 | int startRow = gainRowOfPosition(charOffset); 50 | int analyzeEnd = findNextLineFrom(charOffset); 51 | updateWordWrapAfterEdit(startRow, analyzeEnd, -totalChars); 52 | } 53 | 54 | @Override 55 | public synchronized void insert(char[] c, int charOffset, long timestamp, boolean undoable) { 56 | super.insert(c, charOffset, timestamp, undoable); 57 | 58 | int startRow = gainRowOfPosition(charOffset); 59 | int analyzeEnd = findNextLineFrom(charOffset + c.length); 60 | updateWordWrapAfterEdit(startRow, analyzeEnd, c.length); 61 | } 62 | 63 | @Override 64 | public synchronized void shiftGapStart(int displacement) { 65 | super.shiftGapStart(displacement); 66 | 67 | if (displacement != 0) { 68 | int startOffset = (displacement > 0) 69 | ? gap.getStart() - displacement 70 | : gap.getStart(); 71 | int startRow = gainRowOfPosition(startOffset); 72 | int analyzeEnd = findNextLineFrom(gap.getStart()); 73 | updateWordWrapAfterEdit(startRow, analyzeEnd, displacement); 74 | } 75 | } 76 | 77 | //No error checking is done on parameters. 78 | private int findNextLineFrom(int charOffset) { 79 | int lineEnd = gap.position2index(charOffset); 80 | 81 | while (lineEnd < contents.length) { 82 | // skip the gap 83 | if (lineEnd == gap.getStart()) { 84 | lineEnd = gap.getEnd(); 85 | } 86 | 87 | if (contents[lineEnd] == CommonLanguage.NEWLINE || contents[lineEnd] == CommonLanguage.EOF) { 88 | break; 89 | } 90 | 91 | ++lineEnd; 92 | } 93 | 94 | return gap.index2position(lineEnd) + 1; 95 | } 96 | 97 | private void updateWordWrapAfterEdit(int startRow, int analyzeEnd, int delta) { 98 | if (startRow > 0) { 99 | --startRow; 100 | } 101 | int analyzeStart = rowTable.get(startRow); 102 | 103 | //changes only affect the rowTable after startRow 104 | removeRowMetadata(startRow + 1, analyzeEnd - delta); 105 | adjustOffsetOfRowsFrom(startRow + 1, delta); 106 | analyzeWordWrap(startRow + 1, analyzeStart, analyzeEnd); 107 | } 108 | 109 | private void removeRowMetadata(int fromRow, int endOffset) { 110 | while (fromRow < rowTable.size() && rowTable.get(fromRow) <= endOffset) { 111 | rowTable.remove(fromRow); 112 | } 113 | } 114 | 115 | private void adjustOffsetOfRowsFrom(int fromRow, int offset) { 116 | for (int i = fromRow; i < rowTable.size(); ++i) { 117 | rowTable.set(i, rowTable.get(i) + offset); 118 | } 119 | } 120 | 121 | public void analyzeWordWrap() { 122 | 123 | resetRowTable(); 124 | 125 | if (isWordWrapEnable && !hasMinimumWidthForWordWrap()) { 126 | if (wordWrapable.getEditorWidth() > 0) { 127 | EditorException.fail("Text field has non-zero width but still too small for word wrap"); 128 | } 129 | //没有绘制之前getRowWidth() 可能是0 130 | return; 131 | } 132 | 133 | analyzeWordWrap(1, 0, length()); 134 | } 135 | 136 | private boolean hasMinimumWidthForWordWrap() { 137 | final int maxWidth = wordWrapable.getEditorWidth(); 138 | return (maxWidth >= 2 * wordWrapable.measure('M')); 139 | } 140 | 141 | private void analyzeWordWrap(int rowIndex, int startPosition, int endPosition) { 142 | if (!isWordWrapEnable) { 143 | int startIndex = gap.position2index(startPosition); 144 | int end = gap.position2index(endPosition); 145 | ArrayList rowTable = new ArrayList(); 146 | 147 | while (startIndex < end) { 148 | if (startIndex == gap.getStart()) { 149 | startIndex = gap.getEnd(); 150 | } 151 | char c = contents[startIndex]; 152 | if (c == CommonLanguage.NEWLINE) { 153 | rowTable.add(gap.index2position(startIndex) + 1); 154 | } 155 | ++startIndex; 156 | 157 | } 158 | this.rowTable.addAll(rowIndex, rowTable); 159 | return; 160 | } 161 | if (!hasMinimumWidthForWordWrap()) { 162 | EditorException.fail("Not enough space to do word wrap"); 163 | return; 164 | } 165 | 166 | ArrayList rowTable = new ArrayList(); 167 | int offset = gap.position2index(startPosition); 168 | int end = gap.position2index(endPosition); 169 | int potentialBreakPoint = startPosition; 170 | int wordExtent = 0; 171 | final int maxWidth = wordWrapable.getEditorWidth(); 172 | int remainingWidth = maxWidth; 173 | 174 | while (offset < end) { 175 | // skip the gap 176 | if (offset == gap.getStart()) { 177 | offset = gap.getEnd(); 178 | } 179 | 180 | char c = contents[offset]; 181 | wordExtent += wordWrapable.measure(c); 182 | 183 | if (CommonLanguage.isWhitespace(c)) { 184 | //full word obtained 185 | if (wordExtent <= remainingWidth) { 186 | remainingWidth -= wordExtent; 187 | } else if (wordExtent > maxWidth) { 188 | //handle a word too long to fit on one row 189 | int current = gap.position2index(potentialBreakPoint); 190 | remainingWidth = maxWidth; 191 | 192 | //start the word on a new row, if it isn't already 193 | if (potentialBreakPoint != startPosition && (rowTable.isEmpty() || 194 | potentialBreakPoint != rowTable.get(rowTable.size() - 1))) { 195 | rowTable.add(potentialBreakPoint); 196 | } 197 | 198 | while (current <= offset) { 199 | // skip the gap 200 | if (current == gap.getStart()) { 201 | current = gap.getEnd(); 202 | } 203 | 204 | int advance = wordWrapable.measure(contents[current]); 205 | if (advance > remainingWidth) { 206 | rowTable.add(gap.index2position(current)); 207 | remainingWidth = maxWidth - advance; 208 | } else { 209 | remainingWidth -= advance; 210 | } 211 | 212 | ++current; 213 | } 214 | } else { 215 | //invariant: potentialBreakPoint != startOffset 216 | //put the word on a new row 217 | rowTable.add(potentialBreakPoint); 218 | remainingWidth = maxWidth - wordExtent; 219 | } 220 | 221 | wordExtent = 0; 222 | potentialBreakPoint = gap.index2position(offset) + 1; 223 | } 224 | 225 | if (c == CommonLanguage.NEWLINE) { 226 | //start a new row 227 | rowTable.add(potentialBreakPoint); 228 | remainingWidth = maxWidth; 229 | } 230 | 231 | ++offset; 232 | } 233 | 234 | //merge with existing row table 235 | this.rowTable.addAll(rowIndex, rowTable); 236 | } 237 | 238 | public String gainRowText(int rowNumber) { 239 | 240 | int rowSize = gainRowLength(rowNumber); 241 | if (rowSize == 0) { 242 | return new String(); 243 | } 244 | 245 | int startIndex = rowTable.get(rowNumber); 246 | return subSequence(startIndex, rowSize).toString(); 247 | } 248 | 249 | public int gainRowLength(int row) { 250 | 251 | if (!isValidRow(row)) { 252 | return 0; 253 | } 254 | 255 | if (row != (rowTable.size() - 1)) { 256 | return rowTable.get(row + 1) - rowTable.get(row); 257 | } else { 258 | return length() - rowTable.get(row); 259 | } 260 | } 261 | 262 | public int getRowCount() { 263 | return rowTable.size(); 264 | } 265 | 266 | public int gainPositionOfRow(int row) { 267 | return isValidRow(row) ? rowTable.get(row) : -1; 268 | } 269 | 270 | public int gainRowOfPosition(int position) { 271 | 272 | if (!isValidPosition(position)) { 273 | return -1; 274 | } 275 | 276 | //binary search of rowTable 277 | int right = rowTable.size() - 1; 278 | int left = 0; 279 | while (right >= left) { 280 | int mid = (left + right) / 2; 281 | int nextLineOffset = ((mid + 1) < rowTable.size()) ? rowTable.get(mid + 1) : length(); 282 | if (position >= rowTable.get(mid) && position < nextLineOffset) { 283 | return mid; 284 | } 285 | 286 | if (position >= nextLineOffset) { 287 | left = mid + 1; 288 | } else { 289 | right = mid - 1; 290 | } 291 | } 292 | 293 | //should not be here 294 | return -1; 295 | } 296 | 297 | public int getColumn(int position) { 298 | int row = gainRowOfPosition(position); 299 | EditorException.logIf(row < 0, "Invalid char offset given to getColumn"); 300 | int firstCharOfRow = gainPositionOfRow(row); 301 | return position - firstCharOfRow; 302 | } 303 | 304 | protected boolean isValidRow(int row) { 305 | return row >= 0 && row < rowTable.size(); 306 | } 307 | 308 | 309 | 310 | } 311 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/text/document/DocumentEdit.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.text.document; 2 | 3 | public interface DocumentEdit { 4 | 5 | 6 | void insertBefore(char c, int insertionPoint, long timestamp); 7 | 8 | void insertBefore(char[] cArray, int insertionPoint, long timestamp); 9 | 10 | void deleteAt(int deletionPoint, long timestamp); 11 | 12 | void deleteAt(int deletionPoint, int maxChars, long time); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/text/document/Gap.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.text.document; 2 | 3 | public class Gap { 4 | protected final static int MIN_GAP_SIZE = 64; 5 | 6 | private int start, end; 7 | private char[] contents; 8 | 9 | public Gap(char[] contents) { 10 | this.contents=contents; 11 | start = 0; 12 | end = MIN_GAP_SIZE; 13 | } 14 | 15 | public void move(int distance) { 16 | start += distance; 17 | end += distance; 18 | } 19 | 20 | public void moveStart(int distance) { 21 | start += distance; 22 | } 23 | 24 | public void moveEnd(int distance) { 25 | end += distance; 26 | } 27 | 28 | public int getStart() { 29 | return start; 30 | } 31 | 32 | public void setStart(int start) { 33 | this.start = start; 34 | } 35 | 36 | public int getEnd() { 37 | return end; 38 | } 39 | 40 | public void setEnd(int end) { 41 | this.end = end; 42 | } 43 | 44 | protected final int size() { 45 | return end - start; 46 | } 47 | 48 | protected final int position2index(int position) { 49 | if (isBeforeGap(position)) { 50 | return position; 51 | } else { 52 | return position + size(); 53 | } 54 | } 55 | 56 | protected final int index2position(int index) { 57 | if (isBeforeGap(index)) { 58 | return index; 59 | } else { 60 | return index - size(); 61 | } 62 | } 63 | 64 | protected final boolean isBeforeGap(int index) { 65 | return index < start; 66 | } 67 | 68 | 69 | public void reset(int capacity) { 70 | start = 0; 71 | end = capacity; 72 | } 73 | 74 | final protected void shiftGapLeft(int newGapStart) { 75 | while (start > newGapStart) { 76 | 77 | start--; 78 | end--; 79 | contents[end] = contents[start]; 80 | } 81 | } 82 | 83 | final protected void shiftGapRight(int newGapEnd) { 84 | while (end < newGapEnd) { 85 | contents[start] = contents[end]; 86 | start++; 87 | end++; 88 | } 89 | } 90 | 91 | public void onContentChange(char[] contents) { 92 | this.contents=contents; 93 | } 94 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/text/document/WordWrapable.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.text.document; 2 | 3 | public interface WordWrapable { 4 | int measure(char c); 5 | int getEditorWidth(); 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/text/undo/Undo.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.text.undo; 2 | 3 | public interface Undo { 4 | int undo(); 5 | 6 | int redo(); 7 | 8 | boolean canUndo(); 9 | 10 | boolean canRedo(); 11 | 12 | boolean isBatchEdit(); 13 | 14 | void beginBatchEdit(); 15 | 16 | void endBatchEdit(); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/text/undo/UndoManager.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.text.undo; 2 | 3 | import java.util.LinkedList; 4 | 5 | public class UndoManager implements Undo { 6 | private Undoable undoable; 7 | private LinkedList commands = new LinkedList(); 8 | private boolean isBatchEdit = false; 9 | private int groupId = 0; 10 | private int top = 0; 11 | private long lastEditTime = -1; 12 | 13 | public UndoManager(Undoable undoable) { 14 | this.undoable = undoable; 15 | } 16 | 17 | //private 18 | private void push(Command c) { 19 | trimStack(); 20 | ++top; 21 | commands.add(c); 22 | } 23 | 24 | private void trimStack() { 25 | while (commands.size() > top) { 26 | commands.removeLast(); 27 | } 28 | } 29 | 30 | @Override 31 | public int undo() { 32 | if (canUndo()) { 33 | Command lastUndone = commands.get(top - 1); 34 | int group = lastUndone.group; 35 | do { 36 | Command c = commands.get(top - 1); 37 | if (c.group != group) { 38 | break; 39 | } 40 | 41 | lastUndone = c; 42 | c.undo(); 43 | --top; 44 | } while (canUndo()); 45 | 46 | return lastUndone.findUndoPosition(); 47 | } 48 | 49 | return -1; 50 | } 51 | 52 | @Override 53 | public int redo() { 54 | if (canRedo()) { 55 | Command lastRedone = commands.get(top); 56 | int group = lastRedone.group; 57 | do { 58 | Command c = commands.get(top); 59 | if (c.group != group) { 60 | break; 61 | } 62 | 63 | lastRedone = c; 64 | c.redo(); 65 | ++top; 66 | } while (canRedo()); 67 | 68 | return lastRedone.findRedoPosition(); 69 | } 70 | 71 | return -1; 72 | } 73 | 74 | @Override 75 | public final boolean canUndo() { 76 | return top > 0; 77 | } 78 | 79 | @Override 80 | public final boolean canRedo() { 81 | return top < commands.size(); 82 | } 83 | 84 | @Override 85 | public boolean isBatchEdit() { 86 | return isBatchEdit; 87 | } 88 | 89 | @Override 90 | public void beginBatchEdit() { 91 | isBatchEdit = true; 92 | } 93 | 94 | @Override 95 | public void endBatchEdit() { 96 | isBatchEdit = false; 97 | groupId++; 98 | } 99 | 100 | public void captureInsert(int start, int length, long time) { 101 | boolean mergeSuccess = false; 102 | 103 | if (canUndo()) { 104 | Command c = commands.get(top - 1); 105 | 106 | if (c instanceof InsertCommand && c.merge(start, length, time)) { 107 | mergeSuccess = true; 108 | } else { 109 | c.recordData(); 110 | } 111 | } 112 | 113 | if (!mergeSuccess) { 114 | push(new InsertCommand(start, length, groupId)); 115 | 116 | if (!isBatchEdit) { 117 | groupId++; 118 | } 119 | } 120 | 121 | lastEditTime = time; 122 | } 123 | 124 | public void captureDelete(int start, int length, long time) { 125 | boolean mergeSuccess = false; 126 | 127 | if (canUndo()) { 128 | Command c = commands.get(top - 1); 129 | 130 | if (c instanceof DeleteCommand && c.merge(start, length, time)) { 131 | mergeSuccess = true; 132 | } else { 133 | c.recordData(); 134 | } 135 | } 136 | 137 | if (!mergeSuccess) { 138 | push(new DeleteCommand(start, length, groupId)); 139 | 140 | if (!isBatchEdit) { 141 | groupId++; 142 | } 143 | } 144 | 145 | lastEditTime = time; 146 | } 147 | 148 | 149 | private abstract class Command { 150 | public final static long MERGE_TIME = 1000000000; //750ms in nanoseconds 151 | 152 | public int start, length; 153 | 154 | public String data; 155 | 156 | public int group; 157 | 158 | public abstract void undo(); 159 | 160 | public abstract void redo(); 161 | 162 | public abstract void recordData(); 163 | 164 | public abstract int findUndoPosition(); 165 | 166 | public abstract int findRedoPosition(); 167 | 168 | public abstract boolean merge(int start, int length, long time); 169 | } 170 | 171 | private class InsertCommand extends Command { 172 | 173 | public InsertCommand(int start, int length, int groupNumber) { 174 | this.start = start; 175 | this.length = length; 176 | group = groupNumber; 177 | } 178 | 179 | @Override 180 | public boolean merge(int newStart, int length, long time) { 181 | if (lastEditTime < 0) { 182 | return false; 183 | } 184 | 185 | if ((time - lastEditTime) < MERGE_TIME 186 | && newStart == start + this.length) { 187 | this.length += length; 188 | trimStack(); 189 | return true; 190 | } 191 | 192 | return false; 193 | } 194 | 195 | @Override 196 | public void recordData() { 197 | data = undoable.subSequence(start, length).toString(); 198 | } 199 | 200 | @Override 201 | public void undo() { 202 | if (data == null) { 203 | recordData(); 204 | undoable.shiftGapStart(-length); 205 | } else { 206 | //dummy timestamp of 0 207 | undoable.delete(start, length, 0, false); 208 | } 209 | } 210 | 211 | @Override 212 | public void redo() { 213 | //dummy timestamp of 0 214 | undoable.insert(data.toCharArray(), start, 0, false); 215 | } 216 | 217 | @Override 218 | public int findRedoPosition() { 219 | return start + length; 220 | } 221 | 222 | @Override 223 | public int findUndoPosition() { 224 | return start; 225 | } 226 | } 227 | 228 | 229 | private class DeleteCommand extends Command { 230 | 231 | public DeleteCommand(int start, int length, int seqNumber) { 232 | this.start = start; 233 | this.length = length; 234 | group = seqNumber; 235 | } 236 | 237 | @Override 238 | public boolean merge(int newStart, int length, long time) { 239 | if (lastEditTime < 0) { 240 | return false; 241 | } 242 | 243 | if ((time - lastEditTime) < MERGE_TIME 244 | && newStart == start - this.length - length + 1) { 245 | start = newStart; 246 | this.length += length; 247 | trimStack(); 248 | return true; 249 | } 250 | 251 | return false; 252 | } 253 | 254 | @Override 255 | public void recordData() { 256 | data = new String(undoable.subEditingSequence(length)); 257 | } 258 | 259 | @Override 260 | public void undo() { 261 | if (data == null) { 262 | recordData(); 263 | undoable.shiftGapStart(length); 264 | } else { 265 | //dummy timestamp of 0 266 | undoable.insert(data.toCharArray(), start, 0, false); 267 | } 268 | } 269 | 270 | @Override 271 | public void redo() { 272 | //dummy timestamp of 0 273 | undoable.delete(start, length, 0, false); 274 | } 275 | 276 | @Override 277 | public int findRedoPosition() { 278 | return start; 279 | } 280 | 281 | @Override 282 | public int findUndoPosition() { 283 | return start + length; 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/text/undo/Undoable.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.text.undo; 2 | 3 | public interface Undoable { 4 | String subSequence(int start, int length); 5 | 6 | void shiftGapStart(int distance); 7 | 8 | void delete(int start, int length, long timestamp, boolean undoable); 9 | 10 | void insert(char[] chars, int start, long timestamp, boolean undoable); 11 | 12 | char[] subEditingSequence(int length); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/utils/CharRange.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.utils; 2 | 3 | public class CharRange { 4 | int left,right; 5 | 6 | public CharRange(int left, int right) { 7 | this.left = left; 8 | this.right = right; 9 | } 10 | 11 | public int getLeft() { 12 | return left; 13 | } 14 | 15 | public void setLeft(int left) { 16 | this.left = left; 17 | } 18 | 19 | public int getRight() { 20 | return right; 21 | } 22 | 23 | public void setRight(int right) { 24 | this.right = right; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/utils/EditorException.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.utils; 2 | 3 | import android.util.Log; 4 | 5 | public class EditorException { 6 | private static final String TAG = EditorException.class.getSimpleName(); 7 | private static final boolean DEBUG = true; 8 | 9 | 10 | static public void fail(final String details) { 11 | if (DEBUG) { 12 | Log.e(TAG, "fail: " + details); 13 | 14 | } 15 | } 16 | 17 | static public void logIf(boolean condition, final String details) { 18 | 19 | if (condition) { 20 | fail(details); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/utils/Flag.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Tah Wei Hoon. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License Version 2.0, 5 | * with full text available at http://www.apache.org/licenses/LICENSE-2.0.html 6 | * 7 | * This software is provided "as is". Use at your own risk. 8 | */ 9 | package com.ve.view.utils; 10 | 11 | public class Flag { 12 | private boolean state = false; 13 | 14 | synchronized public final void set(){ 15 | state = true; 16 | } 17 | 18 | synchronized public final void clear(){ 19 | state = false; 20 | } 21 | 22 | synchronized public final boolean isSet(){ 23 | return state; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/utils/LinearSearchStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Tah Wei Hoon. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License Version 2.0, 5 | * with full text available at http://www.apache.org/licenses/LICENSE-2.0.html 6 | * 7 | * This software is provided "as is". Use at your own risk. 8 | */ 9 | package com.ve.view.utils; 10 | 11 | 12 | import com.ve.view.text.document.CommonLanguage; 13 | import com.ve.view.text.document.Document; 14 | 15 | public class LinearSearchStrategy implements SearchStrategy{ 16 | private int _unitsDone = 0; 17 | 18 | @Override 19 | // only applicable to replaceAll operation 20 | public int getProgress(){ 21 | return _unitsDone; 22 | } 23 | 24 | @Override 25 | public int wrappedFind(Document src, String target, int start, 26 | boolean isCaseSensitive, boolean isWholeWord){ 27 | 28 | // search towards end of doc first... 29 | int foundOffset = find(src, target, start, src.length(), 30 | isCaseSensitive, isWholeWord); 31 | // ...then from beginning of doc 32 | if(foundOffset < 0){ 33 | foundOffset = find(src, target, 0, start, 34 | isCaseSensitive, isWholeWord); 35 | } 36 | 37 | return foundOffset; 38 | } 39 | 40 | @Override 41 | public int find(Document src, String target, int start, int end, 42 | boolean isCaseSensitive, boolean isWholeWord) { 43 | if(target.length() == 0){ 44 | return -1; 45 | } 46 | if(start < 0){ 47 | EditorException.fail("BaseDocument.find: Invalid start position"); 48 | start = 0; 49 | } 50 | if(end > src.length()){ 51 | EditorException.fail("BaseDocument.find: Invalid end position"); 52 | end = src.length(); 53 | } 54 | 55 | end = Math.min(end, src.length() - target.length() + 1); 56 | int offset = start; 57 | while(offset < end){ 58 | if(equals(src, target, offset, isCaseSensitive) && 59 | (!isWholeWord || isSandwichedByWhitespace(src, offset, target.length())) ){ 60 | break; 61 | } 62 | 63 | ++offset; 64 | ++_unitsDone; 65 | } 66 | 67 | if (offset < end){ 68 | return offset; 69 | } 70 | else{ 71 | return -1; 72 | } 73 | } 74 | 75 | @Override 76 | public int wrappedFindBackwards(Document src, String target, int start, 77 | boolean isCaseSensitive, boolean isWholeWord){ 78 | 79 | // search towards beginning of doc first... 80 | int foundOffset = findBackwards(src, target, start, -1, 81 | isCaseSensitive, isWholeWord); 82 | // ...then from end of doc 83 | if(foundOffset < 0){ 84 | foundOffset = findBackwards(src, target, src.length()-1, start, 85 | isCaseSensitive, isWholeWord); 86 | } 87 | 88 | return foundOffset; 89 | } 90 | 91 | 92 | @Override 93 | public int findBackwards(Document src, String target, int start, int end, 94 | boolean isCaseSensitive, boolean isWholeWord) { 95 | if(target.length() == 0){ 96 | return -1; 97 | } 98 | if(start >= src.length()){ 99 | EditorException.fail("Invalid start position given to BaseDocument.find"); 100 | start = src.length() - 1; 101 | } 102 | if(end < -1){ 103 | EditorException.fail("Invalid end position given to BaseDocument.find"); 104 | end = -1; 105 | } 106 | int offset = Math.min(start, src.length()-target.length()); 107 | while(offset > end){ 108 | if(equals(src, target, offset, isCaseSensitive) && 109 | (!isWholeWord || isSandwichedByWhitespace(src, offset, target.length()) )){ 110 | break; 111 | } 112 | 113 | --offset; 114 | } 115 | 116 | if (offset > end){ 117 | return offset; 118 | } 119 | else{ 120 | return -1; 121 | } 122 | } 123 | 124 | @Override 125 | public Pair replaceAll(Document src, String searchText, 126 | String replacementText, int mark, 127 | boolean isCaseSensitive, boolean isWholeWord){ 128 | int replacementCount = 0; 129 | int anchor = mark; 130 | _unitsDone = 0; 131 | 132 | final char[] replacement = replacementText.toCharArray(); 133 | int foundIndex = find(src, searchText, 0, src.length(), 134 | isCaseSensitive, isWholeWord); 135 | long timestamp = System.nanoTime(); 136 | 137 | src.beginBatchEdit(); 138 | while (foundIndex != -1){ 139 | src.deleteAt(foundIndex, searchText.length(), timestamp); 140 | src.insertBefore(replacement, foundIndex, timestamp); 141 | if(foundIndex < anchor){ 142 | // adjust anchor because of differences in doc length 143 | // after word replacement 144 | anchor += replacementText.length() - searchText.length(); 145 | } 146 | ++replacementCount; 147 | _unitsDone += searchText.length(); //skip replaced chars 148 | foundIndex = find( 149 | src, 150 | searchText, 151 | foundIndex + replacementText.length(), 152 | src.length(), 153 | isCaseSensitive, 154 | isWholeWord); 155 | } 156 | src.endBatchEdit(); 157 | 158 | return new Pair(replacementCount, Math.max(anchor, 0)); 159 | } 160 | 161 | 162 | protected boolean equals(Document src, String target, 163 | int srcOffset, boolean isCaseSensitive){ 164 | if((src.length() - srcOffset) < target.length()){ 165 | //compared range in src must at least be as long as target 166 | return false; 167 | } 168 | 169 | int i; 170 | for(i = 0; i < target.length(); ++i){ 171 | if (isCaseSensitive && 172 | target.charAt(i) != src.charAt(i + srcOffset)){ 173 | return false; 174 | } 175 | // for case-insensitive search, compare both strings in lower case 176 | if (!isCaseSensitive && 177 | Character.toLowerCase(target.charAt(i)) != 178 | Character.toLowerCase(src.charAt(i + srcOffset))){ 179 | return false; 180 | } 181 | 182 | } 183 | 184 | return true; 185 | } 186 | 187 | /** 188 | * Checks if a word starting at startPosition with size length is bounded 189 | * by whitespace. 190 | */ 191 | protected boolean isSandwichedByWhitespace(Document src, 192 | int start, int length){ 193 | boolean startWithWhitespace = (start == 0) 194 | ? true 195 | : CommonLanguage.isWhitespace(src.charAt(start - 1)); 196 | 197 | int end = start + length; 198 | boolean endWithWhitespace = (end == src.length()) 199 | ? true 200 | : CommonLanguage.isWhitespace(src.charAt(end)); 201 | 202 | return (startWithWhitespace && endWithWhitespace); 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/utils/Pair.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Tah Wei Hoon. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License Version 2.0, 5 | * with full text available at http://www.apache.org/licenses/LICENSE-2.0.html 6 | * 7 | * This software is provided "as is". Use at your own risk. 8 | */ 9 | package com.ve.view.utils; 10 | 11 | public final class Pair { 12 | private int _first; 13 | private int _second; 14 | 15 | public Pair(int x, int y){ 16 | _first = x; 17 | _second = y; 18 | } 19 | 20 | public final int getFirst(){ 21 | return _first; 22 | } 23 | 24 | public final int getSecond(){ 25 | return _second; 26 | } 27 | 28 | public final void setFirst(int value){ 29 | _first = value; 30 | } 31 | 32 | public final void setSecond(int value){ 33 | _second = value; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/utils/Rectangle.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.utils; 2 | 3 | import android.graphics.Rect; 4 | import android.graphics.RectF; 5 | 6 | public class Rectangle { 7 | public int x;//left 8 | public int y;//top 9 | public int width; 10 | public int height; 11 | 12 | public Rectangle() { 13 | } 14 | public static void main(String[] args) { 15 | Rectangle rectangle=new Rectangle(3,4,8,8); 16 | System.out.println(rectangle); 17 | System.out.println(rectangle.right()); 18 | System.out.println(rectangle.bottom()); 19 | } 20 | 21 | public Rectangle(int x, int y, int width, int height) { 22 | this.x = x; 23 | this.y = y; 24 | this.width = width; 25 | this.height = height; 26 | } 27 | 28 | public void setBounds(int x, int y, int width, int height) { 29 | this.x = x; 30 | this.y = y; 31 | this.width = width; 32 | this.height = height; 33 | } 34 | 35 | public int right() { 36 | return x + width; 37 | } 38 | 39 | public int bottom() { 40 | return y + height; 41 | } 42 | 43 | 44 | public RectF toRectF() { 45 | return new RectF(x, y, right(), bottom()); 46 | } 47 | 48 | public Rect toRect() { 49 | return new Rect(x, y, right(), bottom()); 50 | } 51 | 52 | public boolean intersects(Rectangle bounds) { 53 | return this.x >= bounds.x && this.x < bounds.x + bounds.width && this.y >= bounds.y && this.y < bounds.y + bounds.height; 54 | } 55 | 56 | public boolean contains(int x, int y) { 57 | return x >= this.x && x < this.x + this.width && y >= this.y && y < this.y + this.height; 58 | } 59 | 60 | public int getCenterX() { 61 | return this.x + this.width / 2; 62 | } 63 | 64 | public int getCenterY() { 65 | return this.y + this.height / 2; 66 | } 67 | 68 | public void setPosition(int x, int y) { 69 | this.x = x; 70 | this.y = y; 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | return String.format("(%d,%d,%d,%d)\n",x,y,width,height); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/utils/SearchStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Tah Wei Hoon. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License Version 2.0, 5 | * with full text available at http://www.apache.org/licenses/LICENSE-2.0.html 6 | * 7 | * This software is provided "as is". Use at your own risk. 8 | */ 9 | package com.ve.view.utils; 10 | 11 | import com.ve.view.text.document.Document; 12 | 13 | public interface SearchStrategy { 14 | /** 15 | * Searches for target, starting from start (inclusive), 16 | * and stopping at end (exclusive). 17 | * 18 | * @return charOffset of found string; -1 if not found 19 | */ 20 | public int find(Document src, String target, int start, int end, 21 | boolean isCaseSensitive, boolean isWholeWord); 22 | 23 | /** 24 | * Searches for target, starting from start (inclusive), 25 | * wrapping around to the beginning of document and 26 | * stopping at start (exclusive). 27 | * 28 | * @return charOffset of found string; -1 if not found 29 | */ 30 | public int wrappedFind(Document src, String target, int start, 31 | boolean isCaseSensitive, boolean isWholeWord); 32 | 33 | /** 34 | * Searches backwards from startCharOffset (inclusive), 35 | * and stopping at end (exclusive). 36 | * 37 | * @return charOffset of found string; -1 if not found 38 | */ 39 | public int findBackwards(Document src, String target, int start, int end, 40 | boolean isCaseSensitive, boolean isWholeWord); 41 | 42 | /** 43 | * Searches backwards from start (inclusive), wrapping around to 44 | * the end of document and stopping at start (exclusive). 45 | * 46 | * @return charOffset of found string; -1 if not found 47 | */ 48 | public int wrappedFindBackwards(Document src, String target, int start, 49 | boolean isCaseSensitive, boolean isWholeWord); 50 | 51 | /** 52 | * Replace all matches of searchText in src with replacementText. 53 | * 54 | * @param mark Optional. A position in src that can be tracked for changes. 55 | * After replacements are made, the position may be shifted because of 56 | * insertion/deletion of text before it. The new position of mark is 57 | * returned in Pair.second. If mark is an invalid position, Pair.second 58 | * is undefined. 59 | * 60 | * @return Pair.first is the number of replacements made. 61 | * Pair.second is new position of mark after replacements are made. 62 | */ 63 | public Pair replaceAll(Document src, String searchText, 64 | String replacementText, int mark, 65 | boolean isCaseSensitive, boolean isWholeWord); 66 | 67 | 68 | /** 69 | * The number of characters that have been examined by the current find 70 | * operation. This method is not synchronized, and the value returned 71 | * may be outdated. 72 | * 73 | * @return The number of characters searched so far 74 | */ 75 | public int getProgress(); 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/ve/view/utils/ZoomChecker.java: -------------------------------------------------------------------------------- 1 | package com.ve.view.utils; 2 | 3 | import android.view.MotionEvent; 4 | 5 | public class ZoomChecker { 6 | 7 | private ZoomListener listener; 8 | private double distance; 9 | private double scaleFrom = 1, scaleCurrent = 1; 10 | 11 | 12 | private static double calculateDistance(MotionEvent event) { 13 | float dx = event.getX(0) - event.getX(1); 14 | float dy = event.getY(0) - event.getY(1); 15 | return Math.sqrt(dx * dx + dy * dy); 16 | } 17 | 18 | public boolean checkZoom(MotionEvent event) { 19 | if (event.getAction() != MotionEvent.ACTION_MOVE || event.getPointerCount() != 2) { 20 | reset(); 21 | return false; 22 | } 23 | 24 | double newDistance = calculateDistance(event); 25 | if (!isZooming()) { 26 | distance = newDistance; 27 | //listener.onZoomStart(); 28 | scaleCurrent = scaleFrom; 29 | 30 | } else { 31 | if (listener != null) { 32 | double zoom = newDistance / distance; 33 | scaleCurrent = zoom * scaleFrom; 34 | listener.onZoom(zoom,scaleCurrent); 35 | } 36 | } 37 | return true; 38 | 39 | } 40 | 41 | public void reset() { 42 | distance = -1; 43 | scaleFrom = scaleCurrent; 44 | //listener.onZoomEnd(); 45 | 46 | } 47 | 48 | public void setListener(ZoomListener listener) { 49 | this.listener = listener; 50 | } 51 | 52 | public boolean isZooming() { 53 | return distance > 0; 54 | } 55 | 56 | public interface ZoomListener { 57 | void onZoom(double scale,double scaleAll); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /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/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ve3344/CodeEditor/5e785d20085ebfe92bf01bbbb599ab5326862c0a/app/src/main/res/drawable/cursor.png -------------------------------------------------------------------------------- /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 | 9 | 10 | 15 | 16 | 20 | 21 |