├── .github └── workflows │ └── build-debug.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mrikso │ │ └── texteditor │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mrikso │ │ │ └── texteditor │ │ │ ├── MainActivity.java │ │ │ └── TextEditor.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── 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 │ └── mrikso │ └── texteditor │ └── ExampleUnitTest.java ├── build.gradle ├── codeeditor ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mrikso │ │ └── codeeditor │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mrikso │ │ │ └── codeeditor │ │ │ ├── common │ │ │ ├── OnCaretScrollListener.java │ │ │ ├── OnKeyShortcutListener.java │ │ │ ├── OnRowChangedListener.java │ │ │ ├── OnSelectionChangedListener.java │ │ │ └── OnTextChangeListener.java │ │ │ ├── lang │ │ │ ├── Language.java │ │ │ ├── LanguageC.java │ │ │ ├── LanguageCFamily.java │ │ │ ├── LanguageCpp.java │ │ │ ├── LanguageCsharp.java │ │ │ ├── LanguageJava.java │ │ │ ├── LanguageJavascript.java │ │ │ ├── LanguageLua.java │ │ │ ├── LanguageNonProg.java │ │ │ ├── LanguageObjectiveC.java │ │ │ ├── LanguagePHP.java │ │ │ ├── LanguagePython.java │ │ │ ├── LanguageRuby.java │ │ │ ├── LanguageSmali.java │ │ │ └── xml │ │ │ │ └── XMLLanguage.java │ │ │ ├── util │ │ │ ├── DLog.java │ │ │ ├── Document.java │ │ │ ├── DocumentProvider.java │ │ │ ├── FindThread.java │ │ │ ├── Flag.java │ │ │ ├── HelperUtils.java │ │ │ ├── Lexer.java │ │ │ ├── LinearSearchStrategy.java │ │ │ ├── Pair.java │ │ │ ├── ProgressObserver.java │ │ │ ├── ProgressSource.java │ │ │ ├── SearchStrategy.java │ │ │ ├── TextBuffer.java │ │ │ ├── TextBufferCache.java │ │ │ ├── TextWarriorException.java │ │ │ └── UndoStack.java │ │ │ └── view │ │ │ ├── ClipboardPanel.java │ │ │ ├── ColorScheme.java │ │ │ ├── ColorSchemeLight.java │ │ │ ├── FreeScrollingTextField.java │ │ │ ├── KeysInterpreter.java │ │ │ ├── TextFieldController.java │ │ │ ├── TextFieldInputConnection.java │ │ │ ├── TouchNavigationMethod.java │ │ │ ├── YoyoNavigationMethod.java │ │ │ └── autocomplete │ │ │ ├── AutoCompletePanel.java │ │ │ ├── AutoPanelAdapter.java │ │ │ └── ListItem.java │ └── res │ │ ├── drawable │ │ └── icon_method.png │ │ └── layout │ │ └── auto_panel_item.xml │ └── test │ └── java │ └── com │ └── mrikso │ └── codeeditor │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/workflows/build-debug.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | # push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | name: Build debug apk 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Cancel previous runs 14 | uses: styfle/cancel-workflow-action@0.5.0 15 | with: 16 | access_token: ${{ github.token }} 17 | 18 | - uses: actions/checkout@v2 19 | 20 | - name: set up JDK 11 21 | uses: actions/setup-java@v2 22 | with: 23 | java-version: '11' 24 | distribution: 'adopt' 25 | cache: gradle 26 | 27 | - name: Grant execute permission for gradlew 28 | run: chmod +x gradlew 29 | 30 | - name: Build debug apk 31 | uses: eskatos/gradle-command-action@v1 32 | with: 33 | arguments: assembleDebug 34 | distributions-cache-enabled: true 35 | dependencies-cache-enabled: true 36 | configuration-cache-enabled: true 37 | 38 | - name: Upload debug apk 39 | uses: actions/upload-artifact@v2 40 | if: ${{ !github.head_ref }} 41 | with: 42 | name: apk-debug 43 | path: app/build/outputs/apk/debug 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeEditor 2 | Code editor for android, Based TextWarrior View. 3 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.mrikso.texteditor" 9 | minSdkVersion 21 10 | targetSdkVersion 29 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | compileOptions { 24 | sourceCompatibility = 1.8 25 | targetCompatibility = 1.8 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(dir: 'libs', include: ['*.jar']) 31 | // implementation project(':lib-n-ide') 32 | implementation 'androidx.appcompat:appcompat:1.1.0' 33 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 34 | testImplementation 'junit:junit:4.12' 35 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 36 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 37 | implementation project(path: ':codeeditor') 38 | } 39 | -------------------------------------------------------------------------------- /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/mrikso/texteditor/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.texteditor; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.mrikso.texteditor", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrikso/texteditor/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.texteditor; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | 6 | import android.os.Bundle; 7 | 8 | //import com.mrikso.codeeditor.TextEditor; 9 | 10 | 11 | public class MainActivity extends AppCompatActivity { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_main); 17 | TextEditor textEditor = findViewById(R.id.codeEditor); 18 | textEditor.setText("lol"); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrikso/texteditor/TextEditor.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.texteditor; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.graphics.Typeface; 6 | import android.util.AttributeSet; 7 | import android.util.DisplayMetrics; 8 | import android.util.TypedValue; 9 | import android.view.KeyEvent; 10 | import android.widget.Toast; 11 | 12 | import com.mrikso.codeeditor.lang.Language; 13 | import com.mrikso.codeeditor.lang.LanguageJava; 14 | import com.mrikso.codeeditor.util.Document; 15 | import com.mrikso.codeeditor.util.DocumentProvider; 16 | import com.mrikso.codeeditor.util.Lexer; 17 | import com.mrikso.codeeditor.view.ColorScheme; 18 | import com.mrikso.codeeditor.view.FreeScrollingTextField; 19 | import com.mrikso.codeeditor.view.YoyoNavigationMethod; 20 | import com.mrikso.codeeditor.view.autocomplete.AutoCompletePanel; 21 | 22 | import java.io.File; 23 | 24 | 25 | public class TextEditor extends FreeScrollingTextField { 26 | private Document _inputtingDoc; 27 | private boolean _isWordWrap; 28 | private Context mContext; 29 | private String _lastSelectFile; 30 | private int _index; 31 | private Toast toast; 32 | /* 33 | private Handler handler = new Handler() { 34 | @Override 35 | public void handleMessage(Message msg) { 36 | switch (msg.what) { 37 | 38 | case ReadThread.MSG_READ_OK: 39 | setText(msg.obj.toString()); 40 | break; 41 | case ReadThread.MSG_READ_FAIL: 42 | showToast("打开失败"); 43 | break; 44 | case WriteThread.MSG_WRITE_OK: 45 | showToast("保存成功"); 46 | break; 47 | case WriteThread.MSG_WRITE_FAIL: 48 | showToast("保存失败"); 49 | break; 50 | } 51 | } 52 | }; 53 | /*/ 54 | public TextEditor(Context context) { 55 | super(context); 56 | mContext = context; 57 | init(); 58 | } 59 | 60 | public TextEditor(Context context, AttributeSet attributeSet) { 61 | super(context, attributeSet); 62 | mContext = context; 63 | init(); 64 | } 65 | 66 | private void init() { 67 | setVerticalScrollBarEnabled(true); 68 | setTypeface(Typeface.MONOSPACE); 69 | DisplayMetrics dm = mContext.getResources().getDisplayMetrics(); 70 | //设置字体大小 71 | float size = TypedValue.applyDimension(2, BASE_TEXT_SIZE_PIXELS, dm); 72 | setTextSize((int) size); 73 | setShowLineNumbers(true); 74 | //setAutoCompete(true); 75 | setHighlightCurrentRow(true); 76 | setWordWrap(true); 77 | setAutoComplete(true); 78 | setAutoIndent(true); 79 | setUseGboard(true); 80 | setAutoIndentWidth(2); 81 | setLanguage(LanguageJava.getInstance()); 82 | setNavigationMethod(new YoyoNavigationMethod(this)); 83 | int textColor = Color.BLACK;// 默认文字颜色 84 | int selectionText = Color.argb(255, 0, 120, 215);//选择文字颜色 85 | setTextColor(textColor); 86 | setTextHighlightColor(selectionText); 87 | } 88 | 89 | @Override 90 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 91 | // TODO: Implement this method 92 | super.onLayout(changed, left, top, right, bottom); 93 | if (_index != 0 && right > 0) { 94 | moveCaret(_index); 95 | _index = 0; 96 | } 97 | } 98 | 99 | public void setKeywordColor(int color) { 100 | getColorScheme().setColor(ColorScheme.Colorable.KEYWORD, color); 101 | } 102 | 103 | public void setBaseWordColor(int color) { 104 | getColorScheme().setColor(ColorScheme.Colorable.NAME, color); 105 | } 106 | 107 | public void setStringColor(int color) { 108 | getColorScheme().setColor(ColorScheme.Colorable.STRING, color); 109 | } 110 | 111 | public void setCommentColor(int color) { 112 | getColorScheme().setColor(ColorScheme.Colorable.COMMENT, color); 113 | } 114 | 115 | public void setBackgroundColor(int color) { 116 | getColorScheme().setColor(ColorScheme.Colorable.BACKGROUND, color); 117 | } 118 | 119 | public void setTextColor(int color) { 120 | getColorScheme().setColor(ColorScheme.Colorable.FOREGROUND, color); 121 | } 122 | 123 | public void setTextHighlightColor(int color) { 124 | getColorScheme().setColor(ColorScheme.Colorable.SELECTION_BACKGROUND, color); 125 | } 126 | 127 | public void setLanguage(Language language){ 128 | AutoCompletePanel.setLanguage(language); 129 | Lexer.setLanguage(language); 130 | } 131 | 132 | public String getSelectedText() { 133 | // TODO: Implement this method 134 | return hDoc.subSequence(getSelectionStart(), getSelectionEnd() - getSelectionStart()).toString(); 135 | } 136 | 137 | public void gotoLine(int line) { 138 | if (line > hDoc.getRowCount()) { 139 | line = hDoc.getRowCount(); 140 | 141 | } 142 | int i = getText().getLineOffset(line - 1); 143 | setSelection(i); 144 | } 145 | 146 | @Override 147 | public boolean onKeyShortcut(int keyCode, KeyEvent event) { 148 | final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK; 149 | if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) { 150 | switch (keyCode) { 151 | case KeyEvent.KEYCODE_A: 152 | selectAll(); 153 | return true; 154 | case KeyEvent.KEYCODE_X: 155 | cut(); 156 | return true; 157 | case KeyEvent.KEYCODE_C: 158 | copy(); 159 | return true; 160 | case KeyEvent.KEYCODE_V: 161 | paste(); 162 | return true; 163 | } 164 | } 165 | return super.onKeyShortcut(keyCode, event); 166 | } 167 | 168 | @Override 169 | public void setWordWrap(boolean enable) { 170 | // TODO: Implement this method 171 | _isWordWrap = enable; 172 | super.setWordWrap(enable); 173 | } 174 | 175 | public DocumentProvider getText() { 176 | return createDocumentProvider(); 177 | } 178 | 179 | public void setText(CharSequence c) { 180 | Document doc = new Document(this); 181 | doc.setWordWrap(_isWordWrap); 182 | doc.setText(c); 183 | setDocumentProvider(new DocumentProvider(doc)); 184 | } 185 | 186 | public File getOpenedFile() { 187 | if (_lastSelectFile != null) 188 | return new File(_lastSelectFile); 189 | 190 | return null; 191 | } 192 | 193 | public void setOpenedFile(String file) { 194 | _lastSelectFile = file; 195 | } 196 | 197 | public void insert(int idx, String text) { 198 | selectText(false); 199 | moveCaret(idx); 200 | paste(text); 201 | } 202 | 203 | public void replaceAll(CharSequence c) { 204 | replaceText(0, getLength() - 1, c.toString()); 205 | } 206 | 207 | public void setSelection(int index) { 208 | selectText(false); 209 | if (!hasLayout()) 210 | moveCaret(index); 211 | else 212 | _index = index; 213 | } 214 | 215 | public void undo() { 216 | 217 | DocumentProvider doc = createDocumentProvider(); 218 | int newPosition = doc.undo(); 219 | 220 | if (newPosition >= 0) { 221 | //TODO editor.setEdited(false); 222 | // if reached original condition of file 223 | setEdited(true); 224 | respan(); 225 | selectText(false); 226 | moveCaret(newPosition); 227 | invalidate(); 228 | } 229 | 230 | } 231 | 232 | public void redo() { 233 | 234 | DocumentProvider doc = createDocumentProvider(); 235 | int newPosition = doc.redo(); 236 | 237 | if (newPosition >= 0) { 238 | setEdited(true); 239 | 240 | respan(); 241 | selectText(false); 242 | moveCaret(newPosition); 243 | invalidate(); 244 | } 245 | 246 | } 247 | /* 248 | public void open(String filename) { 249 | _lastSelectFile = filename; 250 | 251 | File inputFile = new File(filename); 252 | _inputtingDoc = new Document(this); 253 | _inputtingDoc.setWordWrap(this.isWordWrap()); 254 | ReadThread readThread = new ReadThread(inputFile.getAbsolutePath(), handler); 255 | readThread.start(); 256 | } 257 | 258 | /** 259 | * 保存文件 260 | * * @param file 261 | */ 262 | /* 263 | public void save(String file) { 264 | WriteThread writeThread = new WriteThread(getText().toString(), file, handler); 265 | writeThread.start(); 266 | } 267 | 268 | 269 | */ 270 | private void showToast(CharSequence text) { 271 | if (toast == null) { 272 | toast = Toast.makeText(mContext, text, Toast.LENGTH_SHORT); 273 | } else { 274 | toast.setText(text); 275 | } 276 | toast.show(); 277 | } 278 | } 279 | 280 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIkso/CodeEditor/a807a1459eae6a94b84c5687584677ae9eac8013/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIkso/CodeEditor/a807a1459eae6a94b84c5687584677ae9eac8013/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIkso/CodeEditor/a807a1459eae6a94b84c5687584677ae9eac8013/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIkso/CodeEditor/a807a1459eae6a94b84c5687584677ae9eac8013/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIkso/CodeEditor/a807a1459eae6a94b84c5687584677ae9eac8013/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIkso/CodeEditor/a807a1459eae6a94b84c5687584677ae9eac8013/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIkso/CodeEditor/a807a1459eae6a94b84c5687584677ae9eac8013/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIkso/CodeEditor/a807a1459eae6a94b84c5687584677ae9eac8013/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIkso/CodeEditor/a807a1459eae6a94b84c5687584677ae9eac8013/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIkso/CodeEditor/a807a1459eae6a94b84c5687584677ae9eac8013/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Text Editor 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/mrikso/texteditor/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.texteditor; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.6.3' 12 | 13 | 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /codeeditor/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /codeeditor/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.2" 6 | 7 | defaultConfig { 8 | minSdkVersion 21 9 | targetSdkVersion 29 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles 'consumer-rules.pro' 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | compileOptions { 24 | sourceCompatibility = 1.8 25 | targetCompatibility = 1.8 26 | } 27 | 28 | } 29 | 30 | dependencies { 31 | implementation fileTree(dir: 'libs', include: ['*.jar']) 32 | 33 | implementation 'androidx.appcompat:appcompat:1.1.0' 34 | implementation 'commons-io:commons-io:2.6' 35 | // implementation 'org.apache.commons:commons-lang3:3.7 36 | testImplementation 'junit:junit:4.12' 37 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 38 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 39 | } 40 | -------------------------------------------------------------------------------- /codeeditor/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIkso/CodeEditor/a807a1459eae6a94b84c5687584677ae9eac8013/codeeditor/consumer-rules.pro -------------------------------------------------------------------------------- /codeeditor/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 | -------------------------------------------------------------------------------- /codeeditor/src/androidTest/java/com/mrikso/codeeditor/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.mrikso.codeeditor.test", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /codeeditor/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/common/OnCaretScrollListener.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor.common; 2 | 3 | public interface OnCaretScrollListener { 4 | void updateCaret(int caretIndex); 5 | } 6 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/common/OnKeyShortcutListener.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor.common; 2 | 3 | import android.view.KeyEvent; 4 | 5 | public interface OnKeyShortcutListener { 6 | boolean onKeyShortcut(int keyCode,KeyEvent event); 7 | } 8 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/common/OnRowChangedListener.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor.common; 2 | 3 | public interface OnRowChangedListener { 4 | void onRowChanged(int newRowIndex); 5 | } 6 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/common/OnSelectionChangedListener.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor.common; 2 | 3 | public interface OnSelectionChangedListener { 4 | void onSelectionChanged(boolean active,int selStart, int selEnd); 5 | } 6 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/common/OnTextChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor.common; 2 | 3 | public interface OnTextChangeListener { 4 | void onNewLine(String s, int caretPosition, int pos); 5 | 6 | void onDel(CharSequence text, int cursorPosition, int delCount); 7 | 8 | void onAdd(CharSequence text, int cursorPosition, int addCount); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/Language.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.mrikso.codeeditor.lang; 10 | 11 | import com.mrikso.codeeditor.util.Lexer; 12 | 13 | import java.util.HashMap; 14 | import java.util.*; 15 | 16 | /** 17 | * Base class for programming language syntax. 18 | * By default, C-like symbols and operators are included, but not keywords. 19 | */ 20 | public abstract class Language 21 | { 22 | public final static char EOF = '\uFFFF'; 23 | public final static char NULL_CHAR = '\u0000'; 24 | public final static char NEWLINE = '\n'; 25 | public final static char BACKSPACE = '\b'; 26 | public final static char TAB = '\t'; 27 | public final static String GLYPH_NEWLINE = "\u21b5"; 28 | public final static String GLYPH_SPACE = "\u00b7"; 29 | public final static String GLYPH_TAB = "\u00bb"; 30 | 31 | 32 | private final static char[] BASIC_C_OPERATORS = { 33 | '(', ')', '{', '}', '.', ',', ';', '=', '+', '-', 34 | '/', '*', '&', '!', '|', ':', '[', ']', '<', '>', 35 | '?', '~', '%', '^' 36 | }; 37 | 38 | 39 | protected HashMap _keywordsMap = new HashMap(0); 40 | protected HashMap _namesMap = new HashMap(0); 41 | protected HashMap _basesMap = new HashMap(0); 42 | protected HashMap _usersMap = new HashMap(0);//userWord是那种用户自己定义的标识符 43 | protected HashMap _operatorsMap = generateOperators(BASIC_C_OPERATORS); 44 | 45 | private ArrayList _userCache = new ArrayList(); 46 | private String[] _userWords=new String[0]; 47 | private String[] _keyword; 48 | private String[] _name = new String[0]; 49 | 50 | public void updateUserWord() 51 | { 52 | // TODO: Implement this method 53 | String[] uw = new String[_userCache.size()]; 54 | _userWords = _userCache.toArray(uw); 55 | } 56 | 57 | public String[] getUserWord() 58 | { 59 | return _userWords; 60 | } 61 | 62 | public String[] getNames() 63 | { 64 | return _name; 65 | } 66 | 67 | public String[] getBasePackage(String name) 68 | { 69 | return _basesMap.get(name); 70 | } 71 | 72 | public String[] getKeywords() 73 | { 74 | return _keyword; 75 | } 76 | 77 | public void setKeywords(String[] keywords) 78 | { 79 | _keyword = new String[keywords.length]; 80 | for(int i=0;i(keywords.length); 85 | for (int i = 0; i < keywords.length; ++i) 86 | { 87 | _keywordsMap.put(keywords[i], Lexer.KEYWORD); 88 | } 89 | } 90 | 91 | public void setNames(String[] names) 92 | { 93 | _name = names; 94 | ArrayList buf=new ArrayList(); 95 | _namesMap = new HashMap(names.length); 96 | for (int i = 0; i < names.length; ++i) 97 | { 98 | if(!buf.contains(names[i])) 99 | buf.add(names[i]); 100 | _namesMap.put(names[i], Lexer.NAME); 101 | } 102 | _name=new String[buf.size()]; 103 | buf.toArray(_name); 104 | } 105 | 106 | public void addBasePackage(String name, String[] names) 107 | { 108 | _basesMap.put(name, names); 109 | } 110 | 111 | public void clearUserWord() 112 | { 113 | _userCache.clear(); 114 | _usersMap.clear(); 115 | } 116 | 117 | public void addUserWord(String name) 118 | { 119 | if(!_userCache.contains(name) && !_namesMap.containsKey(name)) 120 | _userCache.add(name); 121 | _usersMap.put(name, Lexer.NAME); 122 | } 123 | 124 | protected void setOperators(char[] operators) 125 | { 126 | _operatorsMap = generateOperators(operators); 127 | } 128 | 129 | private HashMap generateOperators(char[] operators) 130 | { 131 | HashMap operatorsMap = new HashMap(operators.length); 132 | for (int i = 0; i < operators.length; ++i) 133 | { 134 | operatorsMap.put(operators[i], Lexer.OPERATOR); 135 | } 136 | return operatorsMap; 137 | } 138 | 139 | public final boolean isOperator(char c) 140 | { 141 | return _operatorsMap.containsKey(c); 142 | } 143 | 144 | public final boolean isKeyword(String s) 145 | { 146 | return _keywordsMap.containsKey(s); 147 | } 148 | 149 | public final boolean isName(String s) 150 | { 151 | return _namesMap.containsKey(s); 152 | } 153 | 154 | public final boolean isBasePackage(String s) 155 | { 156 | return _basesMap.containsKey(s); 157 | } 158 | 159 | public final boolean isBaseWord(String p, String s) 160 | { 161 | String[] pkg= _basesMap.get(p); 162 | for (String n:pkg) 163 | { 164 | if (n.equals(s)) 165 | return true; 166 | } 167 | return false; 168 | } 169 | 170 | public final boolean isUserWord(String s) 171 | { 172 | return _usersMap.containsKey(s); 173 | } 174 | 175 | private boolean contains(String[] a, String s) 176 | { 177 | for (String n:a) 178 | { 179 | if (n.equals(s)) 180 | return true; 181 | } 182 | return false; 183 | } 184 | 185 | private boolean contains(ArrayList a, String s) 186 | { 187 | for (String n:a) 188 | { 189 | if (n.equals(s)) 190 | return true; 191 | } 192 | return false; 193 | } 194 | 195 | /** 196 | * 空白符 197 | * @param c 198 | * @return 199 | */ 200 | public boolean isWhitespace(char c) 201 | { 202 | return (c == ' ' || c == '\n' || c == '\t' || 203 | c == '\r' || c == '\f' || c == EOF); 204 | } 205 | 206 | /** 207 | * 点运算符 208 | * @param c 209 | * @return 210 | */ 211 | public boolean isSentenceTerminator(char c) 212 | { 213 | return (c == '.'); 214 | } 215 | 216 | /** 217 | * 斜杠 218 | * @param c 219 | * @return 220 | */ 221 | public boolean isEscapeChar(char c) 222 | { 223 | return (c == '\\'); 224 | } 225 | 226 | /** 227 | * Derived classes that do not do represent C-like programming languages 228 | * should return false; otherwise return true 229 | */ 230 | public boolean isProgLang() 231 | { 232 | return true; 233 | } 234 | 235 | /** 236 | * Whether the word after c is a token 237 | */ 238 | public boolean isWordStart(char c) 239 | { 240 | return false; 241 | } 242 | 243 | /** 244 | * Whether cSc is a token, where S is a sequence of characters that are on the same line 245 | * 字符串引号 246 | */ 247 | public boolean isDelimiterA(char c) 248 | { 249 | return (c == '"'); 250 | } 251 | 252 | /** 253 | * Same concept as isDelimiterA(char), but Language and its subclasses can 254 | * specify a second type of symbol to use here 255 | * 单个字符引号 256 | */ 257 | public boolean isDelimiterB(char c) 258 | { 259 | return (c == '\''); 260 | } 261 | 262 | /** 263 | * Whether cL is a token, where L is a sequence of characters until the end of the line 264 | * 宏定义 265 | */ 266 | public boolean isLineAStart(char c) 267 | { 268 | return (c == '#'); 269 | } 270 | 271 | /** 272 | * Same concept as isLineAStart(char), but Language and its subclasses can 273 | * specify a second type of symbol to use here 274 | */ 275 | public boolean isLineBStart(char c) 276 | { 277 | return false; 278 | } 279 | 280 | /** 281 | * Whether c0c1L is a token, where L is a sequence of characters until the end of the line 282 | * 单行注释 283 | */ 284 | public boolean isLineStart(char c0, char c1) 285 | { 286 | return (c0 == '/' && c1 == '/'); 287 | } 288 | 289 | /** 290 | * Whether c0c1 signifies the start of a multi-line token 291 | * 多行注释开始 292 | */ 293 | public boolean isMultilineStartDelimiter(char c0, char c1) 294 | { 295 | return (c0 == '/' && c1 == '*'); 296 | } 297 | 298 | /** 299 | * Whether c0c1 signifies the end of a multi-line token 300 | * 多行注释结束 301 | */ 302 | public boolean isMultilineEndDelimiter(char c0, char c1) 303 | { 304 | return (c0 == '*' && c1 == '/'); 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/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.mrikso.codeeditor.lang; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | 14 | /** 15 | * Singleton class containing the symbols and operators of the C language 16 | */ 17 | public class LanguageC extends Language{ 18 | private static Language _theOne = null; 19 | 20 | private final static String[] keywords = { 21 | "char", "double", "float", "int", "long", "short", "void", 22 | "auto", "const", "extern", "register", "static", "volatile", 23 | "signed", "unsigned", "sizeof", "typedef", 24 | "enum", "struct", "union", 25 | "break", "case", "continue", "default", "do", "else", "for", 26 | "goto", "if", "return", "switch", "while", 27 | "define","include","ifdef","endif","ifndef","error","elif","line","pragma","undef" 28 | }; 29 | private final static String[] functions={ 30 | "abort","abs","acos","asctime","asin","assert","atan","atan2","atexit","atof","atoi","atol" 31 | ,"bsearch","calloc","ceil","clearerr","clock","cos","cosh","ctime","difftime","div" 32 | ,"exit","exp","fabs","fclose","feof","ferror","fflush","fgetc","fgetpos","fgets","floor" 33 | ,"fmod","fopen","fprintf","fputc","fputs","fread","free","freopen","frexp","fscanf","fseek","fsetpos","ftell","fwrite" 34 | ,"getc","getchar","getenv","gets","gmtime","isalnum","isalpha","iscntrl","isdigit","isgraph","islower","isprint","ispunct","isspace","isupper","isxdigit","labs","ldexp","ldiv","localtime","log","log10","longjmp" 35 | ,"main","malloc","memchr","memcmp","memcpy","memmove","memset","mktime","modf","perror","pow","printf" 36 | ,"putc","putchar","puts","qsort","raise","rand","realloc","remove","rename","rewind" 37 | ,"scanf","setbuf","setjmp","setvbuf","signal","sin","sinh","sprintf","sqrt","srand","sscanf","strcat","strchr","strcmp","strcoll","strcpy","strcspn","strerror","strftime","strlen","strncat","strncmp","strncpy","strpbrk","strrchr","strspn","strstr","strtod","strtok","strtol","strtoul","strxfrm","system" 38 | ,"tan","tanh","time","tmpfile","tmpnam","tolower","toupper","ungetc","va_arg","vprintf","vfprintf" 39 | ,"__LINE__","__FILE__","__DATE__","__TIME__","_cplusplus","__STDC__" 40 | 41 | }; 42 | private final static String[] header={ 43 | "math.h","stdio.h","stdlib.h","string.h","time.h","errno.h","ctype.h","local.h" 44 | }; 45 | private final static char[] BASIC_C_OPERATORS = { 46 | '(', ')', '{', '}', '.', ',', ';', '=', '+', '-', 47 | '/', '*', '&', '!', '|', ':', '[', ']', '<', '>', 48 | '?', '~', '%', '^' 49 | }; 50 | public static Language getInstance(){ 51 | if(_theOne == null){ 52 | _theOne = new LanguageC(); 53 | } 54 | return _theOne; 55 | } 56 | 57 | private LanguageC(){ 58 | String[] diyWord= new String[header.length+functions.length]; 59 | System.arraycopy(functions,0,diyWord,0,functions.length); 60 | System.arraycopy(header,0,diyWord,functions.length,header.length); 61 | setKeywords(keywords); 62 | setNames(diyWord); 63 | setOperators(BASIC_C_OPERATORS); 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/LanguageCFamily.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 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.mrikso.codeeditor.lang; 10 | 11 | import com.mrikso.codeeditor.util.Lexer; 12 | 13 | import java.util.HashMap; 14 | 15 | /** 16 | * Singleton class containing C-like symbols and operators but no keywords 17 | */ 18 | public abstract class LanguageCFamily 19 | { 20 | public final static char EOF = '\uFFFF'; 21 | public final static char NULL_CHAR = '\u0000'; 22 | public final static char NEWLINE = '\n'; 23 | public final static char BACKSPACE = '\b'; 24 | public final static char TAB = '\t'; 25 | 26 | protected HashMap _keywords; 27 | protected HashMap _operators; 28 | 29 | private final static char[] basic_c_operators = { 30 | '(', ')', '{', '}', '.', ',', ';', '=', '+', '-', 31 | '/', '*', '&', '!', '|', ':', '[', ']', '<', '>', 32 | '?', '~', '%', '^' 33 | }; 34 | 35 | { replaceOperators(basic_c_operators); } 36 | 37 | 38 | protected void registerKeywords(String[] keywords) 39 | { 40 | _keywords = new HashMap(keywords.length); 41 | for (int i = 0; i < keywords.length; ++i) 42 | { 43 | _keywords.put(keywords[i], Lexer.KEYWORD); 44 | } 45 | } 46 | 47 | protected void replaceOperators(char[] operators) 48 | { 49 | _operators = new HashMap<>(operators.length); 50 | for (int i = 0; i < operators.length; ++i) 51 | { 52 | _operators.put(operators[i], Lexer.OPERATOR); 53 | } 54 | } 55 | 56 | public final boolean isOperator(char c) 57 | { 58 | return _operators.containsKey(c); 59 | } 60 | 61 | public final boolean isKeyword(String s) 62 | { 63 | return _keywords.containsKey(s); 64 | } 65 | 66 | public boolean isWhitespace(char c) 67 | { 68 | return (c == ' ' || c == '\n' || c == '\t' || 69 | c == '\r' || c == '\f' || c == EOF); 70 | } 71 | 72 | public boolean isSentenceTerminator(char c) 73 | { 74 | return (c == '.'); 75 | } 76 | 77 | public boolean isEscapeChar(char c) 78 | { 79 | return (c == '\\'); 80 | } 81 | 82 | /** 83 | * Derived classes that do not do represent C-like programming languages 84 | * should return false; otherwise return true 85 | */ 86 | public boolean isProgLang() 87 | { 88 | return true; 89 | } 90 | 91 | /** 92 | * Whether the word after c is a token 93 | */ 94 | public boolean isWordStart(char c) 95 | { 96 | return false; 97 | } 98 | 99 | /** 100 | * Whether cSc is a token, where S is a sequence of characters that are on the same line 101 | */ 102 | public boolean isDelimiterA(char c) 103 | { 104 | return (c == '"'); 105 | } 106 | 107 | /** 108 | * Same concept as isDelimiterA(char), but Language and its subclasses can 109 | * specify a second type of symbol to use here 110 | */ 111 | public boolean isDelimiterB(char c) 112 | { 113 | return (c == '\''); 114 | } 115 | 116 | /** 117 | * Whether cL is a token, where L is a sequence of characters until the end of the line 118 | */ 119 | public boolean isLineAStart(char c) 120 | { 121 | return (c == '#'); 122 | } 123 | 124 | /** 125 | * Same concept as isLineAStart(char), but Language and its subclasses can 126 | * specify a second type of symbol to use here 127 | */ 128 | public boolean isLineBStart(char c) 129 | { 130 | return false; 131 | } 132 | 133 | /** 134 | * Whether c0c1L is a token, where L is a sequence of characters until the end of the line 135 | */ 136 | public boolean isLineStart(char c0, char c1) 137 | { 138 | return (c0 == '/' && c1 == '/'); 139 | } 140 | 141 | /** 142 | * Whether c0c1 signifies the start of a multi-line token 143 | */ 144 | public boolean isMultilineStartDelimiter(char c0, char c1) 145 | { 146 | return (c0 == '/' && c1 == '*'); 147 | } 148 | 149 | /** 150 | * Whether c0c1 signifies the end of a multi-line token 151 | */ 152 | public boolean isMultilineEndDelimiter(char c0, char c1) 153 | { 154 | return (c0 == '*' && c1 == '/'); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/LanguageCpp.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 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.mrikso.codeeditor.lang; 10 | 11 | public class LanguageCpp extends Language{ 12 | private static Language _theOne = null; 13 | 14 | private final static String[] keywords = { 15 | "bool", "char", "double", "float", "int", "long", "short", "void", "wchar_t", 16 | "auto", "const", "extern", "mutable", "register", "static", "volatile", 17 | "signed", "unsigned", "true", "false", 18 | "new", "delete", "sizeof", "typedef", "typeid", "typename", 19 | "const_cast", "dynamic_cast", "reinterpret_cast", "static_cast", 20 | "class", "enum", "explicit", "operator", "struct", "template", "union", "virtual", 21 | "private", "protected", "public", "friend", "this", 22 | "break", "case", "catch", "continue", "default", "do", "else", "for", 23 | "goto", "if", "return", "switch", "throw", "try", "while", 24 | "export", "namespace", "using", "asm", "inline","restrict" 25 | }; 26 | private final static String[] cFunctions={ 27 | "abort()","abs(int num):int","acos(double arg):double","asctime(const struct tm *ptr):char *","asin(double arg):double","assert(int exp)","atan(double arg):double","atan2(double y,double x):double","atexit(void (*func)(void)):int","atof(const char *str):double","atoi(const char *str):int","atol(const char *str):long" 28 | ,"bsearch(const void *key,const void *buf,size_t num,size_t size,int (*compare)(const void *,const void *))","calloc(size_t num, size_t size)","ceil(double num):double","clearerr(FILE *stream)","clock():clock_t","cos(double arg):double","cosh(double arg):double","ctime(const time_t *time):char *","difftime(time_t time2,time_t time1):double","div(int numerator,int denominator):div_t" 29 | ,"exit(int exit_code)","exp(double arg):double","fabs(double arg):double","fclose(FILE *stream):int","feof(FILE *stream):int","ferror(FILE *stream):int","fflush(FILE *stream):int","fgetc(FILE *stream):int","fgetpos(FILE *stream,fpos_t *position):int","fgets(char *str, int num,FILE *stream):char *","floor(double arg):double" 30 | ,"fmod(double x,double y):double","fopen(const char *fname, const char *mode):FILE *","fprintf(FILE *stream,const char *format,...):int","fputc(int ch,FILE *stream):int","fputs(const char *str,FILE *stream):int","fread(void *buffer, size_t size, size_t num, FILE *stream):int","free(void *ptr)","freopen(const char *fname,const char *mode,FILE *stream):FILE *","frexp(double num,int *exp):double","fscanf(FILE *stream,const char *format,...):double","fseek(FILE *stream,long offset,int origin):int","fsetpos(FILE *stream,const fpos_t *position):int","ftell(FILE *stream):long","fwrite(const void *buffer,size_t size,size_t count,FILE *stream):int" 31 | ,"getc(FILE *stream):int","getchar():int","getenv(const char *name):char *","gets(char *str):char *","gmtime(const time_t *time):struct tm *","isalnum(int ch):int","isalpha(int ch):int","iscntrl(int ch):int","isdigit(int ch):int","isgraph(int ch):int","islower(int ch):int","isprint(int ch):int","ispunct(int ch):int","isspace(int ch):int","isupper(int ch):int","isxdigit(int ch):int","labs(long num):long","ldexp(double num,int exp):double","ldiv(long numerator,long denominator):ldiv_t","localtime(const time_t *time):struct tm *","log(double num):double","log10(double num):double","longjmp(jmp_buf envbuf,int status)" 32 | ,"malloc(size_t size):void *","memchr(const void *buffer,int ch,size_t count):void *","memcmp(const void *buffer1,const void *buffer2,size_t count):int","memcpy(void *to,const void *from,size_t count):void *","memmove(void *to,const void *from,size_t count):void *","memset(void *buffer,int ch,size_t count):void *","mktime(struct tm *time):time_t","modf(double num,double *i):double","perror(const char *str)","pow(double base,double exp):double","printf(const char *format,...):int" 33 | ,"putc(int ch,FILE *stream):int","putchar(int ch):int","puts(char *str):int","qsort(void *buf,size_t num,size_t size,int (*compare)(const void *,const void *))","raise(int signal):int","rand():int","realloc(void *ptr,size_t size):void *","remove(const char *fname):int","rename(const char *oldfname,const char *newfname):int","rewind(FILE *stream)" 34 | ,"scanf(const char *format,...):int","setbuf(FILE *stream,char *buffer)","setjmp(jmp_buf envbuf):int","setvbuf(FILE *stream,char *buffer,int mode,size_t size):int","signal(int signal,void (*func)(int))","sin(double arg):double","sinh(double arg):double","sprintf(char *buffer,const char *format,...):int","sqrt(double num):double","srand(unsigned seed)","sscanf(const char *buffer,const char *format,...):int","strcat(char *str1,const char *str2):char *","strchr(const char *str, int ch):char *","strcmp(const char *str1,const char *str2):int","strcoll(const char *str1,const char *str2):int","strcpy(char *to,const char *from):char *","strcspn(const char *str1,const char *str2):size_t","strerror(int num):char *","strftime(char *str,size_t maxsize,const char *fmt,struct tm *time):size_t","strlen(char *str):size_t","strncat(char *str1,const char *str2,size_t count):char *","strncmp(const char *str1,const char *str2,size_t count):int","strncpy(char *to,const char *from,size_t count):char *","strpbrk(const char *str1,const char *str2):char *","strrchr(const char *str,int ch):char *","strspn(const char *str1, const char *str2):size_t","strstr(const char *str1, const char *str2):char *","strtod(const char *start,char **end):double","strtok(char *str1,const char *str2):char *","strtol(const char *start,char **end,int base):long","strtoul(const char *start,char **end,int base):unsigned long","strxfrm(char *str1,const char *str2,size_t num):size_t","system(const char *command):int" 35 | ,"tan(double arg):double","tanh(double arg):double","time(time_t *time):time_t","tmpfile():FILE *","tmpnam(char *name):char *","tolower(int ch):int","toupper(int ch):int","ungetc(int ch, FILE *stream):int","va_arg(va_list arg_ptr,type):type","vprintf(char *format,va_list arg_ptr):int","vfprintf(FILE *stream,const char *format,va_list arg_ptr):int","vsprintf(char *buffer,char *format,va_list arg_ptr):int" 36 | 37 | }; 38 | 39 | private final static String[] preDefineField={ 40 | "__LINE__","__FILE__","__DATE__","__TIME__","__cplusplus","__STDC__","__func__","__VA_ARGS__","__attribute__" 41 | }; 42 | private final static String[] cppNamespace ={ 43 | "std" 44 | }; 45 | 46 | private final static String[] cppClasses={ 47 | //io 48 | "fstream","ifstream","ofstream","cout","cin","cerr","endl" 49 | //模板库 50 | ,"bitset","string","list","deque","map","multimap","multiset","set","priority_queue","queue","stack","vector" 51 | //c++11 模板库 52 | ,"array","forward_list","unordered_map","unordered_set" 53 | }; 54 | 55 | private final static String[] cppFunctions={ 56 | "any()","append()","assign()","at()","back()","bad()","begin()","c_str()","capacity()","clear()","compare()","copy()","count()","data()","empty()","end()","eof()","equal_range()","erase()","fail()","fill()","find()","find_first_not_of()","find_first_of()","find_last_not_of()","find_last_of()","flags()","flip()","flush()","front()","fstream" 57 | ,"gcount()","get()","get_allocator()","getline()","good()","ignore()","insert()","iterator()","key_comp()","length()","lower_bound()","max_size()","merge()","none()","open()","peek()","pop()","pop_back()","pop_front()","precision()","push()","push_back()","push_front()","put()","putback" 58 | ,"rbegin()","rdstate()","read()","remove()","remove_if()","rend()","replace()","reserve()","reset()","resize()","reverse()","rfind()","seekg()","seekp()","set()","setf()","size()","sort()","splice()","substr()","swap()","sync_with_stdio()","tellg()","tellp()","test()","to_string()","to_ulong()","top" 59 | ,"unique()","unsetf()","upper_bound()","value_comp()","width()","write()" 60 | }; 61 | 62 | private final static String[] extraWord={ 63 | "define","include","ifdef","endif","ifndef","error","elif","line","pragma","undef","main" 64 | }; 65 | private final static char[] BASIC_C_OPERATORS = { 66 | '(', ')', '{', '}', '.', ',', ';', '=', '+', '-', 67 | '/', '*', '&', '!', '|', ':', '[', ']', '<', '>', 68 | '?', '~', '%', '^' 69 | }; 70 | public static Language getInstance(){ 71 | if(_theOne == null){ 72 | _theOne = new LanguageCpp(); 73 | } 74 | return _theOne; 75 | } 76 | public void addNames(String[] names) { 77 | String[] old=this.getNames(); 78 | String[] news=new String[old.length + names.length]; 79 | System.arraycopy(old, 0, news, 0, old.length); 80 | System.arraycopy(names, 0, news, old.length, names.length); 81 | this.setNames(news); 82 | 83 | } 84 | private LanguageCpp(){ 85 | setOperators(BASIC_C_OPERATORS); 86 | setKeywords(keywords); 87 | setNames(cFunctions);//先setName才能addName 88 | addNames(preDefineField); 89 | addNames(cppNamespace); 90 | addNames(cppClasses); 91 | addNames(cppFunctions); 92 | addNames(extraWord); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/LanguageCsharp.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 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.mrikso.codeeditor.lang; 10 | 11 | /** 12 | * Singleton class containing the symbols and operators of the C# language 13 | */ 14 | public class LanguageCsharp extends LanguageCFamily { 15 | private static LanguageCFamily _theOne = null; 16 | 17 | private final static String[] keywords = { 18 | "abstract", "as", "base", "bool", "break", "byte", "case", "catch", 19 | "char", "checked", "class", "const", "continue", "decimal", "default", 20 | "delegate", "do", "double", "else", "enum", "event", "explicit", 21 | "extern", "false", "finally", "fixed", "float", "for", "foreach", 22 | "goto", "if", "implicit", "in", "int", "interface", "internal", "is", 23 | "lock", "long", "namespace", "new", "null", "object", "operator", 24 | "out", "override", "params", "private", "protected", "public", 25 | "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", 26 | "stackalloc", "static", "string", "struct", "switch", "this", "throw", 27 | "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", 28 | "ushort", "using", "virtual", "void", "volatile", "while", 29 | "dynamic", "get", "set", "add", "remove", "global", "value", "var", 30 | "yield", "alias", "partial", 31 | "from", "where", "join", "on", "equals", "into", "let", "orderby", 32 | "ascending", "descending", "select", "group", "by" 33 | }; 34 | 35 | public static LanguageCFamily getCharacterEncodings(){ 36 | if(_theOne == null){ 37 | _theOne = new LanguageCsharp(); 38 | } 39 | return _theOne; 40 | } 41 | 42 | private LanguageCsharp(){ 43 | super.registerKeywords(keywords); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/LanguageJava.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 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.mrikso.codeeditor.lang; 10 | 11 | /** 12 | * Singleton class containing the symbols and operators of the Java language 13 | */ 14 | public class LanguageJava extends Language{ 15 | private static Language _theOne = null; 16 | 17 | private final static String[] keywords = { 18 | "void", "boolean", "byte", "char", "short", "int", "long", "float", "double", "strictfp", 19 | "import", "package", "new", "class", "interface", "extends", "implements", "enum", 20 | "public", "private", "protected", "static", "abstract", "final", "native", "volatile", 21 | "assert", "try", "throw", "throws", "catch", "finally", "instanceof", "super", "this", 22 | "if", "else", "for", "do", "while", "switch", "case", "default", 23 | "continue", "break", "return", "synchronized", "transient", 24 | "true", "false", "null", "import", "package" 25 | }; 26 | 27 | 28 | public static Language getInstance(){ 29 | if(_theOne == null){ 30 | _theOne = new LanguageJava(); 31 | } 32 | return _theOne; 33 | } 34 | 35 | private LanguageJava(){ 36 | setKeywords(keywords); 37 | } 38 | 39 | /** 40 | * Java has no preprocessors. Override base class implementation 41 | */ 42 | public boolean isLineAStart(char c){ 43 | return false; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/LanguageJavascript.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 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.mrikso.codeeditor.lang; 10 | 11 | /** 12 | * Singleton class containing the symbols and operators of the Javascript language 13 | */ 14 | public class LanguageJavascript extends LanguageCFamily { 15 | private static LanguageCFamily _theOne = null; 16 | 17 | private final static String[] keywords = { 18 | "abstract", "boolean", "break", "byte", "case", "catch", "char", 19 | "class", "const", "continue", "debugger", "default", "delete", "do", 20 | "double", "else", "enum", "export", "extends", "false", "final", 21 | "finally", "float", "for", "function", "goto", "if", "implements", 22 | "import", "in", "instanceof", "int", "interface", "long", "native", 23 | "new", "null", "package", "private", "protected", "public", "return", 24 | "short", "static", "super", "switch", "synchronized", "this", "throw", 25 | "throws", "transient", "true", "try", "typeof", "var", "void", 26 | "volatile", "while", "with" 27 | }; 28 | 29 | public static LanguageCFamily getCharacterEncodings(){ 30 | if(_theOne == null){ 31 | _theOne = new LanguageJavascript(); 32 | } 33 | return _theOne; 34 | } 35 | 36 | private LanguageJavascript(){ 37 | super.registerKeywords(keywords); 38 | } 39 | 40 | public boolean isLineAStart(char c){ 41 | return false; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/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.mrikso.codeeditor.lang; 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 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/LanguageNonProg.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.mrikso.codeeditor.lang; 10 | 11 | /** 12 | * Singleton class that represents a non-programming language without keywords, 13 | * operators etc. 14 | */ 15 | public class LanguageNonProg extends Language{ 16 | private static Language _theOne = null; 17 | 18 | private final static String[] keywords = {}; 19 | 20 | private final static char[] operators = {}; 21 | 22 | 23 | public static Language getInstance(){ 24 | if(_theOne == null){ 25 | _theOne = new LanguageNonProg(); 26 | } 27 | return _theOne; 28 | } 29 | 30 | private LanguageNonProg(){ 31 | super.setKeywords(keywords); 32 | super.setOperators(operators); 33 | } 34 | 35 | @Override 36 | public boolean isProgLang(){ 37 | return false; 38 | } 39 | 40 | @Override 41 | public boolean isEscapeChar(char c){ 42 | return false; 43 | } 44 | 45 | @Override 46 | public boolean isDelimiterA(char c){ 47 | return false; 48 | } 49 | 50 | @Override 51 | public boolean isDelimiterB(char c){ 52 | return false; 53 | } 54 | 55 | @Override 56 | public boolean isLineAStart(char c){ 57 | return false; 58 | } 59 | 60 | @Override 61 | public boolean isLineStart(char c0, char c1){ 62 | return false; 63 | } 64 | 65 | @Override 66 | public boolean isMultilineStartDelimiter(char c0, char c1){ 67 | return false; 68 | } 69 | 70 | @Override 71 | public boolean isMultilineEndDelimiter(char c0, char c1){ 72 | return false; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/LanguageObjectiveC.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 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.mrikso.codeeditor.lang; 10 | 11 | /** 12 | * Singleton class containing the symbols and operators of the Objective-C language 13 | */ 14 | public class LanguageObjectiveC extends LanguageCFamily{ 15 | private static LanguageCFamily _theOne = null; 16 | 17 | private final static String[] keywords = { 18 | "char", "double", "float", "int", "long", "short", "void", 19 | "auto", "const", "extern", "register", "static", "volatile", 20 | "signed", "unsigned", "sizeof", "typedef", 21 | "enum", "struct", "union", 22 | "break", "case", "continue", "default", "do", "else", "for", 23 | "goto", "if", "return", "switch", "while", 24 | "@class", "@implementation", "@interface", "@protocol", "@property", 25 | "@private", "@protected", "@public", "@optional", "@required", 26 | "@defs", "@dynamic", "@encode", "@synchronized", "@selector", "@synthesize", 27 | "@try", "@catch", "@throw", "@finally", "@end", 28 | "id", "self", "super", "nil", "Nil", "NULL", "SEL", "BOOL", "YES", "NO", 29 | "in", "out", "inout", "bycopy", "byref", "oneway", 30 | "getter", "setter", "readwrite", "readonly", "assign", "retain", "copy", "nonatomic" 31 | }; 32 | 33 | public static LanguageCFamily getCharacterEncodings(){ 34 | if(_theOne == null){ 35 | _theOne = new LanguageObjectiveC(); 36 | } 37 | return _theOne; 38 | } 39 | 40 | private LanguageObjectiveC(){ 41 | super.registerKeywords(keywords); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/LanguagePHP.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 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.mrikso.codeeditor.lang; 10 | 11 | /** 12 | * Singleton class containing the symbols and operators of the PHP language 13 | */ 14 | public class LanguagePHP extends LanguageCFamily { 15 | private static LanguageCFamily _theOne = null; 16 | 17 | private final static String[] keywords = { 18 | "abstract", "and", "array", "as", "break", "case", "catch", "class", 19 | "clone", "const", "continue", "declare", "default", "do", "else", 20 | "elseif", "enddeclare", "endfor", "endforeach", "endif", "endswitch", 21 | "endwhile", "extends", "final", "for", "foreach", "function", "global", 22 | "goto", "if", "implements", "interface", "instanceof", "namespace", 23 | "new", "or", "private", "protected", "public", "static", "switch", 24 | "throw", "try", "use", "var", "while", "xor", 25 | "die", "echo", "empty", "exit", "eval", "include", "include_once", 26 | "isset", "list", "require", "require_once", "return", "print", "unset", 27 | "self", "static", "parent", "true", "TRUE", "false", "FALSE", "null", "NULL" 28 | }; 29 | 30 | private final static char[] operators = { 31 | '(', ')', '{', '}', '.', ',', ';', '=', '+', '-', 32 | '/', '*', '&', '!', '|', ':', '[', ']', '<', '>', 33 | '?', '~', '%', '^', '`', '@' 34 | }; 35 | 36 | 37 | public static LanguageCFamily getCharacterEncodings(){ 38 | if(_theOne == null){ 39 | _theOne = new LanguagePHP(); 40 | } 41 | return _theOne; 42 | } 43 | 44 | private LanguagePHP(){ 45 | super.registerKeywords(keywords); 46 | super.replaceOperators(operators); 47 | } 48 | 49 | @Override 50 | public boolean isLineAStart(char c){ 51 | return false; 52 | } 53 | 54 | @Override 55 | public boolean isWordStart(char c){ 56 | return (c == '$'); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/LanguagePython.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 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.mrikso.codeeditor.lang; 10 | 11 | /** 12 | * Singleton class containing the symbols and operators of the Python language 13 | */ 14 | public class LanguagePython extends LanguageCFamily{ 15 | private static LanguageCFamily _theOne = null; 16 | 17 | private final static String[] keywords = { 18 | "and", "assert", "break", "class", "continue", "def", "del", 19 | "elif", "else", "except", "exec", "finally", "for", "from", 20 | "global", "if", "import", "in", "is", "lambda", "not", "or", 21 | "pass", "print", "raise", "return", "try", "while", "with", 22 | "yield", "True", "False", "None" 23 | }; 24 | 25 | private final static char[] operators = { 26 | '(', ')', '{', '}', '.', ',', ';', '=', '+', '-', 27 | '/', '*', '&', '!', '|', ':', '[', ']', '<', '>', 28 | '~', '%', '^' 29 | }; // no ternary operator ? : 30 | 31 | 32 | @Override 33 | public boolean isWordStart(char c){ 34 | return (c == '@'); 35 | } 36 | 37 | @Override 38 | public boolean isLineAStart(char c){ 39 | return false; 40 | } 41 | 42 | @Override 43 | public boolean isLineBStart(char c){ 44 | return (c == '#'); 45 | } 46 | 47 | @Override 48 | public boolean isLineStart(char c0, char c1){ 49 | return false; 50 | } 51 | 52 | @Override 53 | public boolean isMultilineStartDelimiter(char c0, char c1){ 54 | return false; 55 | } 56 | 57 | public static LanguageCFamily getCharacterEncodings(){ 58 | if(_theOne == null){ 59 | _theOne = new LanguagePython(); 60 | } 61 | return _theOne; 62 | } 63 | 64 | private LanguagePython(){ 65 | super.registerKeywords(keywords); 66 | super.replaceOperators(operators); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/LanguageRuby.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 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.mrikso.codeeditor.lang; 10 | 11 | /** 12 | * Singleton class containing the symbols and operators of the Ruby language 13 | */ 14 | public class LanguageRuby extends LanguageCFamily { 15 | private static LanguageCFamily _theOne = null; 16 | 17 | private final static String[] keywords = { 18 | "alias", "and", "BEGIN", "begin", "break", "case", "catch", "class", "def", 19 | "defined?", "do", "else", "elsif", "END", "end", "ensure", "false", 20 | "for", "if", "in", "module", "next", "nil", "not", "or", "public", 21 | "private", "protected", "raise", "redo", "rescue", "retry", "return", "self", 22 | "super", "then", "throw", "true", "undef", "unless", "until", "when", "while", 23 | "yield", "self", "nil", "true", "false", "TRUE", "FALSE", "NIL" 24 | }; 25 | 26 | 27 | @Override 28 | public boolean isWordStart(char c){ 29 | return (c == '$'); 30 | } 31 | 32 | @Override 33 | public boolean isLineAStart(char c){ 34 | return false; 35 | } 36 | 37 | @Override 38 | public boolean isLineBStart(char c){ 39 | return (c == '#'); 40 | } 41 | 42 | @Override 43 | public boolean isLineStart(char c0, char c1){ 44 | return false; 45 | } 46 | 47 | @Override 48 | public boolean isMultilineStartDelimiter(char c0, char c1){ 49 | return false; 50 | } 51 | 52 | public static LanguageCFamily getCharacterEncodings(){ 53 | if(_theOne == null){ 54 | _theOne = new LanguageRuby(); 55 | } 56 | return _theOne; 57 | } 58 | 59 | private LanguageRuby(){ 60 | super.registerKeywords(keywords); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/LanguageSmali.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 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.mrikso.codeeditor.lang; 10 | 11 | /** 12 | * Singleton class containing the symbols and operators of the Smali language 13 | */ 14 | public class LanguageSmali extends Language { 15 | private final static String[] keywords = { 16 | "goto", 17 | "return-void", 18 | "nop", 19 | "const/4", 20 | "move-result", 21 | "move-result-wide", 22 | "move-result-object", 23 | "move-exception", 24 | "return", 25 | "return-wide", 26 | "return-object", 27 | "monitor-enter", 28 | "monitor-exit", 29 | "throw", 30 | "move", 31 | "move-wide", 32 | "move-object", 33 | "array-length", 34 | "neg-int", 35 | "not-int", 36 | "neg-long", 37 | "not-long", 38 | "neg-float", 39 | "neg-double", 40 | "int-to-long", 41 | "int-to-float", 42 | "int-to-double", 43 | "long-to-int", 44 | "long-to-float", 45 | "long-to-double", 46 | "float-to-int", 47 | "float-to-long", 48 | "float-to-double", 49 | "double-to-int", 50 | "double-to-long", 51 | "double-to-float", 52 | "int-to-byte", 53 | "int-to-char", 54 | "int-to-short", 55 | "add-int/2addr", 56 | "sub-int/2addr", 57 | "mul-int/2addr", 58 | "div-int/2addr", 59 | "rem-int/2addr", 60 | "and-int/2addr", 61 | "or-int/2addr", 62 | "xor-int/2addr", 63 | "shl-int/2addr", 64 | "shr-int/2addr", 65 | "ushr-int/2addr", 66 | "add-long/2addr", 67 | "sub-long/2addr", 68 | "mul-long/2addr", 69 | "div-long/2addr", 70 | "rem-long/2addr", 71 | "and-long/2addr", 72 | "or-long/2addr", 73 | "xor-long/2addr", 74 | "shl-long/2addr", 75 | "shr-long/2addr", 76 | "ushr-long/2addr", 77 | "add-float/2addr", 78 | "sub-float/2addr", 79 | "mul-float/2addr", 80 | "div-float/2addr", 81 | "rem-float/2addr", 82 | "add-double/2addr", 83 | "sub-double/2addr", 84 | "mul-double/2addr", 85 | "div-double/2addr", 86 | "rem-double/2addr", 87 | "goto/16", 88 | "sget", 89 | "sget-wide", 90 | "sget-object", 91 | "sget-boolean", 92 | "sget-byte", 93 | "sget-char", 94 | "sget-short", 95 | "sput", 96 | "sput-wide", 97 | "sput-object", 98 | "sput-boolean", 99 | "sput-byte", 100 | "sput-char", 101 | "sput-short", 102 | "const-string", 103 | "check-cast", 104 | "new-instance", 105 | "const-class", 106 | "const/high16", 107 | "const-wide/high16", 108 | "const/16", 109 | "const-wide/16", 110 | "if-eqz", 111 | "if-nez", 112 | "if-ltz", 113 | "if-gez", 114 | "if-gtz", 115 | "if-lez", 116 | "add-int/lit8", 117 | "rsub-int/lit8", 118 | "mul-int/lit8", 119 | "div-int/lit8", 120 | "rem-int/lit8", 121 | "and-int/lit8", 122 | "or-int/lit8", 123 | "xor-int/lit8", 124 | "shl-int/lit8", 125 | "shr-int/lit8", 126 | "ushr-int/lit8", 127 | "iget", 128 | "iget-wide", 129 | "iget-object", 130 | "iget-boolean", 131 | "iget-byte", 132 | "iget-char", 133 | "iget-short", 134 | "iput", 135 | "iput-wide", 136 | "iput-object", 137 | "iput-boolean", 138 | "iput-byte", 139 | "iput-char", 140 | "iput-short", 141 | "instance-of", 142 | "new-array", 143 | "iget-quick", 144 | "iget-wide-quick", 145 | "iget-object-quick", 146 | "iput-quick", 147 | "iput-wide-quick", 148 | "iput-object-quick", 149 | "rsub-int", 150 | "add-int/lit16", 151 | "mul-int/lit16", 152 | "div-int/lit16", 153 | "rem-int/lit16", 154 | "and-int/lit16", 155 | "or-int/lit16", 156 | "xor-int/lit16", 157 | "if-eq", 158 | "if-ne", 159 | "if-lt", 160 | "if-ge", 161 | "if-gt", 162 | "if-le", 163 | "move/from16", 164 | "move-wide/from16", 165 | "move-object/from16", 166 | "cmpl-float", 167 | "cmpg-float", 168 | "cmpl-double", 169 | "cmpg-double", 170 | "cmp-long", 171 | "aget", 172 | "aget-wide", 173 | "aget-object", 174 | "aget-boolean", 175 | "aget-byte", 176 | "aget-char", 177 | "aget-short", 178 | "aput", 179 | "aput-wide", 180 | "aput-object", 181 | "aput-boolean", 182 | "aput-byte", 183 | "aput-char", 184 | "aput-short", 185 | "add-int", 186 | "sub-int", 187 | "mul-int", 188 | "div-int", 189 | "rem-int", 190 | "and-int", 191 | "or-int", 192 | "xor-int", 193 | "shl-int", 194 | "shr-int", 195 | "ushr-int", 196 | "add-long", 197 | "sub-long", 198 | "mul-long", 199 | "div-long", 200 | "rem-long", 201 | "and-long", 202 | "or-long", 203 | "xor-long", 204 | "shl-long", 205 | "shr-long", 206 | "ushr-long", 207 | "add-float", 208 | "sub-float", 209 | "mul-float", 210 | "div-float", 211 | "rem-float", 212 | "add-double", 213 | "sub-double", 214 | "mul-double", 215 | "div-double", 216 | "rem-double", 217 | "goto/32", 218 | "const-string/jumbo", 219 | "const", 220 | "const-wide/32", 221 | "fill-array-data", 222 | "packed-switch", 223 | "sparse-switch", 224 | "move/16", 225 | "move-wide/16", 226 | "move-object/16", 227 | "invoke-virtual", 228 | "invoke-super", 229 | "invoke-direct", 230 | "invoke-static", 231 | "invoke-interface", 232 | "filled-new-array", 233 | "invoke-direct-empty", 234 | "execute-inline", 235 | "invoke-virtual-quick", 236 | "invoke-super-quick", 237 | "invoke-virtual/range", 238 | "invoke-super/range", 239 | "invoke-direct/range", 240 | "invoke-static/range", 241 | "invoke-interface/range", 242 | "filled-new-array/range", 243 | "invoke-virtual-quick/range", 244 | "invoke-super-quick/range", 245 | "const-wide" 246 | }; 247 | private final static String[] clalifcators = { 248 | "public", "static", "private", "final", "return", "void" 249 | }; // 250 | 251 | private final static char[] operators = { 252 | '(', ')', '{', '}', '[', ']', '<', '>' 253 | }; // 254 | private final static String[] mProKeyWord = { 255 | ".class", 256 | ".super", 257 | ".implements", 258 | ".field", 259 | ".subannotation", 260 | ".annotation", 261 | ".enum", 262 | ".method", 263 | ".registers", 264 | ".locals", 265 | ".array-data", 266 | ".packed-switch", 267 | ".sparse-switch", 268 | ".catch", 269 | ".catchall", 270 | ".parameter", 271 | ".prologue", 272 | ".epilogue", 273 | ".source", 274 | ".end", 275 | ".restart" 276 | }; 277 | private static Language _theOne = null; 278 | 279 | public void addNames(String[] names) { 280 | String[] old=getNames(); 281 | //if(old.length != 0) { 282 | String[] news = new String[old.length + names.length]; 283 | System.arraycopy(old, 0, news, 0, old.length); 284 | System.arraycopy(names, 0, news, old.length, names.length); 285 | setNames(news); 286 | //} 287 | //else { 288 | //setNames(names); 289 | //} 290 | } 291 | 292 | private LanguageSmali() { 293 | setOperators(operators); 294 | setKeywords(keywords); 295 | addNames(mProKeyWord); 296 | addNames(clalifcators); 297 | 298 | } 299 | 300 | public static Language getInstance() { 301 | if (_theOne == null) { 302 | _theOne = new LanguageSmali(); 303 | } 304 | return _theOne; 305 | } 306 | 307 | @Override 308 | public boolean isLineAStart(char c) { 309 | return false; 310 | } 311 | 312 | @Override 313 | public boolean isLineBStart(char c) { 314 | return (c == '#'); 315 | } 316 | 317 | @Override 318 | public boolean isWordStart(char c){ 319 | return (c == '.'); 320 | } 321 | 322 | @Override 323 | public boolean isLineStart(char c0, char c1) { 324 | return false; 325 | } 326 | 327 | @Override 328 | public boolean isMultilineStartDelimiter(char c0, char c1) { 329 | return false; 330 | } 331 | 332 | } 333 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/lang/xml/XMLLanguage.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor.lang.xml; 2 | 3 | import com.mrikso.codeeditor.lang.Language; 4 | 5 | 6 | /** 7 | * Singleton class containing the symbols and operators of the Javascript language 8 | */ 9 | public class XMLLanguage extends Language { 10 | 11 | private static XMLLanguage _theOne; 12 | 13 | public static Language getInstance() { 14 | if (_theOne == null) { 15 | _theOne = new XMLLanguage(); 16 | } 17 | return _theOne; 18 | } 19 | 20 | private XMLLanguage() { 21 | } 22 | 23 | /** 24 | * Whether the word after c is a token 25 | */ 26 | 27 | public boolean isWordStart2(char c) { 28 | return (c == '.'); 29 | } 30 | 31 | public boolean isLineAStart(char c) { 32 | return false; 33 | } 34 | 35 | /** 36 | * Whether c0c1 signifies the start of a multi-line token 37 | */ 38 | public boolean isMultilineStartDelimiter(char c0, char c1, char c2, char c3) { 39 | return (c0 == '<' && c1 == '!' && c2 == '-' && c3 == '-'); 40 | } 41 | 42 | /** 43 | * Whether c0c1 signifies the end of a multi-line token 44 | */ 45 | public boolean isMultilineEndDelimiter(char c0, char c1, char c2) { 46 | return (c0 == '-' && c1 == '-' && c2 == '>'); 47 | } 48 | public boolean isMultilineStartDelimiter(char c0, char c1) { 49 | return (c0 == '<' && c1 == '!'); 50 | } 51 | 52 | /** 53 | * Whether c0c1 signifies the end of a multi-line token 54 | */ 55 | public boolean isMultilineEndDelimiter(char c0, char c1) { 56 | return (c0 == '-' && c1 == '>'); 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/util/DLog.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor.util; 2 | 3 | import java.util.Arrays; 4 | 5 | 6 | public class DLog { 7 | private static final String TAG = "CodeEditor"; 8 | public static boolean DEBUG = true; 9 | 10 | public static int v(String tag, String msg) { 11 | if (!DEBUG) 12 | return 0; 13 | return android.util.Log.v(tag, msg); 14 | } 15 | 16 | public static int v(String tag, String msg, Throwable tr) { 17 | if (!DEBUG) 18 | return 0; 19 | return android.util.Log.v(tag, msg, tr); 20 | } 21 | 22 | public static int d(String msg) { 23 | return d(TAG, msg); 24 | } 25 | 26 | public static int d(String tag, String msg) { 27 | if (!DEBUG) 28 | return 0; 29 | return android.util.Log.d(tag, msg); 30 | } 31 | 32 | public static int d(String tag, String msg, Throwable tr) { 33 | if (!DEBUG) 34 | return 0; 35 | return android.util.Log.d(tag, msg, tr); 36 | } 37 | 38 | public static int d(String format, Object... args) { 39 | return d(TAG, String.format(format, args)); 40 | } 41 | 42 | public static int d(Throwable t) { 43 | return d(TAG, t.getMessage(), t); 44 | } 45 | 46 | public static int i(String tag, String msg) { 47 | if (!DEBUG) 48 | return 0; 49 | return android.util.Log.i(tag, msg); 50 | } 51 | 52 | public static int i(String tag, String msg, Throwable tr) { 53 | if (!DEBUG) 54 | return 0; 55 | return android.util.Log.i(tag, msg, tr); 56 | } 57 | 58 | public static int w(String msg) { 59 | return android.util.Log.w(TAG, msg); 60 | } 61 | 62 | public static int w(String tag, String msg) { 63 | return android.util.Log.w(tag, msg); 64 | } 65 | 66 | public static int w(String tag, String msg, Throwable tr) { 67 | return android.util.Log.w(tag, msg, tr); 68 | } 69 | 70 | public static int w(String tag, Throwable tr) { 71 | return android.util.Log.w(tag, tr); 72 | } 73 | 74 | public static int e(String tag, String msg) { 75 | return logError(tag, msg, null); 76 | } 77 | 78 | public static int e(String tag, String msg, Throwable tr) { 79 | return logError(tag, msg, tr); 80 | } 81 | 82 | public static int e(String msg) { 83 | return e(TAG, msg); 84 | } 85 | 86 | public static int e(String msg, Throwable t) { 87 | return e(TAG, msg, t); 88 | } 89 | 90 | public static int e(String format, Object... args) { 91 | return e(TAG, String.format(format, args)); 92 | } 93 | 94 | public static int e(Throwable t) { 95 | if (t == null) 96 | return 0; 97 | return logError(TAG, t.getMessage(), t); 98 | } 99 | 100 | private static int logError(String tag, String msg, Throwable t) { 101 | return android.util.Log.e(tag, msg, t); 102 | } 103 | 104 | public static void log(Object... params) { 105 | if (DLog.DEBUG) DLog.d(TAG, "log: " + Arrays.toString(params)); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/util/Document.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.mrikso.codeeditor.util; 10 | 11 | import com.mrikso.codeeditor.lang.Language; 12 | 13 | import java.util.ArrayList; 14 | 15 | /** 16 | * A decorator of TextBuffer that adds word-wrap capabilities. 17 | * 18 | * Positions for word wrap row breaks are stored here. 19 | * Word-wrap is enabled by default. 20 | */ 21 | public class Document extends TextBuffer 22 | { 23 | 24 | private boolean _isWordWrap = false; 25 | 26 | /** Contains info related to printing of characters, display size and so on */ 27 | private TextFieldMetrics _metrics; 28 | 29 | /** A table containing the character offset of every row in the document. 30 | * Values are valid only in word-wrap mode */ 31 | private ArrayList _rowTable; 32 | 33 | public Document(TextFieldMetrics metrics) 34 | { 35 | super(); 36 | _metrics = metrics; 37 | resetRowTable(); 38 | } 39 | 40 | public void setText(CharSequence text) 41 | { 42 | int lineCount=1; 43 | int len=text.length(); 44 | char[] ca=new char[TextBuffer.memoryNeeded(len)]; 45 | for (int i=0;i < len;i++) 46 | { 47 | ca[i] = text.charAt(i); 48 | if (text.charAt(i) == '\n') 49 | lineCount++; 50 | } 51 | setBuffer(ca, len, lineCount); 52 | } 53 | 54 | private void resetRowTable() 55 | { 56 | ArrayList rowTable = new ArrayList(); 57 | rowTable.add(0); //every document contains at least 1 row 58 | _rowTable = rowTable; 59 | } 60 | 61 | public void setMetrics(TextFieldMetrics metrics) 62 | { 63 | _metrics = metrics; 64 | } 65 | 66 | /** 67 | * Enable/disable word wrap. If enabled, the document is immediately 68 | * analyzed for word wrap breakpoints, which might take an arbitrarily long time. 69 | */ 70 | public void setWordWrap(boolean enable) 71 | { 72 | if (enable && !_isWordWrap) 73 | { 74 | _isWordWrap = true; 75 | analyzeWordWrap(); 76 | } 77 | else if (!enable && _isWordWrap) 78 | { 79 | _isWordWrap = false; 80 | analyzeWordWrap(); 81 | } 82 | } 83 | 84 | public boolean isWordWrap() 85 | { 86 | return _isWordWrap; 87 | } 88 | 89 | 90 | @Override 91 | public synchronized void delete(int charOffset, int totalChars, long timestamp, boolean undoable) 92 | { 93 | super.delete(charOffset, totalChars, timestamp, undoable); 94 | 95 | int startRow = findRowNumber(charOffset); 96 | int analyzeEnd = findNextLineFrom(charOffset); 97 | updateWordWrapAfterEdit(startRow, analyzeEnd, -totalChars); 98 | } 99 | 100 | @Override 101 | public synchronized void insert(char[] c, int charOffset, long timestamp, boolean undoable) 102 | { 103 | super.insert(c, charOffset, timestamp, undoable); 104 | 105 | int startRow = findRowNumber(charOffset); 106 | int analyzeEnd = findNextLineFrom(charOffset + c.length); 107 | updateWordWrapAfterEdit(startRow, analyzeEnd, c.length); 108 | } 109 | 110 | @Override 111 | /** 112 | * Moves _gapStartIndex by displacement units. Note that displacement can be 113 | * negative and will move _gapStartIndex to the left. 114 | * 115 | * Only UndoStack should use this method to carry out a simple undo/redo 116 | * of insertions/deletions. No error checking is done. 117 | */ 118 | synchronized void shiftGapStart(int displacement) 119 | { 120 | super.shiftGapStart(displacement); 121 | 122 | if (displacement != 0) 123 | { 124 | int startOffset = (displacement > 0) 125 | ? _gapStartIndex - displacement 126 | : _gapStartIndex; 127 | int startRow = findRowNumber(startOffset); 128 | int analyzeEnd = findNextLineFrom(_gapStartIndex); 129 | updateWordWrapAfterEdit(startRow, analyzeEnd, displacement); 130 | } 131 | } 132 | 133 | //No error checking is done on parameters. 134 | private int findNextLineFrom(int charOffset) 135 | { 136 | int lineEnd = logicalToRealIndex(charOffset); 137 | 138 | while (lineEnd < _contents.length) 139 | { 140 | // skip the gap 141 | if (lineEnd == _gapStartIndex) 142 | { 143 | lineEnd = _gapEndIndex; 144 | } 145 | 146 | if (_contents[lineEnd] == Language.NEWLINE || 147 | _contents[lineEnd] == Language.EOF) 148 | { 149 | break; 150 | } 151 | 152 | ++lineEnd; 153 | } 154 | 155 | return realToLogicalIndex(lineEnd) + 1; 156 | } 157 | 158 | private void updateWordWrapAfterEdit(int startRow, int analyzeEnd, int delta) 159 | { 160 | if (startRow > 0) 161 | { 162 | // if the first word becomes shorter or an inserted space breaks it 163 | // up, it may fit the previous line, so analyse that line too 164 | --startRow; 165 | } 166 | int analyzeStart = _rowTable.get(startRow); 167 | 168 | //changes only affect the rows after startRow 169 | removeRowMetadata(startRow + 1, analyzeEnd - delta); 170 | adjustOffsetOfRowsFrom(startRow + 1, delta); 171 | analyzeWordWrap(startRow + 1, analyzeStart, analyzeEnd); 172 | } 173 | 174 | /** 175 | * Removes row offset info from fromRow to the row that endOffset is on, 176 | * inclusive. 177 | * 178 | * No error checking is done on parameters. 179 | */ 180 | private void removeRowMetadata(int fromRow, int endOffset) 181 | { 182 | while (fromRow < _rowTable.size() && 183 | _rowTable.get(fromRow) <= endOffset) 184 | { 185 | _rowTable.remove(fromRow); 186 | } 187 | } 188 | 189 | private void adjustOffsetOfRowsFrom(int fromRow, int offset) 190 | { 191 | for (int i = fromRow; i < _rowTable.size(); ++i) 192 | { 193 | _rowTable.set(i, _rowTable.get(i) + offset); 194 | } 195 | } 196 | 197 | public void analyzeWordWrap() 198 | { 199 | 200 | resetRowTable(); 201 | 202 | if (_isWordWrap&&!hasMinimumWidthForWordWrap()) 203 | { 204 | if (_metrics.getRowWidth() > 0) 205 | { 206 | TextWarriorException.fail("Text field has non-zero width but still too small for word wrap"); 207 | } 208 | // _metrics.getRowWidth() might legitmately be zero when the text field has not been layout yet 209 | return; 210 | } 211 | 212 | analyzeWordWrap(1, 0, getTextLength()); 213 | } 214 | 215 | private boolean hasMinimumWidthForWordWrap() 216 | { 217 | final int maxWidth = _metrics.getRowWidth(); 218 | //assume the widest char is 2ems wide 219 | return (maxWidth >= 2 * _metrics.getAdvance('M')); 220 | } 221 | 222 | //No error checking is done on parameters. 223 | //A word consists of a sequence of 0 or more non-whitespace characters followed by 224 | //exactly one whitespace character. Note that EOF is considered whitespace. 225 | private void analyzeWordWrap(int rowIndex, int startOffset, int endOffset) 226 | { 227 | if (!_isWordWrap) 228 | { 229 | int offset = logicalToRealIndex(startOffset); 230 | int end = logicalToRealIndex(endOffset); 231 | ArrayList rowTable = new ArrayList(); 232 | 233 | while (offset < end) 234 | { 235 | // skip the gap 236 | if (offset == _gapStartIndex) 237 | { 238 | offset = _gapEndIndex; 239 | } 240 | char c = _contents[offset]; 241 | if (c == Language.NEWLINE) 242 | { 243 | //start a new row 244 | rowTable.add(realToLogicalIndex(offset) + 1); 245 | } 246 | ++offset; 247 | 248 | } 249 | _rowTable.addAll(rowIndex, rowTable); 250 | return; 251 | } 252 | if (!hasMinimumWidthForWordWrap()) 253 | { 254 | TextWarriorException.fail("Not enough space to do word wrap"); 255 | return; 256 | } 257 | 258 | ArrayList rowTable = new ArrayList(); 259 | int offset = logicalToRealIndex(startOffset); 260 | int end = logicalToRealIndex(endOffset); 261 | int potentialBreakPoint = startOffset; 262 | int wordExtent = 0; 263 | final int maxWidth = _metrics.getRowWidth(); 264 | int remainingWidth = maxWidth; 265 | 266 | while (offset < end) 267 | { 268 | // skip the gap 269 | if (offset == _gapStartIndex) 270 | { 271 | offset = _gapEndIndex; 272 | } 273 | 274 | char c = _contents[offset]; 275 | wordExtent += _metrics.getAdvance(c); 276 | 277 | boolean isWhitespace = (c == ' ' || c == Language.TAB 278 | || c == Language.NEWLINE || c == Language.EOF); 279 | 280 | if (isWhitespace) 281 | { 282 | //full word obtained 283 | if (wordExtent <= remainingWidth) 284 | { 285 | remainingWidth -= wordExtent; 286 | } 287 | else if (wordExtent > maxWidth) 288 | { 289 | //handle a word too long to fit on one row 290 | int current = logicalToRealIndex(potentialBreakPoint); 291 | remainingWidth = maxWidth; 292 | 293 | //start the word on a new row, if it isn't already 294 | if (potentialBreakPoint != startOffset && (rowTable.isEmpty() || 295 | potentialBreakPoint != rowTable.get(rowTable.size() - 1))) 296 | { 297 | rowTable.add(potentialBreakPoint); 298 | } 299 | 300 | while (current <= offset) 301 | { 302 | // skip the gap 303 | if (current == _gapStartIndex) 304 | { 305 | current = _gapEndIndex; 306 | } 307 | 308 | int advance = _metrics.getAdvance(_contents[current]); 309 | if (advance > remainingWidth) 310 | { 311 | rowTable.add(realToLogicalIndex(current)); 312 | remainingWidth = maxWidth - advance; 313 | } 314 | else 315 | { 316 | remainingWidth -= advance; 317 | } 318 | 319 | ++current; 320 | } 321 | } 322 | else 323 | { 324 | //invariant: potentialBreakPoint != startOffset 325 | //put the word on a new row 326 | rowTable.add(potentialBreakPoint); 327 | remainingWidth = maxWidth - wordExtent; 328 | } 329 | 330 | wordExtent = 0; 331 | potentialBreakPoint = realToLogicalIndex(offset) + 1; 332 | } 333 | 334 | if (c == Language.NEWLINE) 335 | { 336 | //start a new row 337 | rowTable.add(potentialBreakPoint); 338 | remainingWidth = maxWidth; 339 | } 340 | 341 | ++offset; 342 | } 343 | 344 | //merge with existing row table 345 | _rowTable.addAll(rowIndex, rowTable); 346 | } 347 | 348 | public String getRow(int rowNumber) 349 | { 350 | 351 | int rowSize = getRowSize(rowNumber); 352 | if (rowSize == 0) 353 | { 354 | return new String(); 355 | } 356 | 357 | int startIndex = _rowTable.get(rowNumber); 358 | return subSequence(startIndex, rowSize).toString(); 359 | } 360 | 361 | public int getRowSize(int rowNumber) 362 | { 363 | 364 | if (isInvalidRow(rowNumber)) 365 | { 366 | return 0; 367 | } 368 | 369 | if (rowNumber != (_rowTable.size() - 1)) 370 | { 371 | return _rowTable.get(rowNumber + 1) - _rowTable.get(rowNumber); 372 | } 373 | else 374 | { 375 | //last row 376 | return getTextLength() - _rowTable.get(rowNumber); 377 | } 378 | } 379 | 380 | public int getRowCount() 381 | { 382 | 383 | return _rowTable.size(); 384 | } 385 | 386 | public int getRowOffset(int rowNumber) 387 | { 388 | 389 | 390 | if (isInvalidRow(rowNumber)) 391 | { 392 | return -1; 393 | } 394 | 395 | return _rowTable.get(rowNumber); 396 | } 397 | 398 | /** 399 | * Get the row number that charOffset is on 400 | * 401 | * @return The row number that charOffset is on, or -1 if charOffset is invalid 402 | */ 403 | public int findRowNumber(int charOffset) 404 | { 405 | 406 | if (!isValid(charOffset)) 407 | { 408 | return -1; 409 | } 410 | 411 | //binary search of _rowTable 412 | int right = _rowTable.size() - 1; 413 | int left = 0; 414 | while (right >= left) 415 | { 416 | int mid = (left + right) / 2; 417 | int nextLineOffset = ((mid + 1) < _rowTable.size()) ? _rowTable.get(mid + 1) : getTextLength(); 418 | if (charOffset >= _rowTable.get(mid) && charOffset < nextLineOffset) 419 | { 420 | return mid; 421 | } 422 | 423 | if (charOffset >= nextLineOffset) 424 | { 425 | left = mid + 1; 426 | } 427 | else 428 | { 429 | right = mid - 1; 430 | } 431 | } 432 | 433 | //should not be here 434 | return -1; 435 | } 436 | 437 | 438 | protected boolean isInvalidRow(int rowNumber) 439 | { 440 | return rowNumber < 0 || rowNumber >= _rowTable.size(); 441 | } 442 | 443 | 444 | 445 | public static interface TextFieldMetrics 446 | { 447 | /** 448 | * Returns printed width of c. 449 | * 450 | * @param c Character to measure 451 | * @return Advance of character, in pixels 452 | */ 453 | public int getAdvance(char c); 454 | 455 | /** 456 | * Returns the maximum width available for a row of text to be layout. This 457 | * should not be larger than the width of the text field. 458 | * 459 | * @return Maximum width of a row, in pixels 460 | */ 461 | public int getRowWidth(); 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/util/DocumentProvider.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.mrikso.codeeditor.util; 10 | 11 | import com.mrikso.codeeditor.lang.Language; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * Iterator class to access characters of the underlying text buffer. 17 | * 18 | * The usage procedure is as follows: 19 | * 1. Call seekChar(offset) to mark the position to start iterating 20 | * 2. Call hasNext() to see if there are any more char 21 | * 3. Call next() to get the next char 22 | * 23 | * If there is more than 1 DocumentProvider pointing to the same Document, 24 | * changes made by one DocumentProvider will not cause other DocumentProviders 25 | * to be notified. Implement a publish/subscribe interface if required. 26 | */ 27 | public class DocumentProvider implements CharSequence 28 | { 29 | 30 | /** Current position in the text. Range [ 0, _theText.getTextLength() ) */ 31 | private int _currIndex; 32 | private final Document _theText; 33 | 34 | public DocumentProvider(Document.TextFieldMetrics metrics){ 35 | _currIndex = 0; 36 | _theText = new Document(metrics); 37 | } 38 | 39 | public DocumentProvider(Document doc){ 40 | _currIndex = 0; 41 | _theText = doc; 42 | } 43 | 44 | public DocumentProvider(DocumentProvider rhs){ 45 | _currIndex = 0; 46 | _theText = rhs._theText; 47 | } 48 | 49 | @Override 50 | public int length() 51 | { 52 | // TODO: Implement this method 53 | return _theText.length(); 54 | } 55 | /** 56 | * Get a substring of up to maxChars length, starting from charOffset 57 | */ 58 | public CharSequence subSequence(int charOffset, int maxChars){ 59 | return _theText.subSequence(charOffset, maxChars); 60 | } 61 | 62 | public char charAt(int charOffset){ 63 | if(_theText.isValid(charOffset)){ 64 | return _theText.charAt(charOffset); 65 | } 66 | else{ 67 | return Language.NULL_CHAR; 68 | } 69 | } 70 | 71 | public String getRow(int rowNumber){ 72 | return _theText.getRow(rowNumber); 73 | } 74 | 75 | /** 76 | * Get the row number that charOffset is on 77 | */ 78 | public int findRowNumber(int charOffset){ 79 | return _theText.findRowNumber(charOffset); 80 | } 81 | 82 | /** 83 | * Get the line number that charOffset is on. The difference between a line 84 | * and a row is that a line can be word-wrapped into many rows. 85 | */ 86 | public int findLineNumber(int charOffset){ 87 | return _theText.findLineNumber(charOffset); 88 | } 89 | 90 | /** 91 | * Get the offset of the first character on rowNumber 92 | */ 93 | public int getRowOffset(int rowNumber){ 94 | return _theText.getRowOffset(rowNumber); 95 | } 96 | 97 | 98 | /** 99 | * Get the offset of the first character on lineNumber. The difference 100 | * between a line and a row is that a line can be word-wrapped into many rows. 101 | */ 102 | public int getLineOffset(int lineNumber){ 103 | return _theText.getLineOffset(lineNumber); 104 | } 105 | 106 | /** 107 | * Sets the iterator to point at startingChar. 108 | * 109 | * If startingChar is invalid, hasNext() will return false, and _currIndex 110 | * will be set to -1. 111 | * 112 | * @return startingChar, or -1 if startingChar does not exist 113 | */ 114 | public int seekChar(int startingChar){ 115 | if(_theText.isValid(startingChar)){ 116 | _currIndex = startingChar; 117 | } 118 | else{ 119 | _currIndex = -1; 120 | } 121 | return _currIndex; 122 | } 123 | 124 | public boolean hasNext(){ 125 | return (_currIndex >= 0 && 126 | _currIndex < _theText.getTextLength()); 127 | } 128 | 129 | /** 130 | * Returns the next character and moves the iterator forward. 131 | * 132 | * Does not do bounds-checking. It is the responsibility of the caller 133 | * to check hasNext() first. 134 | * 135 | * @return Next character 136 | */ 137 | public char next(){ 138 | char nextChar = _theText.charAt(_currIndex); 139 | ++_currIndex; 140 | return nextChar; 141 | } 142 | 143 | /** 144 | * Inserts c into the document, shifting existing characters from 145 | * insertionPoint (inclusive) to the right 146 | * 147 | * If insertionPoint is invalid, nothing happens. 148 | */ 149 | public void insertBefore(char c, int insertionPoint, long timestamp){ 150 | if(!_theText.isValid(insertionPoint)){ 151 | return; 152 | } 153 | 154 | char[] a = new char[1]; 155 | a[0] = c; 156 | _theText.insert(a, insertionPoint, timestamp, true); 157 | } 158 | 159 | /** 160 | * Inserts characters of cArray into the document, shifting existing 161 | * characters from insertionPoint (inclusive) to the right 162 | * 163 | * If insertionPoint is invalid, nothing happens. 164 | */ 165 | public void insertBefore(char[] cArray, int insertionPoint, long timestamp){ 166 | if(!_theText.isValid(insertionPoint) || cArray.length == 0){ 167 | return; 168 | } 169 | 170 | _theText.insert(cArray, insertionPoint, timestamp, true); 171 | } 172 | 173 | public void insert(int i, CharSequence s) 174 | { 175 | _theText.insert(new char[]{s.charAt(0)},i,System.nanoTime(),true); 176 | } 177 | /** 178 | * Deletes the character at deletionPoint index. 179 | * If deletionPoint is invalid, nothing happens. 180 | */ 181 | public void deleteAt(int deletionPoint, long timestamp){ 182 | if(!_theText.isValid(deletionPoint)){ 183 | return; 184 | } 185 | _theText.delete(deletionPoint, 1, timestamp, true); 186 | } 187 | 188 | 189 | /** 190 | * Deletes up to maxChars number of characters starting from deletionPoint 191 | * If deletionPoint is invalid, or maxChars is not positive, nothing happens. 192 | */ 193 | public void deleteAt(int deletionPoint, int maxChars, long time){ 194 | if(!_theText.isValid(deletionPoint) || maxChars <= 0){ 195 | return; 196 | } 197 | int totalChars = Math.min(maxChars, _theText.getTextLength() - deletionPoint); 198 | _theText.delete(deletionPoint, totalChars, time, true); 199 | } 200 | 201 | /** 202 | * Returns true if the underlying text buffer is in batch edit mode 203 | */ 204 | public boolean isBatchEdit(){ 205 | return _theText.isBatchEdit(); 206 | } 207 | 208 | /** 209 | * Signals the beginning of a series of insert/delete operations that can be 210 | * undone/redone as a single unit 211 | */ 212 | public void beginBatchEdit(){ 213 | _theText.beginBatchEdit(); 214 | } 215 | 216 | /** 217 | * Signals the end of a series of insert/delete operations that can be 218 | * undone/redone as a single unit 219 | */ 220 | public void endBatchEdit(){ 221 | _theText.endBatchEdit(); 222 | } 223 | 224 | /** 225 | * Returns the number of rows in the document 226 | */ 227 | public int getRowCount(){ 228 | return _theText.getRowCount(); 229 | } 230 | 231 | /** 232 | * Returns the number of characters in the row specified by rowNumber 233 | */ 234 | public int getRowSize(int rowNumber){ 235 | return _theText.getRowSize(rowNumber); 236 | } 237 | 238 | /** 239 | * Returns the number of characters in the document, including the terminal 240 | * End-Of-File character 241 | */ 242 | public int docLength(){ 243 | return _theText.getTextLength(); 244 | } 245 | 246 | //TODO make thread-safe 247 | /** 248 | * Removes spans from the document. 249 | * Beware: Not thread-safe! Another thread may be modifying the same spans 250 | * returned from getSpans() 251 | */ 252 | public void clearSpans(){ 253 | _theText.clearSpans(); 254 | } 255 | 256 | /** 257 | * Beware: Not thread-safe! 258 | */ 259 | public List getSpans(){ 260 | return _theText.getSpans(); 261 | } 262 | 263 | /** 264 | * Sets the spans to use in the document. 265 | * Spans are continuous sequences of characters that have the same format 266 | * like color, font, etc. 267 | * 268 | * @param spans A collection of Pairs, where Pair.first is the start 269 | * position of the token, and Pair.second is the type of the token. 270 | */ 271 | public void setSpans(List spans){ 272 | _theText.setSpans(spans); 273 | } 274 | 275 | public void setMetrics(Document.TextFieldMetrics metrics){ 276 | _theText.setMetrics(metrics); 277 | } 278 | 279 | /** 280 | * Enable/disable word wrap for the document. If enabled, the document is 281 | * immediately analyzed for word wrap breakpoints, which might take an 282 | * arbitrarily long time. 283 | */ 284 | public void setWordWrap(boolean enable){ 285 | _theText.setWordWrap(enable); 286 | } 287 | 288 | public boolean isWordWrap(){ 289 | return _theText.isWordWrap(); 290 | } 291 | 292 | /** 293 | * Analyze the document for word wrap break points. Does nothing if word 294 | * wrap is disabled for the document. 295 | */ 296 | public void analyzeWordWrap(){ 297 | _theText.analyzeWordWrap(); 298 | } 299 | 300 | public boolean canUndo() { 301 | return _theText.canUndo(); 302 | } 303 | 304 | public boolean canRedo() { 305 | return _theText.canRedo(); 306 | } 307 | 308 | public int undo() { 309 | return _theText.undo(); 310 | } 311 | 312 | public int redo() { 313 | return _theText.redo(); 314 | } 315 | 316 | @Override 317 | public String toString() 318 | { 319 | // TODO: Implement this method 320 | return _theText.toString(); 321 | } 322 | 323 | 324 | } 325 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/util/FindThread.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 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.mrikso.codeeditor.util; 10 | 11 | import java.util.Vector; 12 | 13 | 14 | /** 15 | * Worker thread to carry our find and replaceAll operations. 16 | * The find thread should not be reused after it has completed. Create a new one 17 | * for another operation. 18 | */ 19 | public class FindThread extends Thread implements ProgressSource { 20 | /** 21 | * Reported progress will be scaled from 0 to MAX_PROGRESS 22 | */ 23 | private final static int MAX_PROGRESS = 100; 24 | final private SearchStrategy FINDER = new LinearSearchStrategy(); 25 | protected int _requestCode; 26 | protected DocumentProvider _src; 27 | protected Vector _progressObservers = new Vector(); 28 | protected String _searchText; 29 | protected String _replacementText; 30 | protected int _start; 31 | protected boolean _isCaseSensitive; 32 | protected boolean _isWholeWord; 33 | protected boolean _isDone = false; 34 | protected FindResults _results; 35 | private int _docSize = 0; // size, in chars, of the document to search 36 | 37 | private FindThread(int requestCode, DocumentProvider src, String searchText, int start, 38 | boolean isCaseSensitive, boolean isWholeWord) { 39 | _requestCode = requestCode; 40 | _src = src; 41 | _start = start; 42 | _searchText = searchText; 43 | _isCaseSensitive = isCaseSensitive; 44 | _isWholeWord = isWholeWord; 45 | _docSize = src.docLength(); 46 | } 47 | 48 | private FindThread(int requestCode, DocumentProvider src, String searchText, String replacementText, int start, 49 | boolean isCaseSensitive, boolean isWholeWord) { 50 | _requestCode = requestCode; 51 | _src = src; 52 | _start = start; 53 | _searchText = searchText; 54 | _replacementText = replacementText; 55 | _isCaseSensitive = isCaseSensitive; 56 | _isWholeWord = isWholeWord; 57 | _docSize = src.docLength(); 58 | } 59 | 60 | static public FindThread createFindThread(DocumentProvider src, String searchText, int start, boolean isForwardSearch, 61 | boolean isCaseSensitive, boolean isWholeWord) { 62 | 63 | int requestCode = (isForwardSearch) ? ProgressSource.FIND : ProgressSource.FIND_BACKWARDS; 64 | 65 | return new FindThread(requestCode, src, searchText, start, isCaseSensitive, isWholeWord); 66 | } 67 | 68 | static public FindThread createReplaceAllThread(DocumentProvider src, String searchText, String replacementText, int start, 69 | boolean isCaseSensitive, boolean isWholeWord) { 70 | return new FindThread(ProgressSource.REPLACE_ALL, src, searchText, replacementText, start, isCaseSensitive, isWholeWord); 71 | } 72 | 73 | public void run() { 74 | _isDone = false; 75 | _results = new FindResults(_searchText.length()); 76 | 77 | switch (_requestCode) { 78 | case ProgressSource.FIND: 79 | _results.foundOffset = FINDER.wrappedFind(_src, _searchText, _start, _isCaseSensitive, _isWholeWord); 80 | notifyComplete(_results); 81 | break; 82 | case ProgressSource.FIND_BACKWARDS: 83 | _results.foundOffset = FINDER.wrappedFindBackwards(_src, _searchText, _start, 84 | _isCaseSensitive, _isWholeWord); 85 | notifyComplete(_results); 86 | break; 87 | case ProgressSource.REPLACE_ALL: 88 | Pair replaceResult = FINDER.replaceAll(_src, _searchText, _replacementText, _start, 89 | _isCaseSensitive, _isWholeWord); 90 | _results.replacementCount = replaceResult.getFirst(); 91 | _results.newStartPosition = replaceResult.getSecond(); 92 | notifyComplete(_results); 93 | break; 94 | default: 95 | TextWarriorException.assertVerbose(false, "Invalid request code for FindThread"); 96 | break; 97 | } 98 | } 99 | 100 | @Override 101 | public final int getMin() { 102 | return 0; 103 | } 104 | 105 | @Override 106 | public final int getMax() { 107 | return MAX_PROGRESS; 108 | } 109 | 110 | @Override 111 | public final int getCurrent() { 112 | double progressProportion = (_docSize == 0) ? 0 : (double) FINDER.getProgress() / (double) _docSize; 113 | return (int) (progressProportion * MAX_PROGRESS); 114 | } 115 | 116 | @Override 117 | public final void forceStop() { 118 | //TODO implement 119 | } 120 | 121 | @Override 122 | public final boolean isDone() { 123 | return _isDone; 124 | } 125 | 126 | @Override 127 | synchronized public final void registerObserver(ProgressObserver po) { 128 | _progressObservers.addElement(po); 129 | } 130 | 131 | @Override 132 | synchronized public final void removeObservers() { 133 | _progressObservers.clear(); 134 | } 135 | 136 | synchronized protected void notifyComplete(Object result) { 137 | _isDone = true; 138 | for (ProgressObserver po : _progressObservers) { 139 | po.onComplete(_requestCode, result); 140 | } 141 | } 142 | 143 | public final int getRequestCode() { 144 | return _requestCode; 145 | } 146 | 147 | public final FindResults getResults() { 148 | return _results; 149 | } 150 | 151 | public static class FindResults { 152 | public int foundOffset = -1; 153 | public int replacementCount = 0; 154 | public int newStartPosition = 0; 155 | public int searchTextLength = 0; //for convenience 156 | 157 | public FindResults(int searchLength) { 158 | searchTextLength = searchLength; 159 | } 160 | } 161 | 162 | } -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/util/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.mrikso.codeeditor.util; 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 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/util/HelperUtils.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor.util; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Canvas; 7 | import android.graphics.drawable.Drawable; 8 | import android.util.TypedValue; 9 | 10 | import androidx.core.content.ContextCompat; 11 | 12 | import com.mrikso.codeeditor.R; 13 | 14 | public class HelperUtils { 15 | 16 | //get AccentColor 17 | public static int fetchAccentColor(Context context) { 18 | TypedArray typedArray = context.obtainStyledAttributes(new TypedValue().data, new int[]{R.attr.colorAccent}); 19 | int color = typedArray.getColor(0, 0); 20 | typedArray.recycle(); 21 | return color; 22 | } 23 | 24 | public static float getDpi(Context context) { 25 | return context.getResources().getDisplayMetrics().density; 26 | } 27 | 28 | // create bitmap from vector drawable 29 | public static Bitmap getBitmap(Context context, int res) { 30 | Bitmap bitmap = null; 31 | Drawable vectorDrawable = ContextCompat.getDrawable(context, res); 32 | if (vectorDrawable != null) { 33 | vectorDrawable.setAlpha(210); 34 | vectorDrawable.setTint(fetchAccentColor(context)); 35 | bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 36 | Canvas canvas = new Canvas(bitmap); 37 | vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 38 | vectorDrawable.draw(canvas); 39 | return bitmap; 40 | } 41 | return bitmap; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/util/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.mrikso.codeeditor.util; 10 | 11 | 12 | import com.mrikso.codeeditor.lang.Language; 13 | 14 | public class LinearSearchStrategy implements SearchStrategy { 15 | private int _unitsDone = 0; 16 | 17 | @Override 18 | // only applicable to replaceAll operation 19 | public int getProgress() { 20 | return _unitsDone; 21 | } 22 | 23 | @Override 24 | public int wrappedFind(DocumentProvider src, String target, int start, 25 | boolean isCaseSensitive, boolean isWholeWord) { 26 | 27 | // search towards end of doc first... 28 | int foundOffset = find(src, target, start, src.docLength(), 29 | isCaseSensitive, isWholeWord); 30 | // ...then from beginning of doc 31 | if (foundOffset < 0) { 32 | foundOffset = find(src, target, 0, start, 33 | isCaseSensitive, isWholeWord); 34 | } 35 | 36 | return foundOffset; 37 | } 38 | 39 | @Override 40 | public int find(DocumentProvider src, String target, int start, int end, 41 | boolean isCaseSensitive, boolean isWholeWord) { 42 | if (target.length() == 0) { 43 | return -1; 44 | } 45 | if (start < 0) { 46 | TextWarriorException.fail("TextBuffer.find: Invalid start position"); 47 | start = 0; 48 | } 49 | if (end > src.docLength()) { 50 | TextWarriorException.fail("TextBuffer.find: Invalid end position"); 51 | end = src.docLength(); 52 | } 53 | 54 | end = Math.min(end, src.docLength() - target.length() + 1); 55 | int offset = start; 56 | while (offset < end) { 57 | if (equals(src, target, offset, isCaseSensitive) && 58 | (!isWholeWord || isSandwichedByWhitespace(src, offset, target.length()))) { 59 | break; 60 | } 61 | 62 | ++offset; 63 | ++_unitsDone; 64 | } 65 | 66 | if (offset < end) { 67 | return offset; 68 | } else { 69 | return -1; 70 | } 71 | } 72 | 73 | @Override 74 | public int wrappedFindBackwards(DocumentProvider src, String target, int start, 75 | boolean isCaseSensitive, boolean isWholeWord) { 76 | 77 | // search towards beginning of doc first... 78 | int foundOffset = findBackwards(src, target, start, -1, 79 | isCaseSensitive, isWholeWord); 80 | // ...then from end of doc 81 | if (foundOffset < 0) { 82 | foundOffset = findBackwards(src, target, src.docLength() - 1, start, 83 | isCaseSensitive, isWholeWord); 84 | } 85 | 86 | return foundOffset; 87 | } 88 | 89 | 90 | @Override 91 | public int findBackwards(DocumentProvider src, String target, int start, int end, 92 | boolean isCaseSensitive, boolean isWholeWord) { 93 | if (target.length() == 0) { 94 | return -1; 95 | } 96 | if (start >= src.docLength()) { 97 | TextWarriorException.fail("Invalid start position given to TextBuffer.find"); 98 | start = src.docLength() - 1; 99 | } 100 | if (end < -1) { 101 | TextWarriorException.fail("Invalid end position given to TextBuffer.find"); 102 | end = -1; 103 | } 104 | int offset = Math.min(start, src.docLength() - target.length()); 105 | while (offset > end) { 106 | if (equals(src, target, offset, isCaseSensitive) && 107 | (!isWholeWord || isSandwichedByWhitespace(src, offset, target.length()))) { 108 | break; 109 | } 110 | 111 | --offset; 112 | } 113 | 114 | if (offset > end) { 115 | return offset; 116 | } else { 117 | return -1; 118 | } 119 | } 120 | 121 | @Override 122 | public Pair replaceAll(DocumentProvider src, String searchText, 123 | String replacementText, int mark, 124 | boolean isCaseSensitive, boolean isWholeWord) { 125 | int replacementCount = 0; 126 | int anchor = mark; 127 | _unitsDone = 0; 128 | 129 | final char[] replacement = replacementText.toCharArray(); 130 | int foundIndex = find(src, searchText, 0, src.docLength(), 131 | isCaseSensitive, isWholeWord); 132 | long timestamp = System.nanoTime(); 133 | 134 | src.beginBatchEdit(); 135 | while (foundIndex != -1) { 136 | src.deleteAt(foundIndex, searchText.length(), timestamp); 137 | src.insertBefore(replacement, foundIndex, timestamp); 138 | if (foundIndex < anchor) { 139 | // adjust anchor because of differences in doc length 140 | // after word replacement 141 | anchor += replacementText.length() - searchText.length(); 142 | } 143 | ++replacementCount; 144 | _unitsDone += searchText.length(); //skip replaced chars 145 | foundIndex = find( 146 | src, 147 | searchText, 148 | foundIndex + replacementText.length(), 149 | src.docLength(), 150 | isCaseSensitive, 151 | isWholeWord); 152 | } 153 | src.endBatchEdit(); 154 | 155 | return new Pair(replacementCount, Math.max(anchor, 0)); 156 | } 157 | 158 | 159 | protected boolean equals(DocumentProvider src, String target, 160 | int srcOffset, boolean isCaseSensitive) { 161 | if ((src.docLength() - srcOffset) < target.length()) { 162 | //compared range in src must at least be as long as target 163 | return false; 164 | } 165 | 166 | int i; 167 | for (i = 0; i < target.length(); ++i) { 168 | if (isCaseSensitive && 169 | target.charAt(i) != src.charAt(i + srcOffset)) { 170 | return false; 171 | } 172 | // for case-insensitive search, compare both strings in lower case 173 | if (!isCaseSensitive && 174 | Character.toLowerCase(target.charAt(i)) != 175 | Character.toLowerCase(src.charAt(i + srcOffset))) { 176 | return false; 177 | } 178 | 179 | } 180 | 181 | return true; 182 | } 183 | 184 | /** 185 | * Checks if a word starting at startPosition with size length is bounded 186 | * by whitespace. 187 | */ 188 | protected boolean isSandwichedByWhitespace(DocumentProvider src, 189 | int start, int length) { 190 | Language charSet = Lexer.getLanguage(); 191 | boolean startWithWhitespace = (start == 0) || charSet.isWhitespace(src.charAt(start - 1)); 192 | 193 | int end = start + length; 194 | boolean endWithWhitespace = (end == src.docLength()) || charSet.isWhitespace(src.charAt(end)); 195 | 196 | return (startWithWhitespace && endWithWhitespace); 197 | } 198 | 199 | } 200 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/util/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.mrikso.codeeditor.util; 10 | 11 | public final class Pair { 12 | public int first; 13 | public int second; 14 | 15 | public Pair(int x, int y){ 16 | first = x; 17 | second = y; 18 | } 19 | 20 | public final void setFirst(int value){ 21 | first = value; 22 | } 23 | 24 | public final void setSecond(int value){ 25 | second = value; 26 | } 27 | 28 | public final int getFirst() { 29 | return first; 30 | } 31 | 32 | public final int getSecond() { 33 | return second; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "("+first+","+second+")"; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/util/ProgressObserver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 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.mrikso.codeeditor.util; 10 | 11 | /** 12 | * Can be registered with a {@link ProgressSource} of interest, which will cause 13 | * progress updates to be sent to the ProgressObserver. 14 | */ 15 | public interface ProgressObserver { 16 | public void onComplete(int requestCode, Object result); 17 | public void onError(int requestCode, int errorCode, String message); 18 | public void onCancel(int requestCode); 19 | } 20 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/util/ProgressSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 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.mrikso.codeeditor.util; 10 | 11 | /** 12 | * Represents tasks that carry out long computations 13 | */ 14 | public interface ProgressSource { 15 | /** Minimum progress value */ 16 | public abstract int getMin(); 17 | /** Maximum progress value */ 18 | public abstract int getMax(); 19 | /** Current progress value */ 20 | public abstract int getCurrent(); 21 | 22 | /** Whether computation is done */ 23 | public abstract boolean isDone(); 24 | /** Aborts computation */ 25 | public abstract void forceStop(); 26 | /** Registers observers that will be informed of changes to the progress state */ 27 | public abstract void registerObserver(ProgressObserver obsv); 28 | /** Removes all attached observers */ 29 | public abstract void removeObservers(); 30 | 31 | /* Nature of computation tasks */ 32 | static final public int NONE = 0; 33 | static final public int READ = 1; 34 | static final public int WRITE = 2; 35 | static final public int FIND = 4; 36 | static final public int FIND_BACKWARDS = 8; 37 | static final public int REPLACE_ALL = 16; 38 | static final public int ANALYZE_TEXT = 32; 39 | 40 | /* Error codes */ 41 | static final public int ERROR_UNKNOWN = 0; 42 | static final public int ERROR_OUT_OF_MEMORY = 1; 43 | static final public int ERROR_INDEX_OUT_OF_RANGE = 2; 44 | } 45 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/util/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.mrikso.codeeditor.util; 10 | 11 | public interface SearchStrategy { 12 | /** 13 | * Searches for target, starting from start (inclusive), 14 | * and stopping at end (exclusive). 15 | * 16 | * @return charOffset of found string; -1 if not found 17 | */ 18 | public int find(DocumentProvider src, String target, int start, int end, 19 | boolean isCaseSensitive, boolean isWholeWord); 20 | 21 | /** 22 | * Searches for target, starting from start (inclusive), 23 | * wrapping around to the beginning of document and 24 | * stopping at start (exclusive). 25 | * 26 | * @return charOffset of found string; -1 if not found 27 | */ 28 | public int wrappedFind(DocumentProvider src, String target, int start, 29 | boolean isCaseSensitive, boolean isWholeWord); 30 | 31 | /** 32 | * Searches backwards from startCharOffset (inclusive), 33 | * and stopping at end (exclusive). 34 | * 35 | * @return charOffset of found string; -1 if not found 36 | */ 37 | public int findBackwards(DocumentProvider src, String target, int start, int end, 38 | boolean isCaseSensitive, boolean isWholeWord); 39 | 40 | /** 41 | * Searches backwards from start (inclusive), wrapping around to 42 | * the end of document and stopping at start (exclusive). 43 | * 44 | * @return charOffset of found string; -1 if not found 45 | */ 46 | public int wrappedFindBackwards(DocumentProvider src, String target, int start, 47 | boolean isCaseSensitive, boolean isWholeWord); 48 | 49 | /** 50 | * Replace all matches of searchText in src with replacementText. 51 | * 52 | * @param mark Optional. A position in src that can be tracked for changes. 53 | * After replacements are made, the position may be shifted because of 54 | * insertion/deletion of text before it. The new position of mark is 55 | * returned in Pair.second. If mark is an invalid position, Pair.second 56 | * is undefined. 57 | * 58 | * @return Pair.first is the number of replacements made. 59 | * Pair.second is new position of mark after replacements are made. 60 | */ 61 | public Pair replaceAll(DocumentProvider src, String searchText, 62 | String replacementText, int mark, 63 | boolean isCaseSensitive, boolean isWholeWord); 64 | 65 | 66 | /** 67 | * The number of characters that have been examined by the current find 68 | * operation. This method is not synchronized, and the value returned 69 | * may be outdated. 70 | * 71 | * @return The number of characters searched so far 72 | */ 73 | public int getProgress(); 74 | } 75 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/util/TextBufferCache.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.mrikso.codeeditor.util; 10 | 11 | /** 12 | * A LRU cache that stores the last seek line and its corresponding index so 13 | * that future lookups can start from the cached position instead of 14 | * the beginning of the file 15 | * 16 | * _cache.Pair.First = line index 17 | * _cache.Pair.Second = character offset of first character in that line 18 | * 19 | * TextBufferCache always has one valid entry (0,0) signifying that in line 0, 20 | * the first character is at offset 0. This is true even for an "empty" file, 21 | * which is not really empty because TextBuffer inserts a EOF character in it. 22 | * 23 | * Therefore, _cache[0] is always occupied by the entry (0,0). It is not affected 24 | * by invalidateCache, cache miss, etc. operations 25 | */ 26 | public class TextBufferCache { 27 | private static final int CACHE_SIZE = 4; // minimum = 1 28 | private Pair[] _cache = new Pair[CACHE_SIZE]; 29 | 30 | public TextBufferCache(){ 31 | _cache[0] = new Pair(0, 0); // invariant lineIndex and charOffset relation 32 | for (int i = 1; i < CACHE_SIZE; ++i){ 33 | _cache[i] = new Pair(-1, -1); 34 | // -1 line index is used implicitly in calculations in getNearestMatch 35 | } 36 | } 37 | 38 | //TODO consider extracting common parts with getNearestCharOffset(int) 39 | public Pair getNearestLine(int lineIndex){ 40 | int nearestMatch = 0; 41 | int nearestDistance = Integer.MAX_VALUE; 42 | for(int i = 0; i < CACHE_SIZE; ++i){ 43 | int distance = Math.abs(lineIndex - _cache[i].getFirst()); 44 | if (distance < nearestDistance){ 45 | nearestDistance = distance; 46 | nearestMatch = i; 47 | } 48 | } 49 | 50 | Pair nearestEntry = _cache[nearestMatch]; 51 | makeHead(nearestMatch); 52 | return nearestEntry; 53 | } 54 | 55 | public Pair getNearestCharOffset(int charOffset){ 56 | int nearestMatch = 0; 57 | int nearestDistance = Integer.MAX_VALUE; 58 | for(int i = 0; i < CACHE_SIZE; ++i){ 59 | int distance = Math.abs(charOffset - _cache[i].getSecond()); 60 | if (distance < nearestDistance){ 61 | nearestDistance = distance; 62 | nearestMatch = i; 63 | } 64 | } 65 | 66 | Pair nearestEntry = _cache[nearestMatch]; 67 | makeHead(nearestMatch); 68 | return nearestEntry; 69 | } 70 | 71 | /** 72 | * Place _cache[newHead] at the top of the list 73 | */ 74 | private void makeHead(int newHead){ 75 | if(newHead == 0){ 76 | return; 77 | } 78 | 79 | Pair temp = _cache[newHead]; 80 | for(int i = newHead; i > 1; --i){ 81 | _cache[i] = _cache[i-1]; 82 | } 83 | _cache[1] = temp; // _cache[0] is always occupied by (0,0) 84 | } 85 | 86 | public void updateEntry(int lineIndex, int charOffset){ 87 | if(lineIndex <= 0){ 88 | // lineIndex 0 always has 0 charOffset; ignore. Also ignore negative lineIndex 89 | return; 90 | } 91 | 92 | if(!replaceEntry(lineIndex, charOffset)){ 93 | insertEntry(lineIndex, charOffset); 94 | } 95 | } 96 | 97 | private boolean replaceEntry(int lineIndex, int charOffset){ 98 | for (int i = 1; i < CACHE_SIZE; ++i){ 99 | if(_cache[i].getFirst() == lineIndex){ 100 | _cache[i].setSecond(charOffset); 101 | return true; 102 | } 103 | } 104 | return false; 105 | } 106 | 107 | private void insertEntry(int lineIndex, int charOffset){ 108 | makeHead(CACHE_SIZE-1); // rotate right list of entries 109 | // replace head (most recently used entry) with new entry 110 | _cache[1] = new Pair(lineIndex, charOffset); 111 | } 112 | 113 | /** 114 | * Invalidate all cache entries that have char offset >= fromCharOffset 115 | */ 116 | final protected void invalidateCache(int fromCharOffset){ 117 | for (int i = 1; i < CACHE_SIZE; ++i){ 118 | if(_cache[i].getSecond() >= fromCharOffset){ 119 | _cache[i] = new Pair(-1, -1); 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/util/TextWarriorException.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.mrikso.codeeditor.util; 10 | 11 | import android.util.Log; 12 | 13 | public class TextWarriorException extends Exception { 14 | private static final boolean NDEBUG = false; // set to true to suppress assertions 15 | 16 | public TextWarriorException(String msg) { 17 | super(msg); 18 | } 19 | 20 | static public void fail(final String details) { 21 | assertVerbose(false, details); 22 | } 23 | 24 | @SuppressWarnings("all") //suppress dead code warning when NDEBUG == true 25 | static public void assertVerbose(boolean condition, final String details) { 26 | if (NDEBUG) { 27 | return; 28 | } 29 | 30 | if (!condition) { 31 | /* For Android, a Context has to be passed into this method 32 | * to display the error message on the device screen */ 33 | System.err.print("TextWarrior assertion failed: "); 34 | // System.err.println(details); 35 | Log.i("codeeditor", details); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/util/UndoStack.java: -------------------------------------------------------------------------------- 1 | 2 | package com.mrikso.codeeditor.util; 3 | 4 | import java.util.LinkedList; 5 | 6 | /** 7 | * Implements undo/redo for insertion and deletion events of TextBuffer 8 | * 9 | * This class is tightly coupled to the implementation of TextBuffer, in 10 | * particular the inner workings of the gap data structure to optimize 11 | * undo/redo efficiency 12 | * 13 | * When text is inserted/deleted... 14 | * 1. Before text is inserted/deleted, TextBuffer calls captureInsert()/captureDelete() 15 | * 2. If the insertion/deletion is a continuation of the previous edit, 16 | * the incoming edit is merged with the top entry of the undo stack. 17 | * For 2 edits to be considered continuous, they must be the same type, 18 | * (insert or delete), occur within a pre-defined time interval of MERGE_TIME, 19 | * and the later edit must start off where the caret would have been after 20 | * the earlier edit. 21 | * 3. If the incoming edit is not continuous with the previous one, a new entry 22 | * for it is pushed on the stack 23 | * 24 | * Batch mode: 25 | * A client application can specify consecutive insert/delete operations to 26 | * undo/redo as a group. Edits made between a call to beginBatchEdit() 27 | * and a closing endBatchEdit() call are grouped as a unit. 28 | * 29 | * Undo/redo: 30 | * Undo/redo commands merely move the stack pointer and do not delete or insert 31 | * entries. Only when a new edit is made will the entries after the stack 32 | * pointer be deleted. 33 | * 34 | * Optimizaton notes: 35 | * Edited characters are copied lazily. When a new entry is pushed on the undo 36 | * stack, only the starting position and length of the inserted/deleted segment 37 | * is recorded. When another entry is pushed or when the entry is first undone, 38 | * the affected characters are then copied over. This optimization exploits the 39 | * non-destructive nature of continuous edits in TextBuffer -- deleted characters 40 | * can be retrieved from the gap and inserted characters are trivially available. 41 | * For undo/redo of the topmost entry, only the gap boundaries of TextBuffer 42 | * need to be moved. 43 | */ 44 | public class UndoStack { 45 | private TextBuffer _buf; 46 | private LinkedList _stack = new LinkedList(); 47 | private boolean _isBatchEdit = false; 48 | /** for grouping batch operations */ 49 | private int _groupId = 0; 50 | /** where new entries should go */ 51 | private int _top = 0; 52 | /** timestamp for the previous edit operation */ 53 | long _lastEditTime = -1; 54 | 55 | public UndoStack(TextBuffer buf){ 56 | _buf = buf; 57 | } 58 | 59 | /** 60 | * Undo the previous insert/delete operation 61 | * 62 | * @return The suggested position of the caret after the undo, or -1 if 63 | * there is nothing to undo 64 | */ 65 | public int undo(){ 66 | if(canUndo()){ 67 | Command lastUndone = _stack.get(_top-1); 68 | int group = lastUndone._group; 69 | do{ 70 | Command c = _stack.get(_top-1); 71 | if(c._group != group){ 72 | break; 73 | } 74 | 75 | lastUndone = c; 76 | c.undo(); 77 | --_top; 78 | } 79 | while(canUndo()); 80 | 81 | return lastUndone.findUndoPosition(); 82 | } 83 | 84 | return -1; 85 | } 86 | 87 | /** 88 | * Redo the previous insert/delete operation 89 | * 90 | * @return The suggested position of the caret after the redo, or -1 if 91 | * there is nothing to redo 92 | */ 93 | public int redo(){ 94 | if(canRedo()){ 95 | Command lastRedone = _stack.get(_top); 96 | int group = lastRedone._group; 97 | do{ 98 | Command c = _stack.get(_top); 99 | if(c._group != group){ 100 | break; 101 | } 102 | 103 | lastRedone = c; 104 | c.redo(); 105 | ++_top; 106 | } 107 | while(canRedo()); 108 | 109 | return lastRedone.findRedoPosition(); 110 | } 111 | 112 | return -1; 113 | } 114 | 115 | //TODO extract common parts of captureInsert and captureDelete 116 | /** 117 | * Records an insert operation. Should be called before the insertion is 118 | * actually done. 119 | */ 120 | public void captureInsert(int start, int length, long time){ 121 | boolean mergeSuccess = false; 122 | 123 | if(canUndo()){ 124 | Command c = _stack.get(_top - 1); 125 | 126 | if(c instanceof InsertCommand 127 | && c.merge(start, length, time)){ 128 | mergeSuccess = true; 129 | } 130 | else{ 131 | c.recordData(); 132 | } 133 | } 134 | 135 | if(!mergeSuccess){ 136 | push(new InsertCommand(start, length, _groupId)); 137 | 138 | if(!_isBatchEdit){ 139 | _groupId++; 140 | } 141 | } 142 | 143 | _lastEditTime = time; 144 | } 145 | 146 | /** 147 | * Records a delete operation. Should be called before the deletion is 148 | * actually done. 149 | */ 150 | public void captureDelete(int start, int length, long time){ 151 | boolean mergeSuccess = false; 152 | 153 | if(canUndo()){ 154 | Command c = _stack.get(_top - 1); 155 | 156 | if(c instanceof DeleteCommand 157 | && c.merge(start, length, time)){ 158 | mergeSuccess = true; 159 | } 160 | else{ 161 | c.recordData(); 162 | } 163 | } 164 | 165 | if(!mergeSuccess){ 166 | push(new DeleteCommand(start, length, _groupId)); 167 | 168 | if(!_isBatchEdit){ 169 | _groupId++; 170 | } 171 | } 172 | 173 | _lastEditTime = time; 174 | } 175 | 176 | private void push(Command c){ 177 | trimStack(); 178 | ++_top; 179 | _stack.add(c); 180 | } 181 | 182 | private void trimStack(){ 183 | while(_stack.size() > _top){ 184 | _stack.removeLast(); 185 | } 186 | } 187 | 188 | public final boolean canUndo(){ 189 | return _top > 0; 190 | } 191 | 192 | public final boolean canRedo(){ 193 | return _top < _stack.size(); 194 | } 195 | 196 | public boolean isBatchEdit(){ 197 | return _isBatchEdit; 198 | } 199 | 200 | public void beginBatchEdit(){ 201 | _isBatchEdit = true; 202 | } 203 | 204 | public void endBatchEdit(){ 205 | _isBatchEdit = false; 206 | _groupId++; 207 | } 208 | 209 | 210 | 211 | private abstract class Command{ 212 | public final static long MERGE_TIME = 1000000000; //750ms in nanoseconds 213 | /** Start position of the edit */ 214 | public int _start; 215 | /** Length of the affected segment */ 216 | public int _length; 217 | /** Contents of the affected segment */ 218 | public String _data; 219 | /** Group ID. Commands of the same group are undone/redone as a unit */ 220 | public int _group; 221 | 222 | public abstract void undo(); 223 | public abstract void redo(); 224 | /** Populates _data with the affected text */ 225 | public abstract void recordData(); 226 | public abstract int findUndoPosition(); 227 | public abstract int findRedoPosition(); 228 | 229 | /** 230 | * Attempts to merge in an edit. This will only be successful if the new 231 | * edit is continuous. See {@link UndoStack} for the requirements 232 | * of a continuous edit. 233 | * 234 | * @param start Start position of the new edit 235 | * @param length Length of the newly edited segment 236 | * @param time Timestamp when the new edit was made. There are no 237 | * restrictions on the units used, as long as it is consistently used 238 | * in the whole program 239 | * 240 | * @return Whether the merge was successful 241 | */ 242 | public abstract boolean merge(int start, int length, long time); 243 | } 244 | 245 | private class InsertCommand extends Command{ 246 | /** 247 | * Corresponds to an insertion of text of size length just before 248 | * start position. 249 | */ 250 | public InsertCommand(int start, int length, int groupNumber){ 251 | _start = start; 252 | _length = length; 253 | _group = groupNumber; 254 | } 255 | 256 | @Override 257 | public boolean merge(int newStart, int length, long time) { 258 | if(_lastEditTime < 0){ 259 | return false; 260 | } 261 | 262 | if((time - _lastEditTime) < MERGE_TIME 263 | && newStart == _start + _length){ 264 | _length += length; 265 | trimStack(); 266 | return true; 267 | } 268 | 269 | return false; 270 | } 271 | 272 | @Override 273 | public void recordData() { 274 | //TODO handle memory allocation failure 275 | _data = _buf.subSequence(_start, _length).toString(); 276 | } 277 | 278 | @Override 279 | public void undo() { 280 | if(_data == null){ 281 | recordData(); 282 | _buf.shiftGapStart(-_length); 283 | } 284 | else{ 285 | //dummy timestamp of 0 286 | _buf.delete(_start, _length, 0 ,false); 287 | } 288 | } 289 | 290 | @Override 291 | public void redo() { 292 | //dummy timestamp of 0 293 | _buf.insert(_data.toCharArray(), _start, 0, false); 294 | } 295 | 296 | @Override 297 | public int findRedoPosition() { 298 | return _start + _length; 299 | } 300 | 301 | @Override 302 | public int findUndoPosition() { 303 | return _start; 304 | } 305 | } 306 | 307 | 308 | private class DeleteCommand extends Command{ 309 | /** 310 | * Corresponds to an deletion of text of size length starting from 311 | * start position, inclusive. 312 | */ 313 | public DeleteCommand(int start, int length, int seqNumber){ 314 | _start = start; 315 | _length = length; 316 | _group = seqNumber; 317 | } 318 | 319 | @Override 320 | public boolean merge(int newStart, int length, long time) { 321 | if(_lastEditTime < 0){ 322 | return false; 323 | } 324 | 325 | if((time - _lastEditTime) < MERGE_TIME 326 | && newStart == _start - _length - length + 1){ 327 | _start = newStart; 328 | _length += length; 329 | trimStack(); 330 | return true; 331 | } 332 | 333 | return false; 334 | } 335 | 336 | @Override 337 | public void recordData() { 338 | //TODO handle memory allocation failure 339 | _data = new String(_buf.gapSubSequence(_length)); 340 | } 341 | 342 | @Override 343 | public void undo() { 344 | if(_data == null){ 345 | recordData(); 346 | _buf.shiftGapStart(_length); 347 | } 348 | else{ 349 | //dummy timestamp of 0 350 | _buf.insert(_data.toCharArray(), _start, 0, false); 351 | } 352 | } 353 | 354 | @Override 355 | public void redo() { 356 | //dummy timestamp of 0 357 | _buf.delete(_start, _length, 0, false); 358 | } 359 | 360 | @Override 361 | public int findRedoPosition() { 362 | return _start; 363 | } 364 | 365 | @Override 366 | public int findUndoPosition() { 367 | return _start + _length; 368 | } 369 | }// end inner class 370 | } 371 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/view/ClipboardPanel.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Rect; 6 | import android.os.Build; 7 | import android.view.ActionMode; 8 | import android.view.Menu; 9 | import android.view.MenuItem; 10 | import android.view.View; 11 | 12 | import androidx.annotation.RequiresApi; 13 | 14 | import com.mrikso.codeeditor.R; 15 | 16 | public class ClipboardPanel { 17 | protected FreeScrollingTextField _textField; 18 | private Context _context; 19 | 20 | private ActionMode _clipboardActionMode; 21 | private ActionMode.Callback2 _clipboardActionModeCallback2; 22 | private Rect caret; 23 | 24 | public ClipboardPanel(FreeScrollingTextField textField) { 25 | _textField = textField; 26 | _context = textField.getContext(); 27 | } 28 | 29 | public Context getContext() { 30 | return _context; 31 | } 32 | 33 | public void show() { 34 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 35 | initData(); 36 | startClipboardActionNew(); 37 | } else { 38 | startClipboardAction(); 39 | } 40 | 41 | } 42 | 43 | public void hide() { 44 | stopClipboardAction(); 45 | } 46 | 47 | public void startClipboardAction() { 48 | // TODO: Implement this method 49 | if (_clipboardActionMode == null) 50 | _textField.startActionMode(new ActionMode.Callback() { 51 | 52 | @Override 53 | public boolean onCreateActionMode(ActionMode mode, Menu menu) { 54 | // TODO: Implement this method 55 | _clipboardActionMode = mode; 56 | mode.setTitle(android.R.string.selectTextMode); 57 | TypedArray array = _context.getTheme().obtainStyledAttributes(new int[]{ 58 | android.R.attr.actionModeSelectAllDrawable, 59 | android.R.attr.actionModeCutDrawable, 60 | android.R.attr.actionModeCopyDrawable, 61 | android.R.attr.actionModePasteDrawable, 62 | }); 63 | menu.add(0, 0, 0, _context.getString(android.R.string.selectAll)) 64 | .setShowAsActionFlags(2) 65 | .setAlphabeticShortcut('a') 66 | .setIcon(array.getDrawable(0)); 67 | 68 | menu.add(0, 1, 0, _context.getString(android.R.string.cut)) 69 | .setShowAsActionFlags(2) 70 | .setAlphabeticShortcut('x') 71 | .setIcon(array.getDrawable(1)); 72 | 73 | menu.add(0, 2, 0, _context.getString(android.R.string.copy)) 74 | .setShowAsActionFlags(2) 75 | .setAlphabeticShortcut('c') 76 | .setIcon(array.getDrawable(2)); 77 | 78 | menu.add(0, 3, 0, _context.getString(android.R.string.paste)) 79 | .setShowAsActionFlags(2) 80 | .setAlphabeticShortcut('v') 81 | .setIcon(array.getDrawable(3)); 82 | array.recycle(); 83 | return true; 84 | } 85 | 86 | @Override 87 | public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 88 | // TODO: Implement this method 89 | return false; 90 | } 91 | 92 | @Override 93 | public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 94 | // TODO: Implement this method 95 | switch (item.getItemId()) { 96 | case 0: 97 | _textField.selectAll(); 98 | break; 99 | case 1: 100 | _textField.cut(); 101 | mode.finish(); 102 | break; 103 | case 2: 104 | _textField.copy(); 105 | mode.finish(); 106 | break; 107 | case 3: 108 | _textField.paste(); 109 | mode.finish(); 110 | } 111 | return false; 112 | } 113 | 114 | @Override 115 | public void onDestroyActionMode(ActionMode p1) { 116 | // TODO: Implement this method 117 | _textField.selectText(false); 118 | _clipboardActionMode = null; 119 | } 120 | }); 121 | 122 | } 123 | 124 | @RequiresApi(api = Build.VERSION_CODES.M) 125 | public void startClipboardActionNew() { 126 | // TODO: Implement this method 127 | if (_clipboardActionMode == null) 128 | _textField.startActionMode(_clipboardActionModeCallback2, ActionMode.TYPE_FLOATING); 129 | } 130 | 131 | @RequiresApi(api = Build.VERSION_CODES.M) 132 | private void initData(){ 133 | _clipboardActionModeCallback2 = new ActionMode.Callback2() { 134 | @Override 135 | public boolean onCreateActionMode(ActionMode mode, Menu menu) { 136 | // TODO: Implement this method 137 | _clipboardActionMode = mode; 138 | menu.add(0, 0, 0, _context.getString(android.R.string.selectAll)) 139 | .setShowAsActionFlags(2) 140 | .setAlphabeticShortcut('a'); 141 | 142 | menu.add(0, 1, 0, _context.getString(android.R.string.cut)) 143 | .setShowAsActionFlags(2) 144 | .setAlphabeticShortcut('x'); 145 | menu.add(0, 2, 0, _context.getString(android.R.string.copy)) 146 | .setShowAsActionFlags(2) 147 | .setAlphabeticShortcut('c'); 148 | menu.add(0, 3, 0, _context.getString(android.R.string.paste)) 149 | .setShowAsActionFlags(2) 150 | .setAlphabeticShortcut('v'); 151 | menu.add(0, 4, 0, "Delete") 152 | .setShowAsActionFlags(2) 153 | .setAlphabeticShortcut('d'); 154 | return true; 155 | } 156 | 157 | @Override 158 | public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 159 | // TODO: Implement this method 160 | return false; 161 | } 162 | 163 | @Override 164 | public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 165 | // TODO: Implement this method 166 | switch (item.getItemId()) { 167 | case 0: 168 | _textField.selectAll(); 169 | break; 170 | case 1: 171 | _textField.cut(); 172 | mode.finish(); 173 | break; 174 | case 2: 175 | _textField.copy(); 176 | mode.finish(); 177 | break; 178 | case 3: 179 | _textField.paste(); 180 | mode.finish(); 181 | case 4: 182 | _textField.delete(); 183 | mode.finish(); 184 | } 185 | return false; 186 | 187 | 188 | } 189 | 190 | @Override 191 | public void onDestroyActionMode(ActionMode p1) { 192 | // TODO: Implement this method 193 | _textField.selectText(false); 194 | _clipboardActionMode = null; 195 | caret = null; 196 | } 197 | 198 | @Override 199 | public void onGetContentRect(ActionMode mode, View view, Rect outRect){ 200 | caret = _textField.getBoundingBox(_textField.getCaretPosition()); 201 | int x = outRect.left + _textField.getPaddingLeft(); 202 | int y = outRect.bottom + _textField.getPaddingTop(); 203 | outRect = caret; 204 | super.onGetContentRect(mode, view, outRect); 205 | } 206 | }; 207 | 208 | } 209 | 210 | public void stopClipboardAction() { 211 | if (_clipboardActionMode != null) { 212 | //_clipboardActionModeCallback2.onDestroyActionMode(_clipboardActionMode); 213 | _clipboardActionMode.finish(); 214 | _clipboardActionMode = null; 215 | 216 | } 217 | } 218 | 219 | } 220 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/view/ColorScheme.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 | 10 | package com.mrikso.codeeditor.view; 11 | 12 | import com.mrikso.codeeditor.util.Lexer; 13 | import com.mrikso.codeeditor.util.TextWarriorException; 14 | 15 | import java.util.HashMap; 16 | 17 | public abstract class ColorScheme { 18 | public enum Colorable { 19 | FOREGROUND, BACKGROUND, SELECTION_FOREGROUND, SELECTION_BACKGROUND, 20 | CARET_FOREGROUND, CARET_BACKGROUND, CARET_DISABLED, LINE_HIGHLIGHT, 21 | NON_PRINTING_GLYPH, COMMENT, KEYWORD, NAME, NUMBER, STRING, 22 | SECONDARY 23 | } 24 | 25 | protected HashMap _colors = generateDefaultColors(); 26 | 27 | public void setColor(Colorable colorable, int color) { 28 | _colors.put(colorable, color); 29 | } 30 | 31 | public int getColor(Colorable colorable) { 32 | Integer color = _colors.get(colorable); 33 | if (color == null) { 34 | TextWarriorException.fail("Color not specified for " + colorable); 35 | return 0; 36 | } 37 | return color; 38 | } 39 | 40 | // Currently, color scheme is tightly coupled with semantics of the token types 41 | public int getTokenColor(int tokenType) { 42 | Colorable element; 43 | switch (tokenType) { 44 | case Lexer.NORMAL: 45 | element = Colorable.FOREGROUND; 46 | break; 47 | case Lexer.KEYWORD: 48 | element = Colorable.KEYWORD; 49 | break; 50 | case Lexer.NAME: 51 | element = Colorable.NAME; 52 | break; 53 | case Lexer.DOUBLE_SYMBOL_LINE: //fall-through 54 | case Lexer.DOUBLE_SYMBOL_DELIMITED_MULTILINE: 55 | element = Colorable.COMMENT; 56 | break; 57 | case Lexer.SINGLE_SYMBOL_DELIMITED_A: //fall-through 58 | case Lexer.SINGLE_SYMBOL_DELIMITED_B: 59 | element = Colorable.STRING; 60 | break; 61 | case Lexer.NUMBER: 62 | element = Colorable.NUMBER; 63 | break; 64 | case Lexer.SINGLE_SYMBOL_LINE_A: //fall-through 65 | case Lexer.SINGLE_SYMBOL_WORD: 66 | case Lexer.OPERATOR: 67 | element = Colorable.SECONDARY; 68 | break; 69 | case Lexer.SINGLE_SYMBOL_LINE_B: //类型 70 | element = Colorable.NAME; 71 | break; 72 | default: 73 | TextWarriorException.fail("Invalid token type"); 74 | element = Colorable.FOREGROUND; 75 | break; 76 | } 77 | return getColor(element); 78 | } 79 | 80 | /** 81 | * Whether this color scheme uses a dark background, like black or dark grey. 82 | */ 83 | public abstract boolean isDark(); 84 | 85 | private HashMap generateDefaultColors() { 86 | // High-contrast, black-on-white color scheme 87 | HashMap colors = new HashMap(Colorable.values().length); 88 | colors.put(Colorable.FOREGROUND, BLACK);//前景色 89 | colors.put(Colorable.BACKGROUND, WHITE); 90 | colors.put(Colorable.SELECTION_FOREGROUND, WHITE);//选择文本的前景色 91 | colors.put(Colorable.SELECTION_BACKGROUND, 0xFF97C024);//选择文本的背景色 92 | colors.put(Colorable.CARET_FOREGROUND, WHITE); 93 | colors.put(Colorable.CARET_BACKGROUND, LIGHT_BLUE2); 94 | colors.put(Colorable.CARET_DISABLED, GREY); 95 | colors.put(Colorable.LINE_HIGHLIGHT, 0x20888888); 96 | 97 | colors.put(Colorable.NON_PRINTING_GLYPH, 0xff2b91af);//行号 98 | colors.put(Colorable.COMMENT, OLIVE_GREEN); //注释 99 | colors.put(Colorable.KEYWORD, BLUE); //关键字 100 | colors.put(Colorable.NAME, GREY); // Eclipse default color 101 | colors.put(Colorable.NUMBER, PINK); // 数字 102 | colors.put(Colorable.STRING, DARK_RED); //字符串 103 | colors.put(Colorable.SECONDARY, 0xff6f008a);//宏定义 104 | return colors; 105 | } 106 | 107 | // In ARGB format: 0xAARRGGBB 108 | private static final int BLACK = 0xFF000000; 109 | private static final int BLUE = 0xFF0000FF; 110 | private static final int DARK_RED = 0xFFA31515; 111 | private static final int PINK = 0xFFD040DD; 112 | private static final int GREY = 0xFF808080; 113 | private static final int OLIVE_GREEN = 0xFF3F7F5F; 114 | private static final int WHITE = 0xFFFFFFE0; 115 | private static final int LIGHT_BLUE2 = 0xFF40B0FF; 116 | } 117 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/view/ColorSchemeLight.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 | 10 | package com.mrikso.codeeditor.view; 11 | 12 | 13 | /** 14 | * Off-black on off-white background color scheme 15 | */ 16 | public class ColorSchemeLight extends ColorScheme { 17 | 18 | public ColorSchemeLight(){ 19 | setColor(Colorable.FOREGROUND, OFF_BLACK); 20 | setColor(Colorable.BACKGROUND, OFF_WHITE); 21 | setColor(Colorable.SELECTION_FOREGROUND, OFF_WHITE); 22 | setColor(Colorable.CARET_FOREGROUND, OFF_WHITE); 23 | } 24 | 25 | private static final int OFF_WHITE = 0xFFF0F0ED; 26 | private static final int OFF_BLACK = 0xFF333333; 27 | 28 | @Override 29 | public boolean isDark() { 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/view/KeysInterpreter.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.mrikso.codeeditor.view; 10 | 11 | import android.view.KeyEvent; 12 | 13 | import com.mrikso.codeeditor.lang.Language; 14 | 15 | public class KeysInterpreter { 16 | public static boolean isSwitchPanel(KeyEvent event) { 17 | return (event.isShiftPressed() && 18 | (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)); 19 | } 20 | 21 | /** 22 | * Maps shortcut keys and Android keycodes to printable characters. 23 | * Note that whitespace is considered printable. 24 | * 25 | * @param event The KeyEvent to interpret 26 | * @return The printable character the event represents, 27 | * or Language.NULL_CHAR if the event does not represent a printable char 28 | */ 29 | public static char keyEventToPrintableChar(KeyEvent event) { 30 | char c = Language.NULL_CHAR; 31 | 32 | // convert tab, backspace, newline and space keycodes to standard ASCII values 33 | if (isNewline(event)) { 34 | c = Language.NEWLINE; 35 | } else if (isBackspace(event)) { 36 | c = Language.BACKSPACE; 37 | } 38 | // This should be before the check for isSpace() because the 39 | // shortcut for TAB uses the SPACE key. 40 | else if (isTab(event)) { 41 | c = Language.TAB; 42 | } else if (isSpace(event)) { 43 | c = ' '; 44 | } else if (event.isPrintingKey()) { 45 | c = (char) event.getUnicodeChar(event.getMetaState()); 46 | } 47 | 48 | return c; 49 | } 50 | 51 | private static boolean isTab(KeyEvent event) { 52 | return (event.isShiftPressed() && 53 | (event.getKeyCode() == KeyEvent.KEYCODE_SPACE)) || 54 | (event.getKeyCode() == KeyEvent.KEYCODE_TAB); 55 | } 56 | 57 | private static boolean isBackspace(KeyEvent event) { 58 | return (event.getKeyCode() == KeyEvent.KEYCODE_DEL); 59 | } 60 | 61 | private static boolean isNewline(KeyEvent event) { 62 | return (event.getKeyCode() == KeyEvent.KEYCODE_ENTER); 63 | } 64 | 65 | private static boolean isSpace(KeyEvent event) { 66 | return (event.getKeyCode() == KeyEvent.KEYCODE_SPACE); 67 | } 68 | 69 | public static boolean isNavigationKey(KeyEvent event) { 70 | int keyCode = event.getKeyCode(); 71 | return keyCode == KeyEvent.KEYCODE_DPAD_DOWN || 72 | keyCode == KeyEvent.KEYCODE_DPAD_UP || 73 | keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || 74 | keyCode == KeyEvent.KEYCODE_DPAD_LEFT; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/view/TextFieldInputConnection.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor.view; 2 | 3 | import android.text.InputType; 4 | import android.view.KeyEvent; 5 | import android.view.inputmethod.BaseInputConnection; 6 | 7 | import com.mrikso.codeeditor.lang.Language; 8 | import com.mrikso.codeeditor.util.DLog; 9 | import com.mrikso.codeeditor.util.Lexer; 10 | 11 | import static com.mrikso.codeeditor.util.DLog.log; 12 | 13 | //********************************************************************* 14 | //************************** InputConnection ************************** 15 | //********************************************************************* 16 | /* 17 | * Does not provide ExtractedText related methods 18 | */ 19 | public class TextFieldInputConnection extends BaseInputConnection { 20 | private boolean isComposing = false; 21 | private int composingCharCount = 0; 22 | private int mCaretPosition = -1; 23 | private FreeScrollingTextField textField; 24 | 25 | public TextFieldInputConnection(FreeScrollingTextField v ) { 26 | super(v, true); 27 | textField = v; 28 | } 29 | 30 | public void resetComposingState() { 31 | composingCharCount = 0; 32 | isComposing = false; 33 | textField.hDoc.endBatchEdit(); 34 | } 35 | 36 | @Override 37 | public boolean performContextMenuAction(int id) { 38 | switch (id) { 39 | case android.R.id.copy: 40 | textField.copy(); 41 | break; 42 | case android.R.id.cut: 43 | textField.cut(); 44 | break; 45 | case android.R.id.paste: 46 | textField.paste(); 47 | break; 48 | case android.R.id.startSelectingText: 49 | case android.R.id.stopSelectingText: 50 | case android.R.id.selectAll: 51 | textField.selectAll(); 52 | break; 53 | } 54 | 55 | return false; 56 | } 57 | 58 | @Override 59 | public boolean sendKeyEvent(KeyEvent event) { 60 | switch (event.getKeyCode()) { 61 | case KeyEvent.KEYCODE_SHIFT_LEFT: 62 | if (textField.isSelectText()) 63 | textField.selectText(false); 64 | else 65 | textField.selectText(true); 66 | break; 67 | case KeyEvent.KEYCODE_DPAD_LEFT: 68 | textField.moveCaretLeft(); 69 | break; 70 | case KeyEvent.KEYCODE_DPAD_UP: 71 | textField.moveCaretUp(); 72 | break; 73 | case KeyEvent.KEYCODE_DPAD_RIGHT: 74 | textField.moveCaretRight(); 75 | break; 76 | case KeyEvent.KEYCODE_DPAD_DOWN: 77 | textField.moveCaretDown(); 78 | break; 79 | case KeyEvent.KEYCODE_MOVE_HOME: 80 | textField.moveCaret(0); 81 | break; 82 | case KeyEvent.KEYCODE_MOVE_END: 83 | textField.moveCaret(textField.hDoc.length() - 1); 84 | break; 85 | case KeyEvent.KEYCODE_ENTER: 86 | case KeyEvent.KEYCODE_NUMPAD_ENTER: 87 | if (textField.mAutoCompletePanel.isShow()) { 88 | textField.mAutoCompletePanel.selectFirst(); 89 | } else { 90 | return super.sendKeyEvent(event); 91 | } 92 | break; 93 | default: 94 | return super.sendKeyEvent(event); 95 | } 96 | return true; 97 | } 98 | 99 | /** 100 | * Only true when the InputConnection has not been used by the IME yet. 101 | * Can be programatically cleared by resetComposingState() 102 | */ 103 | public boolean isComposingStarted() { 104 | return isComposing; 105 | } 106 | 107 | /** 108 | * 输入法传递过来的字符串 109 | * 110 | * @param text 111 | * @param newCursorPosition 112 | * @return 113 | */ 114 | @Override 115 | public boolean setComposingText(CharSequence text, int newCursorPosition) { 116 | isComposing = true; 117 | if(!textField.hDoc.isBatchEdit()) { 118 | textField.hDoc.beginBatchEdit(); 119 | } 120 | if (textField.mFieldController._isInSelectionMode) { 121 | textField.mFieldController.selectionDelete(); 122 | composingCharCount = 0; 123 | } 124 | else { 125 | textField.mFieldController.replaceComposingText( 126 | textField.getCaretPosition() - composingCharCount, 127 | composingCharCount, 128 | text.toString()); 129 | composingCharCount = text.length(); 130 | } 131 | //TODO reduce invalidate calls 132 | if(newCursorPosition > 1) { 133 | textField.mFieldController.moveCaret(mCaretPosition + newCursorPosition - 1); 134 | } else if(newCursorPosition <= 0) { 135 | textField.mFieldController.moveCaret(mCaretPosition - text.length() - newCursorPosition); 136 | } 137 | // log("setComposingText:"+text+","+newCursorPosition); 138 | return true; 139 | } 140 | 141 | @Override 142 | public boolean commitText(CharSequence text, int newCursorPosition) { 143 | log("commitText:"+text+","+newCursorPosition+","+composingCharCount); 144 | /* 145 | mFieldController.replaceComposingText( 146 | getCaretPosition() - composingCharCount, 147 | composingCharCount, 148 | text.toString()); 149 | composingCharCount = 0; 150 | hDoc.endBatchEdit(); 151 | //TODO reduce invalidate calls 152 | if (newCursorPosition > 1) { 153 | mFieldController.moveCaret(mCaretPosition + newCursorPosition - 1); 154 | } 155 | else if(newCursorPosition==1){ 156 | mFieldController.moveCaret(mCaretPosition + newCursorPosition); 157 | } 158 | // else if (newCursorPosition <= 0) { 159 | // mFieldController.moveCaret(mCaretPosition - text.length() - newCursorPosition); 160 | //} 161 | isComposing = false; 162 | return true; 163 | 164 | */ 165 | return setComposingText(text, newCursorPosition) && finishComposingText(); 166 | } 167 | 168 | 169 | @Override 170 | public boolean deleteSurroundingText(int leftLength, int rightLength) { 171 | if (composingCharCount != 0) { 172 | DLog.d("codeeditor","Warning: Implmentation of InputConnection.deleteSurroundingText" + 173 | " will not skip composing text"); 174 | } 175 | 176 | textField.mFieldController.deleteAroundComposingText(leftLength, rightLength); 177 | return true; 178 | } 179 | 180 | @Override 181 | public boolean finishComposingText() { 182 | resetComposingState(); 183 | return true; 184 | } 185 | 186 | @Override 187 | public int getCursorCapsMode(int reqModes) { 188 | int capsMode = 0; 189 | 190 | // Ignore InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS; not used in TextWarrior 191 | 192 | if ((reqModes & InputType.TYPE_TEXT_FLAG_CAP_WORDS) 193 | == InputType.TYPE_TEXT_FLAG_CAP_WORDS) { 194 | int prevChar = mCaretPosition - 1; 195 | if (prevChar < 0 || Lexer.getLanguage().isWhitespace(textField.hDoc.charAt(prevChar))) { 196 | capsMode |= InputType.TYPE_TEXT_FLAG_CAP_WORDS; 197 | 198 | //set CAP_SENTENCES if client is interested in it 199 | if ((reqModes & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) 200 | == InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) { 201 | capsMode |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; 202 | } 203 | } 204 | } 205 | 206 | // Strangely, Android soft keyboard does not set TYPE_TEXT_FLAG_CAP_SENTENCES 207 | // in reqModes even if it is interested in doing auto-capitalization. 208 | // Android bug? Therefore, we assume TYPE_TEXT_FLAG_CAP_SENTENCES 209 | // is always set to be on the safe side. 210 | else { 211 | Language lang = Lexer.getLanguage(); 212 | 213 | int prevChar = mCaretPosition - 1; 214 | int whitespaceCount = 0; 215 | boolean capsOn = true; 216 | 217 | // Turn on caps mode only for the first char of a sentence. 218 | // A fresh line is also considered to start a new sentence. 219 | // The position immediately after a period is considered lower-case. 220 | // Examples: "abc.com" but "abc. Com" 221 | while (prevChar >= 0) { 222 | char c = textField.hDoc.charAt(prevChar); 223 | if (c == Language.NEWLINE) { 224 | break; 225 | } 226 | 227 | if (!lang.isWhitespace(c)) { 228 | if (whitespaceCount == 0 || !lang.isSentenceTerminator(c)) { 229 | capsOn = false; 230 | } 231 | break; 232 | } 233 | 234 | ++whitespaceCount; 235 | --prevChar; 236 | } 237 | 238 | if (capsOn) { 239 | capsMode |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; 240 | } 241 | } 242 | 243 | return capsMode; 244 | } 245 | 246 | @Override 247 | public CharSequence getTextAfterCursor(int maxLen, int flags) { 248 | return textField.mFieldController.getTextAfterCursor(maxLen); //ignore flags 249 | } 250 | 251 | @Override 252 | public CharSequence getTextBeforeCursor(int maxLen, int flags) { 253 | return textField.mFieldController.getTextBeforeCursor(maxLen); //ignore flags 254 | } 255 | 256 | @Override 257 | public boolean setComposingRegion(int start, int end){ 258 | isComposing = true; 259 | mCaretPosition = start; 260 | composingCharCount = end - start; 261 | return true; 262 | 263 | } 264 | @Override 265 | public boolean setSelection(int start, int end) { 266 | log("setSelection:"+start+","+end); 267 | 268 | if (start == end) { 269 | if (start == 0) { 270 | //适配搜狗输入法 271 | if (textField.getCaretPosition() > 0) { 272 | textField.mFieldController.moveCaret(textField.getCaretPosition() - 1); 273 | } 274 | } else { 275 | textField.mFieldController.moveCaret(start); 276 | } 277 | } else { 278 | textField.mFieldController.setSelectionRange(start, end - start, false, true); 279 | } 280 | return true; 281 | } 282 | }// end inner class 283 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/view/autocomplete/AutoCompletePanel.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor.view.autocomplete; 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.view.View; 8 | import android.widget.AdapterView; 9 | import android.widget.AdapterView.OnItemClickListener; 10 | import android.widget.Filter; 11 | import android.widget.ListPopupWindow; 12 | import android.widget.TextView; 13 | 14 | import com.mrikso.codeeditor.R; 15 | import com.mrikso.codeeditor.lang.Language; 16 | import com.mrikso.codeeditor.lang.LanguageNonProg; 17 | import com.mrikso.codeeditor.view.FreeScrollingTextField; 18 | 19 | 20 | public class AutoCompletePanel { 21 | 22 | public static Language _globalLanguage = LanguageNonProg.getInstance(); 23 | public CharSequence _constraint; 24 | private FreeScrollingTextField _textField; 25 | private Context _context; 26 | private ListPopupWindow _autoCompletePanel; 27 | private AutoPanelAdapter _adapter; 28 | private Filter _filter; 29 | private int _verticalOffset; 30 | private int _height; 31 | private int _horizontal; 32 | private int _backgroundColor; 33 | private GradientDrawable gd; 34 | public int _textColor; 35 | private boolean isShow = false; 36 | 37 | public AutoCompletePanel(FreeScrollingTextField textField) { 38 | _textField = textField; 39 | _context = textField.getContext(); 40 | initAutoCompletePanel(); 41 | 42 | } 43 | 44 | synchronized public static Language getLanguage() { 45 | return _globalLanguage; 46 | } 47 | 48 | synchronized public static void setLanguage(Language lang) { 49 | _globalLanguage = lang; 50 | } 51 | 52 | public void setTextColor(int color) { 53 | _textColor = color; 54 | gd.setStroke(1, color); 55 | _autoCompletePanel.setBackgroundDrawable(gd); 56 | } 57 | 58 | public void setBackgroundColor(int color) { 59 | _backgroundColor = color; 60 | gd.setColor(color); 61 | _autoCompletePanel.setBackgroundDrawable(gd); 62 | } 63 | 64 | public void setBackground(Drawable color) { 65 | _autoCompletePanel.setBackgroundDrawable(color); 66 | } 67 | 68 | @SuppressWarnings("ResourceType") 69 | private void initAutoCompletePanel() { 70 | _autoCompletePanel = new ListPopupWindow(_context); 71 | _autoCompletePanel.setAnchorView(_textField); 72 | _adapter = new AutoPanelAdapter(_context, this, _textField); 73 | _autoCompletePanel.setAdapter(_adapter); 74 | _filter = _adapter.getFilter(); 75 | //_autoCompletePanel.setContentWidth(ListPopupWindow.WRAP_CONTENT); 76 | setHeight(300); 77 | 78 | TypedArray array = _context.getTheme().obtainStyledAttributes(new int[]{ 79 | android.R.attr.colorBackground, 80 | android.R.attr.textColorPrimary, 81 | }); 82 | int backgroundColor = array.getColor(0, 0xFF00FF); 83 | int textColor = array.getColor(1, 0xFF00FF); 84 | array.recycle(); 85 | gd = new GradientDrawable(); 86 | gd.setColor(backgroundColor); 87 | gd.setCornerRadius(4); 88 | gd.setStroke(1, textColor); 89 | setTextColor(textColor); 90 | _autoCompletePanel.setBackgroundDrawable(gd); 91 | _autoCompletePanel.setOnItemClickListener(new OnItemClickListener() { 92 | @Override 93 | public void onItemClick(AdapterView p1, View p2, int p3, long p4) { 94 | select(p3); 95 | } 96 | }); 97 | } 98 | 99 | public void selectFirst() { 100 | select(0); 101 | } 102 | 103 | public void select(int pos) { 104 | View view = _adapter.getView(pos, null, null); 105 | TextView textView = view.findViewById(R.id.auto_panel_text); 106 | String text = textView.getText().toString(); 107 | String commitText = null; 108 | boolean isFunc = text.contains("("); 109 | if (isFunc) { 110 | commitText = text.substring(0, text.indexOf('(')) + "()"; 111 | } else { 112 | commitText = text; 113 | } 114 | _textField.replaceText(_textField.getCaretPosition() - _constraint.length(), _constraint.length(), commitText); 115 | _adapter.abort(); 116 | dismiss(); 117 | if (isFunc) { 118 | _textField.moveCaretLeft(); 119 | } 120 | 121 | } 122 | 123 | public void setWidth(int width) { 124 | _autoCompletePanel.setWidth(width); 125 | } 126 | 127 | public void setHeight(int height) { 128 | if (_height != height) { 129 | _height = height; 130 | _autoCompletePanel.setHeight(height); 131 | } 132 | } 133 | 134 | public void setHorizontalOffset(int horizontal) { 135 | horizontal = Math.min(horizontal, _textField.getWidth() / 2); 136 | if (_horizontal != horizontal) { 137 | _horizontal = horizontal; 138 | _autoCompletePanel.setHorizontalOffset(horizontal); 139 | } 140 | } 141 | 142 | void setVerticalOffset(int verticalOffset) { 143 | //verticalOffset=Math.min(verticalOffset,_textField.getWidth()/2); 144 | int max = 0 - _autoCompletePanel.getHeight(); 145 | if (verticalOffset > max) { 146 | _textField.scrollBy(0, verticalOffset - max); 147 | verticalOffset = max; 148 | } 149 | if (_verticalOffset != verticalOffset) { 150 | _verticalOffset = verticalOffset; 151 | _autoCompletePanel.setVerticalOffset(verticalOffset); 152 | } 153 | } 154 | 155 | public void update(CharSequence constraint) { 156 | _adapter.restart(); 157 | _filter.filter(constraint); 158 | } 159 | 160 | public void show() { 161 | if (!_autoCompletePanel.isShowing()) 162 | _autoCompletePanel.show(); 163 | _autoCompletePanel.getListView().setFadingEdgeLength(0); 164 | isShow = true; 165 | } 166 | 167 | public void dismiss() { 168 | if (_autoCompletePanel.isShowing()) { 169 | isShow = false; 170 | _autoCompletePanel.dismiss(); 171 | } 172 | 173 | } 174 | 175 | public boolean isShow() { 176 | return _autoCompletePanel.isShowing(); 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/view/autocomplete/AutoPanelAdapter.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor.view.autocomplete; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Color; 7 | import android.text.SpannableString; 8 | import android.text.Spanned; 9 | import android.text.style.ForegroundColorSpan; 10 | import android.util.DisplayMetrics; 11 | import android.util.TypedValue; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.BaseAdapter; 16 | import android.widget.Filter; 17 | import android.widget.Filterable; 18 | import android.widget.ImageView; 19 | import android.widget.TextView; 20 | 21 | import com.mrikso.codeeditor.R; 22 | import com.mrikso.codeeditor.util.Flag; 23 | import com.mrikso.codeeditor.view.FreeScrollingTextField; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | import static com.mrikso.codeeditor.util.DLog.log; 29 | 30 | /** 31 | * Adapter定义 32 | */ 33 | public class AutoPanelAdapter extends BaseAdapter implements Filterable { 34 | 35 | private final int PADDING = 20; 36 | private int _h; 37 | private Flag _abort; 38 | private DisplayMetrics dm; 39 | private List listItems; 40 | private Bitmap bitmap; 41 | private Context _context; 42 | private AutoCompletePanel mAutoComplete; 43 | private FreeScrollingTextField mTextFiled; 44 | 45 | public AutoPanelAdapter(Context context, AutoCompletePanel panel, FreeScrollingTextField textField) { 46 | _context = context; 47 | mAutoComplete = panel; 48 | mTextFiled = textField; 49 | _abort = new Flag(); 50 | listItems = new ArrayList<>(); 51 | dm = context.getResources().getDisplayMetrics(); 52 | bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon_method); 53 | } 54 | 55 | public void abort() { 56 | _abort.set(); 57 | } 58 | 59 | @Override 60 | public int getCount() { 61 | return listItems.size(); 62 | } 63 | 64 | @Override 65 | public ListItem getItem(int i) { 66 | return listItems.get(i); 67 | } 68 | 69 | @Override 70 | public long getItemId(int i) { 71 | return i; 72 | } 73 | 74 | @Override 75 | public View getView(int i, View view, ViewGroup viewGroup) { 76 | View tempView = null; 77 | if (view == null) { 78 | View rootView = LayoutInflater.from(_context).inflate(R.layout.auto_panel_item, null); 79 | tempView = rootView; 80 | } else { 81 | tempView = view; 82 | } 83 | TextView textView = tempView.findViewById(R.id.auto_panel_text); 84 | ImageView imageView = tempView.findViewById(R.id.auto_panel_icon); 85 | String text = getItem(i).getText(); 86 | SpannableString spannableString = null; 87 | ForegroundColorSpan foregroundColorSpan = null; 88 | // Setting setting = Setting.getInstance(_context); 89 | log(text); 90 | if (text.contains("(")) { 91 | //函数 92 | ForegroundColorSpan argsForegroundColorSpan = null; 93 | spannableString = new SpannableString(text); 94 | // if(setting.isDarkMode()) { 95 | // foregroundColorSpan = new ForegroundColorSpan(Color.WHITE); 96 | // argsForegroundColorSpan = new ForegroundColorSpan(Color.parseColor("#9C9C9C")); 97 | // } 98 | // else{ 99 | foregroundColorSpan = new ForegroundColorSpan(Color.BLACK); 100 | argsForegroundColorSpan = new ForegroundColorSpan(Color.GRAY); 101 | // } 102 | spannableString.setSpan(foregroundColorSpan, 0, text.indexOf('('), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 103 | spannableString.setSpan(argsForegroundColorSpan, text.indexOf('('), text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 104 | } else if (text.contains("[keyword]")) { 105 | //关键字 106 | // if(setting.isDarkMode()) { 107 | // foregroundColorSpan = new ForegroundColorSpan(Color.YELLOW); 108 | // } 109 | // else{ 110 | foregroundColorSpan = new ForegroundColorSpan(mAutoComplete._textColor); 111 | // } 112 | int idx = text.indexOf("[keyword]"); 113 | text = text.substring(0, idx); 114 | spannableString = new SpannableString(text); 115 | spannableString.setSpan(foregroundColorSpan, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 116 | } else { 117 | //其他 118 | spannableString = new SpannableString(text); 119 | // if(setting.isDarkMode()) { 120 | // foregroundColorSpan = new ForegroundColorSpan(Color.WHITE); 121 | // } 122 | // else{ 123 | foregroundColorSpan = new ForegroundColorSpan(mAutoComplete._textColor); 124 | // } 125 | spannableString.setSpan(foregroundColorSpan, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 126 | } 127 | textView.setText(spannableString); 128 | imageView.setImageBitmap(getItem(i).getBitmap()); 129 | return tempView; 130 | } 131 | 132 | public void restart() { 133 | _abort.clear(); 134 | } 135 | 136 | /** 137 | * 计算列表高 138 | * 139 | * @return 140 | */ 141 | public int getItemHeight() { 142 | if (_h != 0) 143 | return _h; 144 | LayoutInflater inflater = LayoutInflater.from(_context); 145 | View rootView = inflater.inflate(R.layout.auto_panel_item, null); 146 | rootView.measure(0, 0); 147 | _h = rootView.getMeasuredHeight(); 148 | 149 | return _h; 150 | } 151 | 152 | /** 153 | * 实现自动完成的过滤算法 154 | */ 155 | @Override 156 | public Filter getFilter() { 157 | Filter filter = new Filter() { 158 | /** 159 | * 本方法在后台线程执行,定义过滤算法 160 | */ 161 | @Override 162 | protected FilterResults performFiltering(CharSequence constraint) { 163 | // 此处实现过滤 164 | // 过滤后利用FilterResults将过滤结果返回 165 | ArrayList buf = new ArrayList(); 166 | String input = String.valueOf(constraint).toLowerCase(); 167 | 168 | String[] keywords = AutoCompletePanel._globalLanguage.getUserWord(); 169 | for (String k : keywords) { 170 | if (k.toLowerCase().startsWith(input)) 171 | buf.add(k); 172 | } 173 | keywords = AutoCompletePanel._globalLanguage.getKeywords(); 174 | for (String k : keywords) { 175 | if (k.indexOf(input) == 0) 176 | buf.add(k); 177 | } 178 | keywords = AutoCompletePanel._globalLanguage.getNames(); 179 | for (String k : keywords) { 180 | if (k.toLowerCase().startsWith(input)) 181 | buf.add(k); 182 | } 183 | mAutoComplete._constraint = input; 184 | FilterResults filterResults = new FilterResults(); 185 | filterResults.values = buf; // results是上面的过滤结果 186 | filterResults.count = buf.size(); // 结果数量 187 | return filterResults; 188 | } 189 | 190 | /** 191 | * 本方法在UI线程执行,用于更新自动完成列表 192 | */ 193 | @Override 194 | protected void publishResults(CharSequence constraint, FilterResults results) { 195 | if (results != null && results.count > 0 && !_abort.isSet()) { 196 | // 有过滤结果,显示自动完成列表 197 | listItems.clear(); // 清空旧列表 198 | ArrayList stringArrayList = (ArrayList) results.values; 199 | for (int i = 0; i < stringArrayList.size(); i++) { 200 | String itemText = stringArrayList.get(i); 201 | if (itemText.contains("(")) { 202 | listItems.add(new ListItem(bitmap, itemText)); 203 | } else { 204 | listItems.add(new ListItem(null, itemText)); 205 | } 206 | } 207 | int y = mTextFiled.getCaretY() + mTextFiled.rowHeight() / 2 - mTextFiled.getScrollY(); 208 | mAutoComplete.setHeight(getItemHeight() * Math.min(2, results.count)); 209 | 210 | mAutoComplete.setHorizontalOffset(PADDING); 211 | mAutoComplete.setWidth(mTextFiled.getWidth() - PADDING * 2); 212 | mAutoComplete.setVerticalOffset(y - mTextFiled.getHeight());//_textField.getCaretY()-_textField.getScrollY()-_textField.getHeight()); 213 | notifyDataSetChanged(); 214 | mAutoComplete.show(); 215 | } else { 216 | // 无过滤结果,关闭列表 217 | notifyDataSetInvalidated(); 218 | } 219 | } 220 | 221 | }; 222 | return filter; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /codeeditor/src/main/java/com/mrikso/codeeditor/view/autocomplete/ListItem.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor.view.autocomplete; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | public class ListItem { 6 | private Bitmap bitmap; 7 | private String text; 8 | 9 | public ListItem(Bitmap bitmap, String text) { 10 | this.bitmap = bitmap; 11 | this.text = text; 12 | } 13 | 14 | public Bitmap getBitmap() { 15 | return bitmap; 16 | } 17 | 18 | public void setBitmap(Bitmap bitmap) { 19 | this.bitmap = bitmap; 20 | } 21 | 22 | public String getText() { 23 | return text; 24 | } 25 | 26 | public void setText(String text) { 27 | this.text = text; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /codeeditor/src/main/res/drawable/icon_method.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIkso/CodeEditor/a807a1459eae6a94b84c5687584677ae9eac8013/codeeditor/src/main/res/drawable/icon_method.png -------------------------------------------------------------------------------- /codeeditor/src/main/res/layout/auto_panel_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /codeeditor/src/test/java/com/mrikso/codeeditor/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.mrikso.codeeditor; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | 21 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIkso/CodeEditor/a807a1459eae6a94b84c5687584677ae9eac8013/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Mar 03 20:26:06 EET 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='Text Editor' 2 | include ':app' //, ':lib-n-ide' 3 | include ':codeeditor' 4 | --------------------------------------------------------------------------------