├── .gitignore ├── .gitmodules ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── dictionaries │ └── zhipingne.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── .project ├── .settings └── org.eclipse.buildship.core.prefs ├── README.md ├── app ├── .classpath ├── .gitignore ├── .project ├── .settings │ └── org.eclipse.buildship.core.prefs ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── ikws4 │ │ └── codeeditor │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── example.java │ ├── java │ │ └── io │ │ │ └── ikws4 │ │ │ └── codeeditor │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_close_24dp.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_redo_24dp.xml │ │ └── ic_undo_24dp.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── menu │ │ └── toolbar_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ └── test │ └── java │ └── io │ └── ikws4 │ └── codeeditor │ └── ExampleUnitTest.java ├── build.gradle ├── editor ├── .classpath ├── .gitignore ├── .project ├── .settings │ └── org.eclipse.buildship.core.prefs ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── ikws4 │ │ └── codeeditor │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── io │ │ │ └── ikws4 │ │ │ └── codeeditor │ │ │ ├── CodeEditor.java │ │ │ ├── CodeViewer.java │ │ │ ├── api │ │ │ ├── configuration │ │ │ │ ├── ColorScheme.java │ │ │ │ └── SyntaxColorScheme.java │ │ │ ├── document │ │ │ │ ├── Document.java │ │ │ │ └── markup │ │ │ │ │ ├── Markup.java │ │ │ │ │ ├── ReplacedMarkup.java │ │ │ │ │ ├── SyntaxMarkup.java │ │ │ │ │ └── TabMarkup.java │ │ │ ├── editor │ │ │ │ ├── Editor.java │ │ │ │ ├── LayoutModel.java │ │ │ │ ├── ScaleModel.java │ │ │ │ ├── ScrollingModel.java │ │ │ │ ├── SelectionModel.java │ │ │ │ ├── component │ │ │ │ │ └── Component.java │ │ │ │ └── listener │ │ │ │ │ ├── ScaleListener.java │ │ │ │ │ ├── SelectionListener.java │ │ │ │ │ └── VisibleAreaListener.java │ │ │ └── language │ │ │ │ ├── Language.java │ │ │ │ ├── LanguageParser.java │ │ │ │ ├── LanguageStyler.java │ │ │ │ ├── LanguageSuggestionProvider.java │ │ │ │ ├── ParseException.java │ │ │ │ ├── ParseResult.java │ │ │ │ └── Suggestion.java │ │ │ ├── component │ │ │ ├── Gutter.java │ │ │ ├── TextArea.java │ │ │ └── Toolbar.java │ │ │ ├── configuration │ │ │ ├── Configuration.java │ │ │ ├── colorscheme │ │ │ │ ├── ColorSchemes.java │ │ │ │ ├── DarculaColorScheme.java │ │ │ │ └── NordColorScheme.java │ │ │ └── indent │ │ │ │ ├── Indentation.java │ │ │ │ └── Whitespace.java │ │ │ ├── language │ │ │ ├── TSHighlightType.java │ │ │ ├── TSIndentType.java │ │ │ ├── TSLangaugeQuery.java │ │ │ ├── TSLanguageStyler.java │ │ │ ├── TSUtil.java │ │ │ ├── java │ │ │ │ ├── JavaLanguage.java │ │ │ │ ├── JavaParser.java │ │ │ │ ├── JavaQuery.java │ │ │ │ ├── JavaStyler.java │ │ │ │ ├── JavaSuggestionProvider.java │ │ │ │ └── Scms.kt │ │ │ └── none │ │ │ │ └── NoneLanguage.java │ │ │ ├── task │ │ │ ├── FormatTask.java │ │ │ ├── ParsingMarkupTask.java │ │ │ └── TaskFinishedListener.java │ │ │ └── widget │ │ │ ├── HScrollView.java │ │ │ ├── KeyButton.java │ │ │ └── VScrollView.java │ └── res │ │ ├── drawable │ │ ├── cursor.xml │ │ ├── ic_copy.xml │ │ ├── ic_cut.xml │ │ ├── ic_keyboard.xml │ │ ├── ic_keyboard_arrow_down.xml │ │ ├── ic_keyboard_arrow_left.xml │ │ ├── ic_keyboard_arrow_right.xml │ │ ├── ic_keyboard_arrow_up.xml │ │ ├── ic_keyboard_hide.xml │ │ ├── ic_keyboard_tab.xml │ │ ├── ic_paste.xml │ │ └── ic_select_all.xml │ │ ├── layout │ │ ├── editor.xml │ │ ├── item_suggestion.xml │ │ └── toolbar.xml │ │ └── menu │ │ └── clipboard_panel_menu.xml │ └── test │ └── java │ └── io │ └── ikws4 │ └── codeeditor │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jsitter ├── .classpath ├── .gitignore ├── .project ├── .settings │ └── org.eclipse.buildship.core.prefs ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── cpp │ ├── CMakeLists.txt │ └── src │ │ └── jsitter.cc │ └── java │ └── io │ └── ikws4 │ └── jsitter │ ├── TSLanguages.java │ ├── TSNode.java │ ├── TSParser.java │ ├── TSQuery.java │ ├── TSQueryCapture.java │ ├── TSQueryMatch.java │ ├── TSTree.java │ ├── TSTreeCursor.java │ └── TreeSitter.java ├── screenshot └── 1.png └── settings.gradle /.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 | local.properties 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "jsitter/src/main/cpp/tree-sitter"] 2 | path = jsitter/src/main/cpp/tree-sitter 3 | url = https://github.com/tree-sitter/tree-sitter.git 4 | [submodule "jsitter/src/main/cpp/grammars/tree-sitter-java"] 5 | path = jsitter/src/main/cpp/grammars/tree-sitter-java 6 | url = https://github.com/tree-sitter/tree-sitter-java.git 7 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 20 | 21 | 22 | 134 | 135 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/dictionaries/zhipingne.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | darcula 5 | expandtab 6 | instanceof 7 | oldh 8 | oldw 9 | oldx 10 | oldy 11 | strikethrough 12 | styler 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 41 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 26 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | CodeEditor 4 | Project CodeEditor created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | 19 | 1615984813542 20 | 21 | 30 22 | 23 | org.eclipse.core.resources.regexFilterMatcher 24 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments= 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=/Library/Java/JavaVirtualMachines/jdk-12.0.1.jdk/Contents/Home 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Get start 2 | ```bash 3 | git clone --recursive https://github.com/ikws4/CodeEditor.git 4 | ``` 5 | 6 | # Screenshot 7 | ![1](screenshot/1.png) 8 | -------------------------------------------------------------------------------- /app/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | app 4 | Project app created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | 25 | 1615984813546 26 | 27 | 30 28 | 29 | org.eclipse.core.resources.regexFilterMatcher 30 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=.. 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdkVersion 30 7 | buildToolsVersion "30.0.3" 8 | 9 | defaultConfig { 10 | applicationId "io.ikws4.codeeditor" 11 | minSdkVersion 23 12 | targetSdkVersion 30 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | buildFeatures { 30 | viewBinding true 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation 'androidx.appcompat:appcompat:1.2.0' 36 | implementation 'com.google.android.material:material:1.3.0' 37 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 38 | 39 | implementation project(path: ':editor') 40 | 41 | testImplementation 'junit:junit:4.+' 42 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 43 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 44 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/androidTest/java/io/ikws4/codeeditor/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.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 | assertEquals("io.ikws4.codeeditor", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/assets/example.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.internal; 2 | 3 | import android.content.Context; 4 | import android.os.AsyncTask; 5 | import android.text.Editable; 6 | import android.text.Spanned; 7 | import android.util.AttributeSet; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.Nullable; 11 | 12 | import java.lang.ref.WeakReference; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import io.ikws4.codeeditor.api.language.Language; 17 | import io.ikws4.codeeditor.api.language.LanguageStyler; 18 | import io.ikws4.codeeditor.language.java.JavaLanguage; 19 | import io.ikws4.codeeditor.api.document.markup.SyntaxMarkup; 20 | import io.ikws4.jsitter.TSLanguages; 21 | import io.ikws4.jsitter.TSParser; 22 | import io.ikws4.jsitter.TSQuery; 23 | import io.ikws4.jsitter.TSQueryCursor; 24 | import io.ikws4.jsitter.TSTree; 25 | import io.ikws4.jsitter.TSTreeCursor; 26 | 27 | abstract class SyntaxHighlightEditText extends NumberLineEditText { 28 | static { 29 | System.loadLibrary("jsitter"); 30 | } 31 | 32 | // TreeSitter Parser 33 | private final TSParser mTSParser = new TSParser(); 34 | 35 | private SyntaxHighlightTask mSyntaxHighlightTask; 36 | 37 | // TODO: Remove add None Language for default value 38 | private Language mLanguage; 39 | 40 | // Spans 41 | private final List mSyntaxSpans = new ArrayList<>(); 42 | 43 | public SyntaxHighlightEditText(@NonNull Context context) { 44 | this(context, null); 45 | } 46 | 47 | public SyntaxHighlightEditText(@NonNull Context context, @Nullable AttributeSet attrs) { 48 | this(context, attrs, android.R.attr.autoCompleteTextViewStyle); 49 | } 50 | 51 | public SyntaxHighlightEditText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 52 | super(context, attrs, defStyleAttr); 53 | setLanguage(new JavaLanguage()); 54 | } 55 | 56 | @Override 57 | public void afterTextChanged(Editable s) { 58 | super.afterTextChanged(s); 59 | highlight(); 60 | } 61 | 62 | public Language getLanguage() { 63 | return mLanguage; 64 | } 65 | 66 | public void setLanguage(Language language) { 67 | mLanguage = language; 68 | mTSParser.setLanguage(language.getTSLanguage()); 69 | } 70 | 71 | public void updateSyntaxHighlighting() { 72 | if (getLayout() == null) return; 73 | cleanSyntaxHighlightSpan(); 74 | 75 | Editable text = getText(); 76 | int start = getLayout().getLineStart(getMinVisibleLine()); 77 | int end = getLayout().getLineEnd(getMaxVisibleLine()); 78 | int n = syntaxHighlightSpanIndexOf(end); 79 | for (int i = syntaxHighlightSpanIndexOf(start); i < n; i++) { 80 | SyntaxMarkup span = mSyntaxSpans.get(i); 81 | text.setSpan(span, span.getStart(), span.getEnd(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 82 | } 83 | } 84 | 85 | private int syntaxHighlightSpanIndexOf(int pos) { 86 | int l = 0, r = mSyntaxSpans.size(); 87 | while (l < r) { 88 | int m = l + (r - l) / 2; 89 | SyntaxMarkup span = mSyntaxSpans.get(m); 90 | if (span.getStart() < pos) { 91 | l = m + 1; 92 | } else { 93 | r = m; 94 | } 95 | } 96 | return l; 97 | } 98 | 99 | private void cleanSyntaxHighlightSpan() { 100 | SyntaxMarkup[] spans = getText().getSpans(0, getText().length(), SyntaxMarkup.class); 101 | Editable text = getText(); 102 | for (SyntaxMarkup span : spans) { 103 | text.removeSpan(span); 104 | } 105 | } 106 | 107 | private void highlight() { 108 | if (mLanguage == null) return; 109 | // Stop the previous task 110 | if (mSyntaxHighlightTask != null) { 111 | mSyntaxHighlightTask.cancel(true); 112 | } 113 | mSyntaxHighlightTask = new SyntaxHighlightTask(this); 114 | mSyntaxHighlightTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 115 | 116 | TSTree tree = mTSParser.parseString(getText().toString()); 117 | 118 | TSTreeCursor cursor = tree.getRootNode().walk(); 119 | } 120 | 121 | private static class SyntaxHighlightTask extends AsyncTask> { 122 | private final WeakReference mEditor; 123 | 124 | public SyntaxHighlightTask(SyntaxHighlightEditText editor) { 125 | super(); 126 | mEditor = new WeakReference<>(editor); 127 | } 128 | 129 | @Override 130 | protected List doInBackground(Void... voids) { 131 | SyntaxHighlightEditText editor = mEditor.get(); 132 | LanguageStyler h = editor.getLanguage().getStyler(); 133 | return h.process(editor.getText().toString(), editor.getColorScheme().getSyntaxScheme()); 134 | } 135 | 136 | @Override 137 | protected void onPostExecute(List spans) { 138 | if (spans == null) return; 139 | SyntaxHighlightEditText editor = mEditor.get(); 140 | editor.mSyntaxSpans.clear(); 141 | editor.mSyntaxSpans.addAll(spans); 142 | editor.updateSyntaxHighlighting(); 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /app/src/main/java/io/ikws4/codeeditor/MainActivity.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import android.os.Bundle; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | 11 | import io.ikws4.codeeditor.api.document.Document; 12 | import io.ikws4.codeeditor.databinding.ActivityMainBinding; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater()); 20 | setContentView(binding.getRoot()); 21 | 22 | CodeEditor editor = binding.codeEditor; 23 | editor.setDocument(new Document(getExampleJavaSourceCode())); 24 | 25 | binding.toolbar.setOnMenuItemClickListener((menu) -> { 26 | int id = menu.getItemId(); 27 | if (id == R.id.undo) { 28 | editor.undo(); 29 | } else if (id == R.id.redo) { 30 | editor.redo(); 31 | } else if (id == R.id.format) { 32 | editor.format(); 33 | } else { 34 | return false; 35 | } 36 | return true; 37 | }); 38 | 39 | } 40 | 41 | private String getExampleJavaSourceCode() { 42 | try { 43 | InputStream inputStream = getAssets().open("example.java"); 44 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 45 | StringBuilder builder = new StringBuilder(); 46 | for (String line; (line = reader.readLine()) != null;) { 47 | builder.append(line).append('\n'); 48 | } 49 | // for (int i = 0; i < 6; i++) { 50 | // builder.append(builder.toString()); 51 | // } 52 | return builder.toString(); 53 | } catch (Exception e) { 54 | return ""; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /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_close_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /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/drawable/ic_redo_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_undo_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 21 | 22 | 23 | 24 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/menu/toolbar_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 10 | 15 | 16 | 19 | -------------------------------------------------------------------------------- /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/ikws4/CodeEditor/ec300715612665fa8dc9d3ba8dd4f7416679c3f7/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikws4/CodeEditor/ec300715612665fa8dc9d3ba8dd4f7416679c3f7/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikws4/CodeEditor/ec300715612665fa8dc9d3ba8dd4f7416679c3f7/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikws4/CodeEditor/ec300715612665fa8dc9d3ba8dd4f7416679c3f7/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikws4/CodeEditor/ec300715612665fa8dc9d3ba8dd4f7416679c3f7/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikws4/CodeEditor/ec300715612665fa8dc9d3ba8dd4f7416679c3f7/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikws4/CodeEditor/ec300715612665fa8dc9d3ba8dd4f7416679c3f7/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikws4/CodeEditor/ec300715612665fa8dc9d3ba8dd4f7416679c3f7/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikws4/CodeEditor/ec300715612665fa8dc9d3ba8dd4f7416679c3f7/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikws4/CodeEditor/ec300715612665fa8dc9d3ba8dd4f7416679c3f7/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF2E3440 4 | #FF3B4252 5 | #FF434C5E 6 | #FF4C566A 7 | #FFD8DEE9 8 | #FFE5E9F0 9 | #FFECEFF4 10 | #FF8FBCBB 11 | #FF88C0D0 12 | #FF81A1C1 13 | #FF5E81AC 14 | #FFBF616A 15 | #FFD08770 16 | #FFEBCB8B 17 | #FFA3BE8C 18 | #FFB48EAD 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CodeEditor 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | -------------------------------------------------------------------------------- /app/src/test/java/io/ikws4/codeeditor/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.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 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | ext.kotlin_version = '1.4.31' 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath "com.android.tools.build:gradle:4.1.2" 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | maven { url 'https://jitpack.io' } 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } -------------------------------------------------------------------------------- /editor/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /editor/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /editor/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | editor 4 | Project editor created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | 25 | 1615984813549 26 | 27 | 30 28 | 29 | org.eclipse.core.resources.regexFilterMatcher 30 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /editor/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=.. 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /editor/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | apply plugin: 'kotlin-android' 5 | 6 | android { 7 | compileSdkVersion 30 8 | buildToolsVersion "30.0.3" 9 | 10 | defaultConfig { 11 | minSdkVersion 23 12 | targetSdkVersion 30 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | consumerProguardFiles "consumer-rules.pro" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation 'androidx.appcompat:appcompat:1.2.0' 34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 35 | implementation 'com.google.googlejavaformat:google-java-format:1.7' 36 | implementation "androidx.recyclerview:recyclerview:1.1.0" 37 | 38 | implementation project(path: ':jsitter') 39 | 40 | testImplementation 'junit:junit:4.+' 41 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 42 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 43 | } -------------------------------------------------------------------------------- /editor/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikws4/CodeEditor/ec300715612665fa8dc9d3ba8dd4f7416679c3f7/editor/consumer-rules.pro -------------------------------------------------------------------------------- /editor/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 -------------------------------------------------------------------------------- /editor/src/androidTest/java/io/ikws4/codeeditor/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.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 | assertEquals("io.ikws4.codeeditor.test", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /editor/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/CodeEditor.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.graphics.Rect; 6 | import android.text.Layout; 7 | import android.text.Selection; 8 | import android.util.AttributeSet; 9 | import android.view.LayoutInflater; 10 | import android.view.MotionEvent; 11 | import android.view.ScaleGestureDetector; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.view.inputmethod.InputMethodManager; 15 | import android.widget.FrameLayout; 16 | 17 | import androidx.annotation.NonNull; 18 | import androidx.annotation.Nullable; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Objects; 23 | 24 | import io.ikws4.codeeditor.api.configuration.ColorScheme; 25 | import io.ikws4.codeeditor.api.document.Document; 26 | import io.ikws4.codeeditor.api.editor.Editor; 27 | import io.ikws4.codeeditor.api.editor.LayoutModel; 28 | import io.ikws4.codeeditor.api.editor.ScaleModel; 29 | import io.ikws4.codeeditor.api.editor.ScrollingModel; 30 | import io.ikws4.codeeditor.api.editor.SelectionModel; 31 | import io.ikws4.codeeditor.api.editor.component.Component; 32 | import io.ikws4.codeeditor.api.editor.listener.ScaleListener; 33 | import io.ikws4.codeeditor.api.editor.listener.SelectionListener; 34 | import io.ikws4.codeeditor.api.editor.listener.VisibleAreaListener; 35 | import io.ikws4.codeeditor.api.language.Language; 36 | import io.ikws4.codeeditor.component.Gutter; 37 | import io.ikws4.codeeditor.component.TextArea; 38 | import io.ikws4.codeeditor.component.Toolbar; 39 | import io.ikws4.codeeditor.configuration.Configuration; 40 | import io.ikws4.codeeditor.language.java.JavaLanguage; 41 | import io.ikws4.codeeditor.widget.HScrollView; 42 | import io.ikws4.codeeditor.widget.VScrollView; 43 | 44 | @SuppressLint("ClickableViewAccessibility") 45 | public class CodeEditor extends FrameLayout implements Editor, ScaleGestureDetector.OnScaleGestureListener, 46 | SelectionModel, ScrollingModel, ScaleModel, LayoutModel { 47 | static { 48 | System.loadLibrary("jsitter"); 49 | } 50 | 51 | private static final String TAG = "Editor"; 52 | 53 | private Configuration mConfiguration; 54 | private Language mLanguage; 55 | 56 | private final List mSelectionListeners; 57 | private final List mVisibleAreaListeners; 58 | private final List mScaleListeners; 59 | 60 | private final HScrollView mHScrollView; 61 | private final VScrollView mVScrollView; 62 | private final TextArea mTextArea; 63 | private final Toolbar mToolbar; 64 | private final Gutter mGutter; 65 | 66 | private final ScaleGestureDetector mScaleGestureDetector; 67 | 68 | // visible area 69 | private int mScrollX; 70 | private int mScrollY; 71 | private int mWidth; 72 | private int mHeight; 73 | private Rect mVisibleArea = new Rect(); 74 | 75 | // selection 76 | private int mSelStart; 77 | private int mSelEnd; 78 | 79 | // scale 80 | private float mScaleFactor = 1.0f; 81 | 82 | private final InputMethodManager mIMM; 83 | 84 | 85 | public CodeEditor(@NonNull Context context) { 86 | this(context, null); 87 | } 88 | 89 | public CodeEditor(@NonNull Context context, @Nullable AttributeSet attrs) { 90 | this(context, attrs, 0); 91 | } 92 | 93 | public CodeEditor(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 94 | super(context, attrs, defStyleAttr); 95 | LayoutInflater.from(context).inflate(R.layout.editor, this, true); 96 | 97 | mConfiguration = new Configuration(); 98 | mLanguage = new JavaLanguage(); 99 | 100 | mScaleListeners = new ArrayList<>(); 101 | mSelectionListeners = new ArrayList<>(); 102 | mVisibleAreaListeners = new ArrayList<>(); 103 | 104 | mHScrollView = findViewById(R.id.hScrollView); 105 | mVScrollView = findViewById(R.id.vScrollView); 106 | mTextArea = findViewById(R.id.textArea); 107 | mToolbar = findViewById(R.id.toolbar); 108 | mGutter = findViewById(R.id.gutter); 109 | 110 | // delegate scroll and scale event 111 | mHScrollView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { 112 | mScrollX = scrollX; 113 | notifyVisibleAreaChanged(); 114 | }); 115 | mVScrollView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { 116 | mScrollY = scrollY; 117 | notifyVisibleAreaChanged(); 118 | }); 119 | mHScrollView.setOnTouchListener((v, event) -> onTouchEvent(event)); 120 | mVScrollView.setOnTouchListener((v, event) -> onTouchEvent(event)); 121 | mScaleGestureDetector = new ScaleGestureDetector(context, this); 122 | 123 | // selection event 124 | mTextArea.setOnSelectionChangedListener(this::notifySelectionChanged); 125 | 126 | // for view model 127 | if (isViwer()) { 128 | mTextArea.setEnabled(false); 129 | mTextArea.setFocusable(false); 130 | mTextArea.setMovementMethod(null); 131 | mToolbar.setVisibility(GONE); 132 | } 133 | 134 | mIMM = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 135 | 136 | bindComponent(this); 137 | } 138 | 139 | private void bindComponent(ViewGroup root) { 140 | for (int i = 0; i < root.getChildCount(); i++) { 141 | View child = root.getChildAt(i); 142 | if (child instanceof Component) { 143 | addComponent((Component) child); 144 | } 145 | if (child instanceof ViewGroup) { 146 | bindComponent((ViewGroup) child); 147 | } 148 | } 149 | } 150 | 151 | private void addComponent(Component component) { 152 | Objects.requireNonNull(component); 153 | component.onAttachEditor(this); 154 | } 155 | 156 | private void notifyVisibleAreaChanged() { 157 | Rect visibleArea = new Rect(mScrollX, mScrollY, mScrollX + mWidth, mScrollY + mHeight); 158 | for (VisibleAreaListener l : mVisibleAreaListeners) { 159 | l.onVisibleAreaChanged(visibleArea, mVisibleArea); 160 | } 161 | mVisibleArea = visibleArea; 162 | } 163 | 164 | private void notifySelectionChanged(int start, int end) { 165 | for (SelectionListener l : mSelectionListeners) { 166 | l.onSelectionChanged(start, end, mSelStart, mSelEnd); 167 | } 168 | mSelStart = start; 169 | mSelEnd = end; 170 | } 171 | 172 | private void notifyScaleChanged(float factor) { 173 | mScaleFactor = Math.max(0.5f, Math.min(1.5f, mScaleFactor * factor)); 174 | for (ScaleListener l : mScaleListeners) { 175 | l.onScaleChanged(mScaleFactor); 176 | } 177 | } 178 | 179 | @Override 180 | public boolean onTouchEvent(MotionEvent event) { 181 | mScaleGestureDetector.onTouchEvent(event); 182 | return false; 183 | } 184 | 185 | @Override 186 | public boolean onScaleBegin(ScaleGestureDetector detector) { 187 | return true; 188 | } 189 | 190 | @Override 191 | public boolean onScale(ScaleGestureDetector detector) { 192 | notifyScaleChanged(detector.getScaleFactor()); 193 | return true; 194 | } 195 | 196 | @Override 197 | public void onScaleEnd(ScaleGestureDetector detector) { 198 | } 199 | 200 | @Override 201 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 202 | mWidth = w; 203 | mHeight = h; 204 | notifyVisibleAreaChanged(); 205 | } 206 | 207 | @NonNull 208 | @Override 209 | public Document getDocument() { 210 | return (Document) mTextArea.getText(); 211 | } 212 | 213 | @NonNull 214 | public Configuration getConfiguration() { 215 | return mConfiguration; 216 | } 217 | 218 | public void setConfiguration(@NonNull Configuration configuration) { 219 | Objects.requireNonNull(configuration); 220 | mConfiguration = configuration; 221 | } 222 | 223 | @NonNull 224 | @Override 225 | public ColorScheme getColorScheme() { 226 | return mConfiguration.getColorScheme(); 227 | } 228 | 229 | @NonNull 230 | public Language getLanguage() { 231 | return mLanguage; 232 | } 233 | 234 | @NonNull 235 | @Override 236 | public Component findComponentById(int id) { 237 | Component component = findViewById(id); 238 | Objects.requireNonNull(component, "Component not found, id: " + id); 239 | return component; 240 | } 241 | 242 | @Override 243 | public boolean isViwer() { 244 | return false; 245 | } 246 | 247 | @NonNull 248 | @Override 249 | public SelectionModel getSelectionModel() { 250 | return this; 251 | } 252 | 253 | @NonNull 254 | @Override 255 | public ScrollingModel getScrollingModel() { 256 | return this; 257 | } 258 | 259 | @NonNull 260 | @Override 261 | public ScaleModel getScacleModel() { 262 | return this; 263 | } 264 | 265 | @NonNull 266 | @Override 267 | public LayoutModel getLayoutModel() { 268 | return this; 269 | } 270 | 271 | @Override 272 | public void showSoftInput() { 273 | mIMM.showSoftInput(mTextArea, 0); 274 | } 275 | 276 | @Override 277 | public void hideSoftInput() { 278 | mIMM.hideSoftInputFromWindow(getWindowToken(), 0); 279 | } 280 | 281 | /////////////////////////////////////////////////////////////////////////// 282 | // ScrollingModel 283 | /////////////////////////////////////////////////////////////////////////// 284 | @Override 285 | public void scrollToCaret() { 286 | mTextArea.scrollTo(getScrollX(), getLineBaseline(getCurrentLine())); 287 | } 288 | 289 | @Override 290 | public void scrollTo(int x, int y) { 291 | mVScrollView.scrollTo(0, y); 292 | mHScrollView.scrollTo(x, 0); 293 | } 294 | 295 | @Override 296 | public void scrollBy(int x, int y) { 297 | mVScrollView.scrollBy(0, y); 298 | mHScrollView.scrollBy(x, 0); 299 | } 300 | 301 | @NonNull 302 | @Override 303 | public Rect getVisibleArea() { 304 | return mVisibleArea; 305 | } 306 | 307 | @Override 308 | public void addVisibleAreaListener(@NonNull VisibleAreaListener l) { 309 | Objects.requireNonNull(l); 310 | mVisibleAreaListeners.add(l); 311 | } 312 | 313 | @Override 314 | public void removeVisibleAreaListener(@NonNull VisibleAreaListener l) { 315 | Objects.requireNonNull(l); 316 | mVisibleAreaListeners.remove(l); 317 | } 318 | 319 | /////////////////////////////////////////////////////////////////////////// 320 | // SelectionModel 321 | /////////////////////////////////////////////////////////////////////////// 322 | @Override 323 | public int getSelectionStart() { 324 | return mTextArea.getSelectionStart(); 325 | } 326 | 327 | @Override 328 | public int getSelectionEnd() { 329 | return mTextArea.getSelectionEnd(); 330 | } 331 | 332 | @NonNull 333 | @Override 334 | public CharSequence getSelectionText() { 335 | return mTextArea.getText().subSequence(getSelectionStart(), getSelectionEnd()); 336 | } 337 | 338 | @Override 339 | public boolean hasSelection() { 340 | return mTextArea.hasSelection(); 341 | } 342 | 343 | @Override 344 | public void setSelection(int startOffset, int endOffset) { 345 | mTextArea.setSelection(startOffset, endOffset); 346 | } 347 | 348 | @Override 349 | public void removeSelection() { 350 | Selection.removeSelection(mTextArea.getText()); 351 | } 352 | 353 | @Override 354 | public void addSelectionListener(@NonNull SelectionListener l) { 355 | Objects.requireNonNull(l); 356 | mSelectionListeners.add(l); 357 | } 358 | 359 | @Override 360 | public void removeSelectionListener(@NonNull SelectionListener l) { 361 | Objects.requireNonNull(l); 362 | mSelectionListeners.remove(l); 363 | } 364 | 365 | @Override 366 | public void selectionLineAtCaret() { 367 | Layout layout = mTextArea.getLayout(); 368 | if (layout != null) { 369 | int line = mTextArea.getCurrentLine(); 370 | int startOffset = layout.getLineStart(line); 371 | int endOffset = layout.getLineEnd(line); 372 | mTextArea.setSelection(startOffset, endOffset); 373 | } 374 | } 375 | 376 | @Override 377 | public void moveUp() { 378 | focusIfNot(); 379 | mTextArea.caretMoveUp(); 380 | } 381 | 382 | @Override 383 | public void moveDown() { 384 | focusIfNot(); 385 | mTextArea.caretMoveDown(); 386 | } 387 | 388 | @Override 389 | public void moveLeft() { 390 | focusIfNot(); 391 | mTextArea.caretMoveLeft(); 392 | } 393 | 394 | @Override 395 | public void moveRight() { 396 | focusIfNot(); 397 | mTextArea.caretMoveRight(); 398 | } 399 | 400 | /** 401 | * If the {@link TextArea} is not focus, then request focus. 402 | */ 403 | private void focusIfNot() { 404 | if (!mTextArea.hasFocus()) { 405 | mTextArea.requestFocus(); 406 | } 407 | } 408 | 409 | /////////////////////////////////////////////////////////////////////////// 410 | // ScaleModel 411 | /////////////////////////////////////////////////////////////////////////// 412 | @Override 413 | public float getScaleFactor() { 414 | return mScaleFactor; 415 | } 416 | 417 | @Override 418 | public void addScaleListener(@NonNull ScaleListener l) { 419 | Objects.requireNonNull(l); 420 | mScaleListeners.add(l); 421 | } 422 | 423 | @Override 424 | public void removeScaleListener(@NonNull ScaleListener l) { 425 | Objects.requireNonNull(l); 426 | mScaleListeners.remove(l); 427 | } 428 | 429 | /////////////////////////////////////////////////////////////////////////// 430 | // Layout 431 | /////////////////////////////////////////////////////////////////////////// 432 | @Override 433 | public int getLineBaseline(int line) { 434 | return mTextArea.getLayout().getLineBaseline(line); 435 | } 436 | 437 | @Override 438 | public int getCurrentLine() { 439 | return mTextArea.getCurrentLine(); 440 | } 441 | 442 | @Override 443 | public int getTopLine() { 444 | return mTextArea.getTopLine(); 445 | } 446 | 447 | @Override 448 | public int getBottomLine() { 449 | return mTextArea.getBottomLine(); 450 | } 451 | 452 | @Override 453 | public int getLineCount() { 454 | return mTextArea.getLineCount(); 455 | } 456 | 457 | /////////////////////////////////////////////////////////////////////////// 458 | // Others 459 | /////////////////////////////////////////////////////////////////////////// 460 | public void setLangauge(@NonNull Language langauge) { 461 | Objects.requireNonNull(langauge); 462 | mLanguage = langauge; 463 | } 464 | 465 | public void cut() { 466 | mTextArea.cut(); 467 | } 468 | 469 | public void paste() { 470 | mTextArea.paste(); 471 | } 472 | 473 | public void undo() { 474 | mTextArea.undo(); 475 | } 476 | 477 | public void redo() { 478 | mTextArea.redo(); 479 | } 480 | 481 | public void replace() { 482 | mTextArea.replace(); 483 | } 484 | 485 | public void format() { 486 | mTextArea.format(); 487 | } 488 | 489 | public void setDocument(@NonNull Document document) { 490 | Objects.requireNonNull(document); 491 | mTextArea.setText(document); 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/CodeViewer.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | 9 | /** 10 | * This provide the same features like {@link CodeEditor}, but read-only. 11 | */ 12 | public class CodeViewer extends CodeEditor { 13 | public CodeViewer(@NonNull Context context) { 14 | super(context); 15 | } 16 | 17 | public CodeViewer(@NonNull Context context, @Nullable AttributeSet attrs) { 18 | super(context, attrs); 19 | } 20 | 21 | public CodeViewer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 22 | super(context, attrs, defStyleAttr); 23 | } 24 | 25 | @Override 26 | public boolean isViwer() { 27 | return true; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/configuration/ColorScheme.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.configuration; 2 | 3 | import androidx.annotation.ColorInt; 4 | 5 | public interface ColorScheme { 6 | @ColorInt 7 | int getBackgroundColor(); 8 | 9 | @ColorInt 10 | int getTextColor(); 11 | 12 | @ColorInt 13 | int getGutterColor(); 14 | 15 | @ColorInt 16 | int getGutterDividerColor(); 17 | 18 | @ColorInt 19 | int getGutterTextColor(); 20 | 21 | @ColorInt 22 | int getGutterActiveTextColor(); 23 | 24 | @ColorInt 25 | int getCursorLineColor(); 26 | 27 | @ColorInt 28 | int getSelectionColor(); 29 | 30 | @ColorInt 31 | int getCompletionMenuBackgroundColor(); 32 | 33 | @ColorInt 34 | int getIndentColor(); 35 | 36 | SyntaxColorScheme getSyntaxColorScheme(); 37 | } 38 | 39 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/configuration/SyntaxColorScheme.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.configuration; 2 | 3 | import androidx.annotation.ColorInt; 4 | 5 | public interface SyntaxColorScheme { 6 | @ColorInt 7 | int getAnnotationColor(); 8 | 9 | @ColorInt 10 | int getConstantColor(); 11 | 12 | @ColorInt 13 | int getCommentColor(); 14 | 15 | @ColorInt 16 | int getNumberColor(); 17 | 18 | @ColorInt 19 | int getOperatorColor(); 20 | 21 | @ColorInt 22 | int getKeywordColor(); 23 | 24 | @ColorInt 25 | int getTypeColor(); 26 | 27 | @ColorInt 28 | int getMethodColor(); 29 | 30 | @ColorInt 31 | int getStringColor(); 32 | } 33 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/document/Document.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.document; 2 | 3 | import android.text.Editable; 4 | import android.text.Spannable; 5 | import android.text.SpannableStringBuilder; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Objects; 12 | 13 | import io.ikws4.codeeditor.api.document.markup.Markup; 14 | 15 | public class Document extends SpannableStringBuilder { 16 | private List mMarkups; 17 | private boolean mUpdating = false; 18 | private int mStart; 19 | private int mEnd; 20 | 21 | public Document(CharSequence source) { 22 | super(source); 23 | mMarkups = new ArrayList<>(); 24 | } 25 | 26 | public void setMarkupSource(@NonNull List markups) { 27 | Objects.requireNonNull(markups); 28 | mMarkups = markups; 29 | notifyVisibleRangeChanged(mStart, mEnd, true); 30 | } 31 | 32 | public void setMarkup(Markup markup) { 33 | setSpan(markup, markup.getStart(), markup.getEnd(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 34 | } 35 | 36 | /** 37 | * Notify the visible area was changed, in order to refresh the markups 38 | */ 39 | public void notifyVisibleRangeChanged(int start, int end, boolean updateAll) { 40 | if (mUpdating) return; 41 | 42 | mUpdating = true; 43 | if (updateAll) { 44 | removeMarkups(mStart, mEnd); 45 | addMarkups(start, end); 46 | } else { 47 | if (start >= mStart) { 48 | if (start != 0) { 49 | removeMarkups(mStart, start); 50 | } 51 | addMarkups(mEnd, end); 52 | } else { 53 | addMarkups(start, mStart); 54 | removeMarkups(end, mEnd); 55 | } 56 | } 57 | mStart = start; 58 | mEnd = end; 59 | mUpdating = false; 60 | } 61 | 62 | public void notifyTextChanged(int start, int offset) { 63 | if (mUpdating) return; 64 | 65 | mUpdating = true; 66 | shiftMarkups(start, offset); 67 | mUpdating = false; 68 | } 69 | 70 | /** 71 | * Shift markups by given start and offset. 72 | */ 73 | private void shiftMarkups(int start, int offset) { 74 | for (int i = getMarkupIndex(start); i < mMarkups.size(); i++) { 75 | mMarkups.get(i).shift(offset); 76 | } 77 | } 78 | 79 | /** 80 | * Remove markups by given range (start, end) 81 | */ 82 | private void removeMarkups(int start, int end) { 83 | Markup[] markups = getSpans(start, end, Markup.class); 84 | for (Markup markup : markups) { 85 | removeSpan(markup); 86 | } 87 | } 88 | 89 | /** 90 | * Add markups by given range (start, end) 91 | */ 92 | private void addMarkups(int start, int end) { 93 | int startIndex = getMarkupIndex(start); 94 | int endIndex = Math.min(mMarkups.size() - 1, getMarkupIndex(end)); 95 | for (int i = startIndex; i <= endIndex; i++) { 96 | Markup markup = mMarkups.get(i); 97 | setMarkup(markup); 98 | } 99 | } 100 | 101 | private int getMarkupIndex(int start) { 102 | int l = 0, r = mMarkups.size(), m; 103 | while (l < r) { 104 | m = l + (r - l) / 2; 105 | if (mMarkups.get(m).getStart() < start) { 106 | l = m + 1; 107 | } else { 108 | r = m; 109 | } 110 | } 111 | return l; 112 | } 113 | 114 | public static class Factory extends Editable.Factory { 115 | private static final Document.Factory sInstance = new Document.Factory(); 116 | 117 | public static Document.Factory getInstance() { 118 | return sInstance; 119 | } 120 | 121 | @Override 122 | public Editable newEditable(CharSequence source) { 123 | return new Document(source); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/document/markup/Markup.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.document.markup; 2 | 3 | public interface Markup { 4 | int getStart(); 5 | 6 | int getEnd(); 7 | 8 | void shift(int offset); 9 | } 10 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/document/markup/ReplacedMarkup.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.document.markup; 2 | 3 | import android.text.NoCopySpan; 4 | import android.view.KeyEvent; 5 | 6 | import io.ikws4.codeeditor.component.TextArea; 7 | 8 | public class ReplacedMarkup implements NoCopySpan, Markup { 9 | private final char[] mText; 10 | private int mStart; 11 | private int mEnd; 12 | 13 | public ReplacedMarkup(char[] text, int start, int end) { 14 | mText = text; 15 | mStart = start; 16 | mEnd = end; 17 | } 18 | 19 | public char[] getText() { 20 | return mText; 21 | } 22 | 23 | @Override 24 | public int getStart() { 25 | return mStart; 26 | } 27 | 28 | @Override 29 | public int getEnd() { 30 | return mEnd; 31 | } 32 | 33 | @Override 34 | public void shift(int offset) { 35 | mStart += offset; 36 | mEnd += offset; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/document/markup/SyntaxMarkup.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.document.markup; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.graphics.Typeface; 5 | import android.text.TextPaint; 6 | import android.text.style.CharacterStyle; 7 | 8 | import androidx.annotation.ColorInt; 9 | 10 | /** 11 | * A markup that support {@link Typeface} and foreground color 12 | */ 13 | public class SyntaxMarkup extends CharacterStyle implements Markup { 14 | private final int mStyle; 15 | private final int mColor; 16 | private int mStart; 17 | private int mEnd; 18 | 19 | public SyntaxMarkup(@ColorInt int color, int start, int end) { 20 | this(Typeface.NORMAL, color, start, end); 21 | } 22 | 23 | /** 24 | * @param style An integer constant describing the style for this span. Examples 25 | * include bold, italic, and normal. Values are constants defined 26 | * in {@link Typeface}. 27 | * @param color text color 28 | */ 29 | public SyntaxMarkup(int style, @ColorInt int color, int start, int end) { 30 | mStyle = style; 31 | mColor = color; 32 | mStart = start; 33 | mEnd = end; 34 | } 35 | 36 | @Override 37 | public int getStart() { 38 | return mStart; 39 | } 40 | 41 | @Override 42 | public int getEnd() { 43 | return mEnd; 44 | } 45 | 46 | @Override 47 | public void shift(int offset) { 48 | mStart += offset; 49 | mEnd += offset; 50 | } 51 | 52 | @Override 53 | public void updateDrawState(TextPaint paint) { 54 | apply(paint, mStyle, mColor); 55 | } 56 | 57 | @SuppressLint("WrongConstant") 58 | private static void apply(TextPaint paint, int style, int color) { 59 | 60 | int oldStyle; 61 | 62 | Typeface old = paint.getTypeface(); 63 | if (old == null) { 64 | oldStyle = 0; 65 | } else { 66 | oldStyle = old.getStyle(); 67 | } 68 | 69 | int want = oldStyle | style; 70 | 71 | Typeface tf; 72 | if (old == null) { 73 | tf = Typeface.defaultFromStyle(want); 74 | } else { 75 | tf = Typeface.create(old, want); 76 | } 77 | 78 | int fake = want & ~tf.getStyle(); 79 | 80 | if ((fake & Typeface.BOLD) != 0) { 81 | paint.setFakeBoldText(true); 82 | } 83 | 84 | if ((fake & Typeface.ITALIC) != 0) { 85 | paint.setTextSkewX(-0.25f); 86 | } 87 | 88 | paint.setTypeface(tf); 89 | paint.setColor(color); 90 | } 91 | } -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/document/markup/TabMarkup.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.document.markup; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | import android.text.Layout; 6 | import android.text.TextPaint; 7 | import android.text.style.LeadingMarginSpan; 8 | 9 | import androidx.annotation.ColorInt; 10 | 11 | public class TabMarkup extends ReplacedMarkup implements LeadingMarginSpan { 12 | private final String mTab; 13 | private final int mColor; 14 | private float[] mPoints; 15 | 16 | public TabMarkup(String tab, @ColorInt int color, int start, int end) { 17 | super(new char[0], start, end); 18 | mTab = tab; 19 | mColor = color; 20 | } 21 | 22 | @Override 23 | public int getLeadingMargin(boolean first) { 24 | return 0; 25 | } 26 | 27 | @Override 28 | public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { 29 | if (mTab.length() <= 0) return; 30 | if (mPoints == null) { 31 | float charWidth = p.measureText(String.valueOf(mTab.charAt(0))); 32 | int centerY = top + (bottom - top) / 2; 33 | mPoints = new float[2 * mTab.length()]; 34 | for (int i = 0; i < mPoints.length; i += 2) { 35 | mPoints[i] = charWidth * (i + 1) / 2; 36 | mPoints[i + 1] = centerY; 37 | } 38 | 39 | } 40 | TextPaint paint = new TextPaint(p); 41 | paint.setColor(mColor); 42 | paint.setAntiAlias(true); 43 | paint.setStrokeWidth(5); 44 | paint.setStrokeCap(Paint.Cap.ROUND); 45 | c.drawPoints(mPoints, paint); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/editor/Editor.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.editor; 2 | 3 | import androidx.annotation.IdRes; 4 | import androidx.annotation.IntegerRes; 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | import java.nio.charset.Charset; 9 | 10 | import javax.annotation.Nonnull; 11 | 12 | import io.ikws4.codeeditor.api.configuration.ColorScheme; 13 | import io.ikws4.codeeditor.api.document.Document; 14 | import io.ikws4.codeeditor.api.editor.component.Component; 15 | import io.ikws4.codeeditor.api.language.Language; 16 | import io.ikws4.codeeditor.configuration.Configuration; 17 | 18 | public interface Editor { 19 | @NonNull 20 | Document getDocument(); 21 | 22 | @NonNull 23 | Configuration getConfiguration(); 24 | 25 | @NonNull 26 | ColorScheme getColorScheme(); 27 | 28 | @NonNull 29 | Language getLanguage(); 30 | 31 | @Nonnull 32 | Component findComponentById(@IdRes int id); 33 | 34 | /** 35 | * Returns the value indicating whether the editor operates in viewer mode, with 36 | * all modification actions disabled. 37 | * 38 | * @return true if the editor works as a viewer, false otherwise 39 | */ 40 | boolean isViwer(); 41 | 42 | /** 43 | * Returns the selection model for the editor, which can be used to select ranges of text in 44 | * the document and retrieve information about the selection. 45 | * 46 | * @return the selection model instance. 47 | */ 48 | @NonNull 49 | SelectionModel getSelectionModel(); 50 | 51 | /** 52 | * Returns the scrolling model for the document, which can be used to scroll the document 53 | * and retrieve information about the current position of the scrollbars. 54 | * 55 | * @return the scrolling model instance. 56 | */ 57 | @NonNull 58 | ScrollingModel getScrollingModel(); 59 | 60 | @NonNull 61 | ScaleModel getScacleModel(); 62 | 63 | @NonNull 64 | LayoutModel getLayoutModel(); 65 | 66 | void hideSoftInput(); 67 | 68 | void showSoftInput(); 69 | } -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/editor/LayoutModel.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.editor; 2 | 3 | public interface LayoutModel { 4 | /** 5 | * Return the vertical position of the baseline of the specified line. 6 | */ 7 | int getLineBaseline(int line); 8 | 9 | /** 10 | * Return the current line of the layout. 11 | */ 12 | int getCurrentLine(); 13 | 14 | /** 15 | * Return the top visible line of the layout. 16 | */ 17 | int getTopLine(); 18 | 19 | /** 20 | * Return the bottom visible line of the layout. 21 | */ 22 | int getBottomLine(); 23 | 24 | /** 25 | * Return the number of lines of text in this layout. 26 | */ 27 | int getLineCount(); 28 | } 29 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/editor/ScaleModel.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.editor; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import io.ikws4.codeeditor.api.editor.listener.ScaleListener; 6 | 7 | public interface ScaleModel { 8 | float getScaleFactor(); 9 | 10 | void addScaleListener(@NonNull ScaleListener l); 11 | 12 | void removeScaleListener(@NonNull ScaleListener l); 13 | } -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/editor/ScrollingModel.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.editor; 2 | 3 | import android.graphics.Rect; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import io.ikws4.codeeditor.api.editor.listener.VisibleAreaListener; 8 | 9 | public interface ScrollingModel { 10 | void scrollToCaret(); 11 | 12 | void scrollTo(int x, int y); 13 | 14 | void scrollBy(int x, int y); 15 | 16 | @NonNull 17 | Rect getVisibleArea(); 18 | 19 | int getScrollX(); 20 | 21 | int getScrollY(); 22 | 23 | void addVisibleAreaListener(@NonNull VisibleAreaListener l); 24 | 25 | void removeVisibleAreaListener(@NonNull VisibleAreaListener l); 26 | } 27 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/editor/SelectionModel.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.editor; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import io.ikws4.codeeditor.api.editor.listener.SelectionListener; 6 | 7 | public interface SelectionModel { 8 | int getSelectionStart(); 9 | 10 | int getSelectionEnd(); 11 | 12 | @NonNull 13 | CharSequence getSelectionText(); 14 | 15 | boolean hasSelection(); 16 | 17 | void setSelection(int startOffset, int endOffset); 18 | 19 | void removeSelection(); 20 | 21 | void addSelectionListener(@NonNull SelectionListener l); 22 | 23 | void removeSelectionListener(@NonNull SelectionListener l); 24 | 25 | void selectionLineAtCaret(); 26 | 27 | void moveUp(); 28 | 29 | void moveDown(); 30 | 31 | void moveLeft(); 32 | 33 | void moveRight(); 34 | } 35 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/editor/component/Component.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.editor.component; 2 | 3 | import io.ikws4.codeeditor.api.editor.Editor; 4 | 5 | public interface Component { 6 | void onAttachEditor(Editor editor); 7 | 8 | int getComponentWidth(); 9 | 10 | int getComponentHeight(); 11 | } 12 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/editor/listener/ScaleListener.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.editor.listener; 2 | 3 | public interface ScaleListener { 4 | void onScaleChanged(float factor); 5 | } 6 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/editor/listener/SelectionListener.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.editor.listener; 2 | 3 | public interface SelectionListener { 4 | void onSelectionChanged(int start, int end, int oldStart, int oldEnd); 5 | } 6 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/editor/listener/VisibleAreaListener.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.editor.listener; 2 | 3 | import android.graphics.Rect; 4 | 5 | public interface VisibleAreaListener { 6 | void onVisibleAreaChanged(Rect rect, Rect oldRect); 7 | } 8 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/language/Language.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.language; 2 | 3 | public interface Language { 4 | /** 5 | * @return the language name 6 | */ 7 | String getName(); 8 | 9 | LanguageParser getParser(); 10 | 11 | LanguageSuggestionProvider getSuggestionProvider(); 12 | 13 | LanguageStyler getStyler(); 14 | } 15 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/language/LanguageParser.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.language; 2 | 3 | public interface LanguageParser { 4 | 5 | /** 6 | * 7 | * @param name the language that need parsed 8 | * @param source source code 9 | * @return {@link ParseResult} 10 | */ 11 | ParseResult parse(String name, String source); 12 | } 13 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/language/LanguageStyler.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.language; 2 | 3 | import java.util.List; 4 | 5 | import io.ikws4.codeeditor.api.configuration.SyntaxColorScheme; 6 | import io.ikws4.codeeditor.api.document.markup.Markup; 7 | 8 | public interface LanguageStyler { 9 | 10 | void editSyntaxTree(int startByte, int oldEndByte, int newEndByte, int startRow, int startColumn, int oldEndRow, int oldEndColumn, int newEndRow, int newEndColumn); 11 | 12 | int getIndentLevel(int line, int prevnonblankLine); 13 | 14 | String format(String source); 15 | 16 | List process(String source, SyntaxColorScheme scheme); 17 | } 18 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/language/LanguageSuggestionProvider.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.language; 2 | 3 | import java.util.List; 4 | 5 | public interface LanguageSuggestionProvider { 6 | List getAll(); 7 | 8 | void process(int line, String text); 9 | 10 | void delete(int line); 11 | 12 | void clear(); 13 | } 14 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/language/ParseException.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.language; 2 | 3 | public class ParseException extends RuntimeException { 4 | public final String message; 5 | public final int line; 6 | public final int column; 7 | 8 | public ParseException(String message, int line, int column) { 9 | super(message); 10 | this.message = message; 11 | this.line = line; 12 | this.column = column; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/language/ParseResult.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.language; 2 | 3 | /** 4 | * A wrap class for {@link ParseException} 5 | */ 6 | public class ParseResult { 7 | private final ParseException mParseException; 8 | 9 | public ParseResult(ParseException parseException) { 10 | mParseException = parseException; 11 | } 12 | 13 | public String getMessage() { 14 | return mParseException.message; 15 | } 16 | 17 | public int getLine() { 18 | return mParseException.line; 19 | } 20 | 21 | public int getColumn() { 22 | return mParseException.column; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/api/language/Suggestion.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.api.language; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | public class Suggestion { 6 | private final Type mType; 7 | private final String mText; 8 | private final String mReturnType; 9 | 10 | public Suggestion(Type type, String text) { 11 | this(type, text, ""); 12 | } 13 | 14 | public Suggestion(Type type, String text, String returnType) { 15 | mType = type; 16 | mText = text; 17 | mReturnType = returnType; 18 | } 19 | 20 | public Type getType() { 21 | return mType; 22 | } 23 | 24 | public String getText() { 25 | return mText; 26 | } 27 | 28 | public String getReturnType() { 29 | return mReturnType; 30 | } 31 | 32 | @NonNull 33 | @Override 34 | public String toString() { 35 | return mText; 36 | } 37 | 38 | public enum Type { 39 | KEYWORD("k"), 40 | 41 | IDENTIFIER("w"), 42 | 43 | FUNCTION("f"), 44 | 45 | FIELD("v"), 46 | 47 | TYPE("t"); 48 | 49 | private final String mText; 50 | 51 | Type(String text) { 52 | mText = text; 53 | } 54 | 55 | @NonNull 56 | @Override 57 | public String toString() { 58 | return mText; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/component/Gutter.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.component; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.Rect; 7 | import android.graphics.Typeface; 8 | import android.util.AttributeSet; 9 | import android.util.TypedValue; 10 | import android.view.View; 11 | 12 | import androidx.annotation.Nullable; 13 | 14 | import io.ikws4.codeeditor.api.configuration.ColorScheme; 15 | import io.ikws4.codeeditor.api.editor.Editor; 16 | import io.ikws4.codeeditor.api.editor.LayoutModel; 17 | import io.ikws4.codeeditor.api.editor.component.Component; 18 | import io.ikws4.codeeditor.api.editor.listener.ScaleListener; 19 | import io.ikws4.codeeditor.api.editor.listener.VisibleAreaListener; 20 | 21 | public class Gutter extends View implements Component, VisibleAreaListener, ScaleListener { 22 | private Editor mEditor; 23 | private int mScrollY; 24 | private final Paint mTextPaint = new Paint(); 25 | private final Paint mActiveTextPaint = new Paint(); 26 | 27 | public Gutter(Context context) { 28 | this(context, null); 29 | } 30 | 31 | public Gutter(Context context, @Nullable AttributeSet attrs) { 32 | this(context, attrs, 0); 33 | } 34 | 35 | public Gutter(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 36 | super(context, attrs, defStyleAttr); 37 | } 38 | 39 | @Override 40 | public void onAttachEditor(Editor editor) { 41 | mEditor = editor; 42 | 43 | ColorScheme colorScheme = editor.getConfiguration().getColorScheme(); 44 | 45 | setBackgroundColor(colorScheme.getGutterColor()); 46 | setTextSize(mEditor.getConfiguration().getFontSize() * mEditor.getScacleModel().getScaleFactor()); 47 | 48 | mTextPaint.setColor(colorScheme.getGutterTextColor()); 49 | mTextPaint.setTypeface(Typeface.MONOSPACE); 50 | mTextPaint.setAntiAlias(true); 51 | mTextPaint.setTextAlign(Paint.Align.RIGHT); 52 | 53 | mActiveTextPaint.setColor(colorScheme.getGutterActiveTextColor()); 54 | mActiveTextPaint.setTypeface(Typeface.MONOSPACE); 55 | mActiveTextPaint.setAntiAlias(true); 56 | mActiveTextPaint.setTextAlign(Paint.Align.RIGHT); 57 | 58 | editor.getScrollingModel().addVisibleAreaListener(this); 59 | editor.getScacleModel().addScaleListener(this); 60 | } 61 | 62 | @Override 63 | public int getComponentWidth() { 64 | return getWidth(); 65 | } 66 | 67 | @Override 68 | public int getComponentHeight() { 69 | return getHeight(); 70 | } 71 | 72 | @Override 73 | public void onVisibleAreaChanged(Rect rect, Rect oldRect) { 74 | mScrollY = rect.top; 75 | } 76 | 77 | @Override 78 | public void onScaleChanged(float factor) { 79 | setTextSize(mEditor.getConfiguration().getFontSize() * factor); 80 | } 81 | 82 | @Override 83 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 84 | int w = View.resolveSize(getWidth(), widthMeasureSpec); 85 | int h = View.resolveSize(getHeight(), heightMeasureSpec); 86 | setMeasuredDimension(w, h); 87 | } 88 | 89 | @Override 90 | protected void onDraw(Canvas canvas) { 91 | measureGutterWidth(); 92 | drawLineNumber(canvas); 93 | } 94 | 95 | private void drawLineNumber(Canvas canvas) { 96 | LayoutModel layout = mEditor.getLayoutModel(); 97 | int topLine = layout.getTopLine(); 98 | int bottomLine = layout.getBottomLine(); 99 | int currentLine = layout.getCurrentLine(); 100 | 101 | for (int line = topLine; line <= bottomLine; line++) { 102 | String realLine = String.valueOf(line + 1); 103 | float x = getWidth() - getPaddingRight(); 104 | float y = mEditor.getLayoutModel().getLineBaseline(line) - mScrollY; 105 | canvas.drawText(realLine, x, y, line == currentLine && !mEditor.isViwer() ? mActiveTextPaint : mTextPaint); 106 | } 107 | } 108 | 109 | private void measureGutterWidth() { 110 | int lineCount = mEditor.getLayoutModel().getLineCount(); 111 | String lineCountText = String.valueOf(lineCount); 112 | 113 | // When the number digits are not equal, we need relayout. 114 | // eg. when linenumber from 99 become to 100. 115 | if (Math.log10(lineCount) != Math.log10(lineCount - 1)) { 116 | requestLayout(); 117 | } 118 | 119 | getLayoutParams().width = (int) (mTextPaint.measureText(lineCountText)); 120 | getLayoutParams().width += getPaddingLeft() + getPaddingRight(); 121 | } 122 | 123 | private void setTextSize(float size) { 124 | float s = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, size, getResources().getDisplayMetrics()); 125 | mTextPaint.setTextSize(s); 126 | mActiveTextPaint.setTextSize(s); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/component/Toolbar.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.component; 2 | 3 | import android.content.Context; 4 | import android.graphics.Rect; 5 | import android.util.AttributeSet; 6 | import android.view.LayoutInflater; 7 | import android.widget.FrameLayout; 8 | 9 | import androidx.annotation.Nullable; 10 | 11 | import io.ikws4.codeeditor.R; 12 | import io.ikws4.codeeditor.api.editor.Editor; 13 | import io.ikws4.codeeditor.api.editor.component.Component; 14 | import io.ikws4.codeeditor.api.editor.listener.VisibleAreaListener; 15 | import io.ikws4.codeeditor.widget.KeyButton; 16 | 17 | /** 18 | * Provide such a arrow keys, and Tab key. 19 | */ 20 | public class Toolbar extends FrameLayout implements Component, VisibleAreaListener { 21 | 22 | private KeyButton mKeyboardToggleButton; 23 | private boolean mKeyboardShowing; 24 | private int mEditorHeight; 25 | 26 | public Toolbar(Context context) { 27 | this(context, null); 28 | } 29 | 30 | public Toolbar(Context context, @Nullable AttributeSet attrs) { 31 | this(context, attrs, 0); 32 | } 33 | 34 | public Toolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 35 | super(context, attrs, defStyleAttr); 36 | LayoutInflater.from(context).inflate(R.layout.toolbar, this, true); 37 | } 38 | 39 | @Override 40 | public void onAttachEditor(Editor editor) { 41 | setBackgroundColor(editor.getColorScheme().getBackgroundColor()); 42 | 43 | KeyButton tab = findViewById(R.id.tab); 44 | KeyButton up = findViewById(R.id.up); 45 | KeyButton down = findViewById(R.id.down); 46 | KeyButton left = findViewById(R.id.left); 47 | KeyButton right = findViewById(R.id.right); 48 | mKeyboardToggleButton = findViewById(R.id.keyboard_toggle); 49 | 50 | up.setOnPressedListener(editor.getSelectionModel()::moveUp); 51 | down.setOnPressedListener(editor.getSelectionModel()::moveDown); 52 | left.setOnPressedListener(editor.getSelectionModel()::moveLeft); 53 | right.setOnPressedListener(editor.getSelectionModel()::moveRight); 54 | mKeyboardToggleButton.setOnPressedListener(() -> { 55 | if (mKeyboardShowing) { 56 | editor.hideSoftInput(); 57 | } else { 58 | editor.showSoftInput(); 59 | } 60 | }); 61 | 62 | editor.getScrollingModel().addVisibleAreaListener(this); 63 | } 64 | 65 | @Override 66 | public int getComponentWidth() { 67 | return getWidth(); 68 | } 69 | 70 | @Override 71 | public int getComponentHeight() { 72 | return getHeight(); 73 | } 74 | 75 | @Override 76 | public void onVisibleAreaChanged(Rect rect, Rect oldRect) { 77 | int diff = mEditorHeight - rect.height(); 78 | if (Math.abs(diff) > 100) { 79 | boolean showing = diff > 100; 80 | if (showing) { 81 | mKeyboardShowing = true; 82 | mKeyboardToggleButton.setImageResource(R.drawable.ic_keyboard_hide); 83 | } else { 84 | mKeyboardShowing = false; 85 | mKeyboardToggleButton.setImageResource(R.drawable.ic_keyboard); 86 | } 87 | mEditorHeight = rect.height(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/configuration/Configuration.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.configuration; 2 | 3 | 4 | import io.ikws4.codeeditor.api.configuration.ColorScheme; 5 | import io.ikws4.codeeditor.configuration.colorscheme.ColorSchemes; 6 | import io.ikws4.codeeditor.configuration.indent.Indentation; 7 | 8 | public class Configuration { 9 | private ColorScheme colorScheme = ColorSchemes.NORD; 10 | 11 | private float fontSize = 14.0f; 12 | 13 | /** 14 | * When on, lines longer than the width of the window will wrap and 15 | * displaying continues on the next line. 16 | */ 17 | private boolean wrap = false; 18 | 19 | /** 20 | * Complete the part of keywords or line that has been typed. 21 | * This is useful if your are using complicated keywords. 22 | */ 23 | private boolean completion = true; 24 | 25 | //TODO: pinchZoom doc 26 | private boolean pinchZoom = true; 27 | 28 | /** 29 | * Draw the line number in front of each line. 30 | */ 31 | private boolean number = true; 32 | 33 | // TODO: cursorLine doc with hl-CursorLine 34 | /** 35 | * When on, Highlight the screen line of the cursor. 36 | */ 37 | private boolean cursorLine = true; 38 | 39 | // TODO: highlightDelimiter doc 40 | private boolean highlightDelimiter = true; 41 | 42 | /** 43 | * Copy indent from current line when starting a new line. 44 | */ 45 | private boolean autoIndent = true; 46 | 47 | private Indentation indentation = Indentation.WHITE_SPACE_4; 48 | 49 | public ColorScheme getColorScheme() { 50 | return colorScheme; 51 | } 52 | 53 | public void setColorScheme(ColorScheme colorScheme) { 54 | this.colorScheme = colorScheme; 55 | } 56 | 57 | public float getFontSize() { 58 | return fontSize; 59 | } 60 | 61 | public void setFontSize(float fontSize) { 62 | this.fontSize = fontSize; 63 | } 64 | 65 | public boolean isWrap() { 66 | return wrap; 67 | } 68 | 69 | public void setWrap(boolean wrap) { 70 | this.wrap = wrap; 71 | } 72 | 73 | public boolean isCompletion() { 74 | return completion; 75 | } 76 | 77 | public void setCompletion(boolean completion) { 78 | this.completion = completion; 79 | } 80 | 81 | public boolean isPinchZoom() { 82 | return pinchZoom; 83 | } 84 | 85 | public void setPinchZoom(boolean pinchZoom) { 86 | this.pinchZoom = pinchZoom; 87 | } 88 | 89 | public boolean isNumber() { 90 | return number; 91 | } 92 | 93 | public void setNumber(boolean number) { 94 | this.number = number; 95 | } 96 | 97 | public boolean isCursorLine() { 98 | return cursorLine; 99 | } 100 | 101 | public void setCursorLine(boolean cursorLine) { 102 | this.cursorLine = cursorLine; 103 | } 104 | 105 | public boolean isHighlightDelimiter() { 106 | return highlightDelimiter; 107 | } 108 | 109 | public void setHighlightDelimiter(boolean highlightDelimiter) { 110 | this.highlightDelimiter = highlightDelimiter; 111 | } 112 | 113 | public boolean isAutoIndent() { 114 | return autoIndent; 115 | } 116 | 117 | public void setAutoIndent(boolean autoIndent) { 118 | this.autoIndent = autoIndent; 119 | } 120 | 121 | public Indentation getIndentation() { 122 | return indentation; 123 | } 124 | 125 | public void setIndentation(Indentation indentation) { 126 | this.indentation = indentation; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/configuration/colorscheme/ColorSchemes.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.configuration.colorscheme; 2 | 3 | public class ColorSchemes { 4 | public static final DarculaColorScheme DARCULA = new DarculaColorScheme(); 5 | 6 | public static final NordColorScheme NORD = new NordColorScheme(); 7 | } -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/configuration/colorscheme/DarculaColorScheme.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.configuration.colorscheme; 2 | 3 | import io.ikws4.codeeditor.api.configuration.ColorScheme; 4 | import io.ikws4.codeeditor.api.configuration.SyntaxColorScheme; 5 | 6 | class DarculaColorScheme implements ColorScheme { 7 | @Override 8 | public int getBackgroundColor() { 9 | return 0xFF303030; 10 | } 11 | 12 | @Override 13 | public int getTextColor() { 14 | return 0xFFABB7C5; 15 | } 16 | 17 | @Override 18 | public int getGutterColor() { 19 | return 0xFF313335; 20 | } 21 | 22 | @Override 23 | public int getGutterDividerColor() { 24 | return 0xFF555555; 25 | } 26 | 27 | @Override 28 | public int getGutterTextColor() { 29 | return 0xFF616366; 30 | } 31 | 32 | @Override 33 | public int getGutterActiveTextColor() { 34 | return 0xFFA4A3A3; 35 | } 36 | 37 | @Override 38 | public int getCursorLineColor() { 39 | return 0xFF3A3A3A; 40 | } 41 | 42 | @Override 43 | public int getSelectionColor() { 44 | return 0xFF28427F; 45 | } 46 | 47 | @Override 48 | public int getCompletionMenuBackgroundColor() { 49 | // FIXME: this color need make constract with #getBackgroundColor 50 | return 0xFF303030; 51 | } 52 | 53 | @Override 54 | public int getIndentColor() { 55 | return 0xFF606060; 56 | } 57 | 58 | @Override 59 | public SyntaxColorScheme getSyntaxColorScheme() { 60 | return new SyntaxColorScheme() { 61 | @Override 62 | public int getAnnotationColor() { 63 | return 0xFFBABABA; 64 | } 65 | 66 | @Override 67 | public int getCommentColor() { 68 | return 0xFF66747B; 69 | } 70 | 71 | @Override 72 | public int getConstantColor() { 73 | return 0xFFEC7600; 74 | } 75 | 76 | @Override 77 | public int getNumberColor() { 78 | return 0xFF6897BB; 79 | } 80 | 81 | @Override 82 | public int getOperatorColor() { 83 | return 0xFFE8E2B7; 84 | } 85 | 86 | @Override 87 | public int getKeywordColor() { 88 | return 0xFFEC7600; 89 | } 90 | 91 | @Override 92 | public int getTypeColor() { 93 | return 0xFFEC7600; 94 | } 95 | 96 | @Override 97 | public int getMethodColor() { 98 | return 0xFFFEC76C; 99 | } 100 | 101 | @Override 102 | public int getStringColor() { 103 | return 0xFF6E875A; 104 | } 105 | }; 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/configuration/colorscheme/NordColorScheme.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.configuration.colorscheme; 2 | 3 | import io.ikws4.codeeditor.api.configuration.ColorScheme; 4 | import io.ikws4.codeeditor.api.configuration.SyntaxColorScheme; 5 | 6 | class NordColorScheme implements ColorScheme { 7 | @Override 8 | public int getBackgroundColor() { 9 | return 0xFF2E3440; 10 | } 11 | 12 | @Override 13 | public int getTextColor() { 14 | return 0xFFD8DEE9; 15 | } 16 | 17 | @Override 18 | public int getGutterColor() { 19 | return 0xFF2E3440; 20 | } 21 | 22 | @Override 23 | public int getGutterDividerColor() { 24 | return 0xFF2E3440; 25 | } 26 | 27 | @Override 28 | public int getGutterTextColor() { 29 | return 0xFF4C566A; 30 | } 31 | 32 | @Override 33 | public int getGutterActiveTextColor() { 34 | return 0xFFD8DEE9; 35 | } 36 | 37 | @Override 38 | public int getCursorLineColor() { 39 | return 0xFF3B4252; 40 | } 41 | 42 | @Override 43 | public int getSelectionColor() { 44 | return 0xFF3B4252; 45 | } 46 | 47 | @Override 48 | public int getCompletionMenuBackgroundColor() { 49 | return 0xFF4C566A; 50 | } 51 | 52 | @Override 53 | public int getIndentColor() { 54 | return 0xFF4C566A; 55 | } 56 | 57 | public SyntaxColorScheme getSyntaxColorScheme() { 58 | return new SyntaxColorScheme() { 59 | @Override 60 | public int getAnnotationColor() { 61 | return 0XFFD08770; 62 | } 63 | 64 | @Override 65 | public int getCommentColor() { 66 | return 0xFF4C566A; 67 | } 68 | 69 | @Override 70 | public int getNumberColor() { 71 | return 0xFFB48EAD; 72 | } 73 | 74 | @Override 75 | public int getOperatorColor() { 76 | return 0xFF81A1C1; 77 | } 78 | 79 | @Override 80 | public int getKeywordColor() { 81 | return 0xFF81A1C1; 82 | } 83 | 84 | @Override 85 | public int getTypeColor() { 86 | return 0xFF8FBCBB; 87 | } 88 | 89 | @Override 90 | public int getConstantColor() { 91 | return 0xFFD8DEE9; 92 | } 93 | 94 | @Override 95 | public int getMethodColor() { 96 | return 0xFF88C0D0; 97 | } 98 | 99 | @Override 100 | public int getStringColor() { 101 | return 0xFFA3BE8C; 102 | } 103 | }; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/configuration/indent/Indentation.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.configuration.indent; 2 | 3 | public class Indentation { 4 | 5 | public static final Indentation TAB = new Indentation(Whitespace.TAB.toString(), 1); 6 | 7 | public static final Indentation WHITE_SPACE_2 = new Indentation(Whitespace.SPACE.toString(), 2); 8 | 9 | public static final Indentation WHITE_SPACE_4 = new Indentation(Whitespace.SPACE.toString(), 4); 10 | 11 | public static final Indentation WHITE_SPACE_8 = new Indentation(Whitespace.SPACE.toString(), 8); 12 | 13 | private final String mIndentationString; 14 | 15 | private Indentation(String indentationString, int length) { 16 | StringBuilder builder = new StringBuilder(); 17 | for (int i = 0; i < length; i++) { 18 | builder.append(indentationString); 19 | } 20 | mIndentationString = builder.toString(); 21 | } 22 | 23 | /** 24 | * Returns the string that needs to be appended at the beginning of a line, 25 | * with the result that the line is indented by the given level. 26 | * 27 | * @param level The identation level. 28 | * 29 | * @return The indentation string. 30 | */ 31 | public String get(int level) { 32 | StringBuilder builder = new StringBuilder(); 33 | for (int i = 0; i < level; i++) { 34 | builder.append(mIndentationString); 35 | } 36 | return builder.toString(); 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/configuration/indent/Whitespace.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.configuration.indent; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | enum Whitespace { 6 | 7 | /** 8 | * A single space character. 9 | */ 10 | SPACE(" "), 11 | 12 | /** 13 | * A single tab character. 14 | */ 15 | TAB("\t"); 16 | 17 | private final String mWhitespaceString; 18 | 19 | Whitespace(String whitespaceString) { 20 | mWhitespaceString = whitespaceString; 21 | } 22 | 23 | @NonNull 24 | @Override 25 | public String toString() { 26 | return mWhitespaceString; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/language/TSHighlightType.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.language; 2 | 3 | public enum TSHighlightType { 4 | Annotation, 5 | Attribute, 6 | Boolean, 7 | Character, 8 | Comment, 9 | Conditional, 10 | Constant, 11 | ConstBuiltin, 12 | ConstMacro, 13 | Constructor, 14 | Error, 15 | Exception, 16 | Field, 17 | Float, 18 | Function, 19 | FuncBuiltin, 20 | FuncMarco, 21 | Include, 22 | Keyword, 23 | KeywordFunction, 24 | KeywordOperator, 25 | Label, 26 | Method, 27 | Namespace, 28 | None, 29 | Number, 30 | Operator, 31 | Parameter, 32 | ParameterReference, 33 | Property, 34 | PunctDelimiter, 35 | PunctBracket, 36 | PunctSpecial, 37 | Repeat, 38 | String, 39 | StringRegex, 40 | StringEscape, 41 | Symbol, 42 | Tag, 43 | TagDelimiter, 44 | Text, 45 | Strong, 46 | Emphasis, 47 | Underline, 48 | Strike, 49 | Title, 50 | Literal, 51 | URL, 52 | Note, 53 | Warning, 54 | Danger, 55 | Type, 56 | TypeBuiltin, 57 | Variable, 58 | VariableBuiltin, 59 | } 60 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/language/TSIndentType.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.language; 2 | 3 | public enum TSIndentType { 4 | Indent, 5 | Branch, 6 | Return, 7 | Ignore 8 | } 9 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/language/TSLangaugeQuery.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.language; 2 | 3 | /** 4 | * Provide query. 5 | *

6 | * A query are what treesitter uses to extract informations from the syntax tree. 7 | *

8 | *

9 | * see How to write query? 10 | *

11 | */ 12 | public interface TSLangaugeQuery { 13 | String highlight(); 14 | 15 | String indent(); 16 | } 17 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/language/TSLanguageStyler.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.language; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | 9 | import io.ikws4.codeeditor.api.configuration.SyntaxColorScheme; 10 | import io.ikws4.codeeditor.api.document.markup.Markup; 11 | import io.ikws4.codeeditor.api.language.LanguageStyler; 12 | import io.ikws4.jsitter.TSNode; 13 | import io.ikws4.jsitter.TSParser; 14 | import io.ikws4.jsitter.TSQuery; 15 | import io.ikws4.jsitter.TSQueryCapture; 16 | import io.ikws4.jsitter.TSTree; 17 | 18 | public abstract class TSLanguageStyler implements LanguageStyler { 19 | private static final String TAG = "TSLanguageStyler"; 20 | 21 | private final static HashMap hlmap; 22 | private final TSParser mParser; 23 | private final TSQuery mHighlightQuery; 24 | private final TSQuery mIndentQuery; 25 | private TSTree mTree; 26 | 27 | static { 28 | hlmap = new HashMap<>(); 29 | hlmap.put("annotation", TSHighlightType.Annotation); 30 | hlmap.put("attribute", TSHighlightType.Attribute); 31 | hlmap.put("boolean", TSHighlightType.Boolean); 32 | hlmap.put("character", TSHighlightType.Character); 33 | hlmap.put("comment", TSHighlightType.Comment); 34 | hlmap.put("conditional", TSHighlightType.Conditional); 35 | hlmap.put("constant", TSHighlightType.Constant); 36 | hlmap.put("constant.builtin", TSHighlightType.ConstBuiltin); 37 | hlmap.put("constant.macro", TSHighlightType.ConstMacro); 38 | hlmap.put("constructor", TSHighlightType.Constructor); 39 | hlmap.put("error", TSHighlightType.Error); 40 | hlmap.put("exception", TSHighlightType.Exception); 41 | hlmap.put("field", TSHighlightType.Field); 42 | hlmap.put("float", TSHighlightType.Float); 43 | hlmap.put("function", TSHighlightType.Function); 44 | hlmap.put("function.builtin", TSHighlightType.FuncBuiltin); 45 | hlmap.put("function.macro", TSHighlightType.FuncMarco); 46 | hlmap.put("include", TSHighlightType.Include); 47 | hlmap.put("keyword", TSHighlightType.Keyword); 48 | hlmap.put("keyword.function", TSHighlightType.KeywordFunction); 49 | hlmap.put("keyword.operator", TSHighlightType.KeywordOperator); 50 | hlmap.put("label", TSHighlightType.Label); 51 | hlmap.put("method", TSHighlightType.Method); 52 | hlmap.put("namespace", TSHighlightType.Namespace); 53 | hlmap.put("none", TSHighlightType.None); 54 | hlmap.put("number", TSHighlightType.Number); 55 | hlmap.put("operator", TSHighlightType.Operator); 56 | hlmap.put("parameter", TSHighlightType.Parameter); 57 | hlmap.put("parameter.reference", TSHighlightType.ParameterReference); 58 | hlmap.put("property", TSHighlightType.Property); 59 | hlmap.put("punctuation.delimiter", TSHighlightType.PunctDelimiter); 60 | hlmap.put("punctuation.bracket", TSHighlightType.PunctBracket); 61 | hlmap.put("punctuation.special", TSHighlightType.PunctSpecial); 62 | hlmap.put("repeat", TSHighlightType.Repeat); 63 | hlmap.put("string", TSHighlightType.String); 64 | hlmap.put("string.regex", TSHighlightType.StringRegex); 65 | hlmap.put("string.escape", TSHighlightType.StringEscape); 66 | hlmap.put("symbol", TSHighlightType.Symbol); 67 | hlmap.put("tag", TSHighlightType.Tag); 68 | hlmap.put("tag.delimiter", TSHighlightType.TagDelimiter); 69 | hlmap.put("text", TSHighlightType.Text); 70 | hlmap.put("text.strong", TSHighlightType.Strong); 71 | hlmap.put("text.emphasis", TSHighlightType.Emphasis); 72 | hlmap.put("text.underline", TSHighlightType.Underline); 73 | hlmap.put("text.strike", TSHighlightType.Strike); 74 | hlmap.put("text.title", TSHighlightType.Title); 75 | hlmap.put("text.literal", TSHighlightType.Literal); 76 | hlmap.put("text.url", TSHighlightType.URL); 77 | hlmap.put("text.note", TSHighlightType.Note); 78 | hlmap.put("text.warning", TSHighlightType.Warning); 79 | hlmap.put("text.danger", TSHighlightType.Danger); 80 | hlmap.put("type", TSHighlightType.Type); 81 | hlmap.put("type.builtin", TSHighlightType.TypeBuiltin); 82 | hlmap.put("variable", TSHighlightType.Variable); 83 | hlmap.put("variable.builtin", TSHighlightType.VariableBuiltin); 84 | } 85 | 86 | /** 87 | * @param language see {@link io.ikws4.jsitter.TSLanguages} 88 | */ 89 | public TSLanguageStyler(long language, TSLangaugeQuery queryScm) { 90 | mParser = new TSParser(language); 91 | mHighlightQuery = new TSQuery(language, queryScm.highlight()); 92 | mIndentQuery = new TSQuery(language, queryScm.indent()); 93 | } 94 | 95 | @Override 96 | public void editSyntaxTree(int startByte, int oldEndByte, int newEndByte, int startRow, int startColumn, int oldEndRow, int oldEndColumn, int newEndRow, int newEndColumn) { 97 | if (mTree == null) return; 98 | mTree.edit(startByte, oldEndByte, newEndByte, startRow, startColumn, oldEndRow, oldEndColumn, newEndRow, newEndColumn); 99 | } 100 | 101 | /** 102 | * Reference https://github.com/nvim-treesitter/nvim-treesitter/blob/master/lua/nvim-treesitter/indent.lua 103 | */ 104 | @Override 105 | public int getIndentLevel(int line, int prevnonblankLine) { 106 | int level = 0; 107 | 108 | TSNode root = mTree.getRoot(); 109 | TSNode curr = TSUtil.getNodeAtLine(root, line); 110 | 111 | HashMap queryMap = new HashMap<>(); 112 | for (TSQueryCapture capture : mIndentQuery.captureIter(root)) { 113 | TSIndentType type = TSIndentType.Ignore; 114 | switch (capture.getName()) { 115 | case "indent": 116 | type = TSIndentType.Indent; 117 | break; 118 | case "branch": 119 | type = TSIndentType.Branch; 120 | break; 121 | case "return": 122 | type = TSIndentType.Return; 123 | break; 124 | } 125 | queryMap.put(capture.getNode(), type); 126 | } 127 | 128 | if (curr == null) { 129 | if (prevnonblankLine != line) { 130 | TSNode prevNode = TSUtil.getNodeAtLine(root, prevnonblankLine); 131 | boolean usePrev = prevNode != null && prevNode.getEndRow() < line; 132 | usePrev &= queryMap.get(prevNode) != TSIndentType.Return; 133 | if (usePrev) { 134 | curr = prevNode; 135 | } 136 | } 137 | } 138 | 139 | if (curr == null) { 140 | TSNode wrapper = root.decendantForRange(line, 0, line, -1); 141 | assert wrapper != null; 142 | 143 | curr = wrapper.getChild(0); 144 | if (curr == null) curr = wrapper; 145 | 146 | if (queryMap.get(wrapper) == TSIndentType.Indent && wrapper != root) { 147 | level = 1; 148 | } 149 | } 150 | 151 | while (curr != null && queryMap.get(curr) == TSIndentType.Branch) { 152 | curr = curr.getParent(); 153 | } 154 | 155 | boolean first = true; 156 | assert curr != null; 157 | 158 | int prevRow = curr.getStartRow(); 159 | 160 | while (curr != null) { 161 | if (TSIndentType.Ignore == queryMap.get(curr) && curr.getStartRow() < line && curr.getEndRow() > line) { 162 | return 0; 163 | } 164 | 165 | int row = curr.getStartRow(); 166 | if (!first && TSIndentType.Indent == queryMap.get(curr) && prevRow != row) { 167 | level++; 168 | prevRow = row; 169 | } 170 | 171 | curr = curr.getParent(); 172 | first = false; 173 | } 174 | 175 | return level; 176 | } 177 | 178 | @Override 179 | public List process(String source, SyntaxColorScheme scheme) { 180 | List markups = new ArrayList<>(); 181 | parse(source); 182 | 183 | for (TSQueryCapture capture : mHighlightQuery.captureIter(mTree.getRoot())) { 184 | TSNode node = capture.getNode(); 185 | Markup markup = onBuildMarkup(hlmap.get(capture.getName()), node.getStartByte(), node.getEndByte(), scheme); 186 | if (markup != null) markups.add(markup); 187 | } 188 | 189 | return markups; 190 | } 191 | 192 | @Nullable 193 | protected abstract Markup onBuildMarkup(TSHighlightType type, int start, int end, SyntaxColorScheme scheme); 194 | 195 | private void parse(String source) { 196 | synchronized (this) { 197 | mTree = mParser.parse(source, mTree); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/language/TSUtil.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.language; 2 | 3 | import io.ikws4.jsitter.TSNode; 4 | 5 | class TSUtil { 6 | public static TSNode getNodeAtLine(TSNode root, int line) { 7 | for (TSNode node : root.childrenIter()) { 8 | int startRow = node.getStartRow(); 9 | int endRow = node.getEndRow(); 10 | if (startRow == line) return node; 11 | 12 | if (node.getChildCount() > 0 && startRow < line && line <= endRow) { 13 | return getNodeAtLine(node, line); 14 | } 15 | } 16 | return null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/language/java/JavaLanguage.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.language.java; 2 | 3 | import io.ikws4.codeeditor.api.language.Language; 4 | import io.ikws4.codeeditor.api.language.LanguageParser; 5 | import io.ikws4.codeeditor.api.language.LanguageSuggestionProvider; 6 | import io.ikws4.codeeditor.api.language.LanguageStyler; 7 | 8 | public class JavaLanguage implements Language { 9 | @Override 10 | public String getName() { 11 | return "java"; 12 | } 13 | 14 | @Override 15 | public LanguageParser getParser() { 16 | return JavaParser.getInstance(); 17 | } 18 | 19 | @Override 20 | public LanguageSuggestionProvider getSuggestionProvider() { 21 | return JavaSuggestionProvider.getInstance(); 22 | } 23 | 24 | @Override 25 | public LanguageStyler getStyler() { 26 | return JavaStyler.getInstance(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/language/java/JavaParser.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.language.java; 2 | 3 | 4 | import io.ikws4.codeeditor.api.language.LanguageParser; 5 | import io.ikws4.codeeditor.api.language.ParseException; 6 | import io.ikws4.codeeditor.api.language.ParseResult; 7 | 8 | class JavaParser implements LanguageParser { 9 | private static JavaParser sInstance; 10 | 11 | private JavaParser() {} 12 | 13 | public static JavaParser getInstance() { 14 | if (sInstance == null) { 15 | sInstance = new JavaParser(); 16 | } 17 | return sInstance; 18 | } 19 | 20 | @Override 21 | public ParseResult parse(String name, String source) { 22 | // TODO: Implement java parser 23 | ParseException exception = new ParseException("Unable to parse unsupported language", 0, 0); 24 | return new ParseResult(exception); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/language/java/JavaQuery.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.language.java; 2 | 3 | import io.ikws4.codeeditor.language.TSLangaugeQuery; 4 | 5 | class JavaQuery implements TSLangaugeQuery { 6 | @Override 7 | public String highlight() { 8 | return ScmsKt.HIGHTLIGHT; 9 | } 10 | 11 | @Override 12 | public String indent() { 13 | return ScmsKt.INDENT; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/language/java/JavaStyler.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.language.java; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.googlejavaformat.java.Formatter; 6 | import com.google.googlejavaformat.java.FormatterException; 7 | import com.google.googlejavaformat.java.JavaFormatterOptions; 8 | 9 | import io.ikws4.codeeditor.api.configuration.SyntaxColorScheme; 10 | import io.ikws4.codeeditor.api.document.markup.Markup; 11 | import io.ikws4.codeeditor.language.TSHighlightType; 12 | import io.ikws4.codeeditor.api.document.markup.SyntaxMarkup; 13 | import io.ikws4.codeeditor.language.TSLanguageStyler; 14 | import io.ikws4.jsitter.TSLanguages; 15 | 16 | class JavaStyler extends TSLanguageStyler { 17 | private static final String TAG = "JavaStyler"; 18 | 19 | private final Formatter mFormatter; 20 | private static JavaStyler sInstance; 21 | 22 | private JavaStyler() { 23 | super(TSLanguages.java(), new JavaQuery()); 24 | JavaFormatterOptions options = JavaFormatterOptions.builder() 25 | .style(JavaFormatterOptions.Style.AOSP) 26 | .build(); 27 | mFormatter = new Formatter(options); 28 | } 29 | 30 | public static JavaStyler getInstance() { 31 | if (sInstance == null) { 32 | sInstance = new JavaStyler(); 33 | } 34 | return sInstance; 35 | } 36 | 37 | @Override 38 | public String format(String source) { 39 | try { 40 | return mFormatter.formatSource(source); 41 | } catch (FormatterException e) { 42 | Log.w(TAG, "format: ", e); 43 | } 44 | return source; 45 | } 46 | 47 | @Override 48 | protected Markup onBuildMarkup(TSHighlightType type, int start, int end, SyntaxColorScheme scheme) { 49 | switch (type) { 50 | case Attribute: 51 | return new SyntaxMarkup(scheme.getAnnotationColor(), start, end); 52 | case Comment: 53 | return new SyntaxMarkup(scheme.getCommentColor(), start, end); 54 | case Method: 55 | case Function: 56 | return new SyntaxMarkup(scheme.getMethodColor(), start, end); 57 | case Include: 58 | case ConstBuiltin: 59 | case Boolean: 60 | case FuncBuiltin: 61 | case TypeBuiltin: 62 | case Conditional: 63 | case Repeat: 64 | case KeywordOperator: 65 | case Exception: 66 | case Keyword: 67 | return new SyntaxMarkup(scheme.getKeywordColor(), start, end); 68 | case Float: 69 | case Number: 70 | return new SyntaxMarkup(scheme.getNumberColor(), start, end); 71 | case Operator: 72 | return new SyntaxMarkup(scheme.getOperatorColor(), start, end); 73 | case Character: 74 | case String: 75 | return new SyntaxMarkup(scheme.getStringColor(), start, end); 76 | case Type: 77 | return new SyntaxMarkup(scheme.getTypeColor(), start, end); 78 | } 79 | return null; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/language/java/JavaSuggestionProvider.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.language.java; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import io.ikws4.codeeditor.api.language.LanguageSuggestionProvider; 9 | import io.ikws4.codeeditor.api.language.Suggestion; 10 | import io.ikws4.codeeditor.api.language.Suggestion.Type; 11 | 12 | class JavaSuggestionProvider implements LanguageSuggestionProvider { 13 | private static JavaSuggestionProvider sInstance; 14 | private final Map> mLineSuggestions = new HashMap<>(); 15 | private final String[] mKeywords = ("abstract|assert|break|case|catch|class|continue|default|" + 16 | "do|else|enum|exports|extends|final|finally|for|if|implements|" + 17 | "import|instanceof|interface|module|native|new|open|opens|" + 18 | "package|private|protected|provides|public|requires|return|static|" + 19 | "strictfp|switch|synchronized|throw|throws|to|transient|transitive|" + 20 | "try|uses|volatile|while|with").split("\\|"); 21 | 22 | private JavaSuggestionProvider() { 23 | } 24 | 25 | public static JavaSuggestionProvider getInstance() { 26 | if (sInstance == null) { 27 | sInstance = new JavaSuggestionProvider(); 28 | } 29 | return sInstance; 30 | } 31 | 32 | @Override 33 | public List getAll() { 34 | List suggestions = new ArrayList<>(); 35 | // keywords 36 | for (String keyword : mKeywords) { 37 | suggestions.add(new Suggestion(Type.KEYWORD, keyword)); 38 | } 39 | for (List value : mLineSuggestions.values()) { 40 | suggestions.addAll(value); 41 | } 42 | return suggestions; 43 | } 44 | 45 | @Override 46 | public void process(int line, String text) { 47 | List list = mLineSuggestions.get(line); 48 | if (list == null) { 49 | list = new ArrayList<>(); 50 | } 51 | 52 | // TODO: java suggestion provider process implementation 53 | } 54 | 55 | @Override 56 | public void delete(int line) { 57 | mLineSuggestions.remove(line); 58 | } 59 | 60 | @Override 61 | public void clear() { 62 | mLineSuggestions.clear(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/language/java/Scms.kt: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.language.java 2 | 3 | internal const val HIGHTLIGHT = """ 4 | ; CREDITS @maxbrunsfeld (maxbrunsfeld@gmail.com) 5 | 6 | ; Variables 7 | 8 | (identifier) @variable 9 | 10 | ; Methods 11 | 12 | (method_declaration 13 | name: (identifier) @method) 14 | (method_invocation 15 | name: (identifier) @method) 16 | 17 | (super) @function.builtin 18 | 19 | ; Parameters 20 | (formal_parameter 21 | name: (identifier) @parameter) 22 | (catch_formal_parameter 23 | name: (identifier) @parameter) 24 | 25 | (spread_parameter 26 | (variable_declarator) @parameter) ; int... foo 27 | 28 | ;; Lambda parameter 29 | (inferred_parameters (identifier) @parameter) ; (x,y) -> ... 30 | (lambda_expression 31 | parameters: (identifier) @parameter) ; x -> ... 32 | 33 | 34 | ; Annotations 35 | 36 | 37 | (annotation 38 | name: (identifier) @attribute) @attribute 39 | (marker_annotation 40 | name: (identifier) @attribute) @attribute 41 | 42 | 43 | ; Operators 44 | 45 | [ 46 | "@" 47 | "+" 48 | ":" 49 | "++" 50 | "-" 51 | "--" 52 | "&" 53 | "&&" 54 | "|" 55 | "||" 56 | "!=" 57 | "==" 58 | "*" 59 | "/" 60 | "%" 61 | "<" 62 | "<=" 63 | ">" 64 | ">=" 65 | "=" 66 | "-=" 67 | "+=" 68 | "*=" 69 | "/=" 70 | "%=" 71 | "->" 72 | "^" 73 | "^=" 74 | "&=" 75 | "|=" 76 | "~" 77 | ">>" 78 | ">>>" 79 | "<<" 80 | "::" 81 | ] @operator 82 | 83 | ; Types 84 | 85 | (interface_declaration 86 | name: (identifier) @type) 87 | (class_declaration 88 | name: (identifier) @type) 89 | (enum_declaration 90 | name: (identifier) @type) 91 | (constructor_declaration 92 | name: (identifier) @type) 93 | (type_identifier) @type 94 | 95 | 96 | 97 | ((field_access 98 | object: (identifier) @type) 99 | (#match? @type "^[A-Z]")) 100 | ((scoped_identifier 101 | scope: (identifier) @type) 102 | (#match? @type "^[A-Z]")) 103 | 104 | [ 105 | (boolean_type) 106 | (integral_type) 107 | (floating_point_type) 108 | (void_type) 109 | ] @type.builtin 110 | 111 | ; Variables 112 | 113 | ((identifier) @constant 114 | (#match? @constant "^[A-Z_][A-Z\d_]+${'$'}")) 115 | 116 | (this) @variable.builtin 117 | 118 | ; Literals 119 | 120 | [ 121 | (hex_integer_literal) 122 | (decimal_integer_literal) 123 | (octal_integer_literal) 124 | (binary_integer_literal) 125 | ] @number 126 | 127 | [ 128 | (decimal_floating_point_literal) 129 | (hex_floating_point_literal) 130 | ] @float 131 | 132 | (character_literal) @character 133 | (string_literal) @string 134 | (null_literal) @constant.builtin 135 | 136 | (comment) @comment 137 | 138 | [ 139 | (true) 140 | (false) 141 | ] @boolean 142 | 143 | ; Keywords 144 | 145 | [ 146 | "abstract" 147 | "assert" 148 | "break" 149 | "class" 150 | "continue" 151 | "default" 152 | "enum" 153 | "exports" 154 | "extends" 155 | "final" 156 | "implements" 157 | "instanceof" 158 | "interface" 159 | "module" 160 | "native" 161 | "open" 162 | "opens" 163 | "package" 164 | "private" 165 | "protected" 166 | "provides" 167 | "public" 168 | "requires" 169 | "return" 170 | "static" 171 | "strictfp" 172 | "synchronized" 173 | "to" 174 | "transient" 175 | "transitive" 176 | "uses" 177 | "volatile" 178 | "with" 179 | ] @keyword 180 | 181 | [ 182 | "new" 183 | ] @keyword.operator 184 | 185 | ; Conditionals 186 | 187 | [ 188 | "if" 189 | "else" 190 | "switch" 191 | "case" 192 | ] @conditional 193 | 194 | (ternary_expression ["?" ":"] @conditional) 195 | 196 | ; 197 | 198 | [ 199 | "for" 200 | "while" 201 | "do" 202 | ] @repeat 203 | 204 | ; Includes 205 | 206 | "import" @include 207 | "package" @include 208 | 209 | ; Punctuation 210 | 211 | [ 212 | ";" 213 | "." 214 | "..." 215 | "," 216 | ] @punctuation.delimiter 217 | 218 | [ 219 | "[" 220 | "]" 221 | "{" 222 | "}" 223 | "(" 224 | ")" 225 | ] @punctuation.bracket 226 | 227 | ; Exceptions 228 | 229 | [ 230 | "throw" 231 | "throws" 232 | "finally" 233 | "try" 234 | "catch" 235 | ] @exception 236 | 237 | ; Labels 238 | (labeled_statement 239 | (identifier) @label) 240 | """ 241 | 242 | internal const val INDENT = """ 243 | [ 244 | (class_declaration) 245 | (class_body) 246 | (constructor_declaration) 247 | (constructor_body) 248 | (block) 249 | (switch_block) 250 | (array_initializer) 251 | (argument_list) 252 | (formal_parameters) 253 | ] @indent 254 | 255 | [ 256 | "(" 257 | ")" 258 | "{" 259 | "}" 260 | "[" 261 | "]" 262 | ] @branch 263 | 264 | (comment) @ignore 265 | """ -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/language/none/NoneLanguage.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.language.none; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import io.ikws4.codeeditor.api.configuration.SyntaxColorScheme; 7 | import io.ikws4.codeeditor.api.document.markup.Markup; 8 | import io.ikws4.codeeditor.api.language.Language; 9 | import io.ikws4.codeeditor.api.language.LanguageParser; 10 | import io.ikws4.codeeditor.api.language.LanguageStyler; 11 | import io.ikws4.codeeditor.api.language.LanguageSuggestionProvider; 12 | import io.ikws4.codeeditor.api.language.ParseException; 13 | import io.ikws4.codeeditor.api.language.ParseResult; 14 | import io.ikws4.codeeditor.api.language.Suggestion; 15 | 16 | public class NoneLanguage implements Language { 17 | @Override 18 | public String getName() { 19 | return "none"; 20 | } 21 | 22 | @Override 23 | public LanguageParser getParser() { 24 | return new LanguageParser() { 25 | @Override 26 | public ParseResult parse(String name, String source) { 27 | ParseException exception = new ParseException("Unable to parse unsupported language", 0, 0); 28 | return new ParseResult(exception); 29 | } 30 | }; 31 | } 32 | 33 | @Override 34 | public LanguageSuggestionProvider getSuggestionProvider() { 35 | return new LanguageSuggestionProvider() { 36 | @Override 37 | public List getAll() { 38 | return Collections.emptyList(); 39 | } 40 | 41 | @Override 42 | public void process(int line, String text) { 43 | 44 | } 45 | 46 | @Override 47 | public void delete(int line) { 48 | 49 | } 50 | 51 | @Override 52 | public void clear() { 53 | 54 | } 55 | }; 56 | } 57 | 58 | @Override 59 | public LanguageStyler getStyler() { 60 | return new LanguageStyler() { 61 | @Override 62 | public void editSyntaxTree(int startByte, int oldEndByte, int newEndByte, int startRow, int startColumn, int oldEndRow, int oldEndColumn, int newEndRow, int newEndColumn) { 63 | 64 | } 65 | 66 | @Override 67 | public int getIndentLevel(int line, int prevnonblankLine) { 68 | return 0; 69 | } 70 | 71 | @Override 72 | public String format(String source) { 73 | return ""; 74 | } 75 | 76 | @Override 77 | public List process(String source, SyntaxColorScheme scheme) { 78 | return Collections.emptyList(); 79 | } 80 | }; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/task/FormatTask.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.task; 2 | 3 | import android.os.AsyncTask; 4 | 5 | import java.lang.ref.WeakReference; 6 | 7 | import io.ikws4.codeeditor.api.editor.Editor; 8 | 9 | public class FormatTask extends AsyncTask { 10 | private final WeakReference mEditor; 11 | private final TaskFinishedListener mListener; 12 | 13 | public FormatTask(Editor editor, TaskFinishedListener listener) { 14 | super(); 15 | mEditor = new WeakReference<>(editor); 16 | mListener = listener; 17 | } 18 | 19 | @Override 20 | protected String doInBackground(Void... voids) { 21 | Editor editor = mEditor.get(); 22 | String text = editor.getDocument().toString(); 23 | return editor.getLanguage().getStyler().format(text); 24 | } 25 | 26 | @Override 27 | protected void onPostExecute(String s) { 28 | mListener.onFinished(s); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/task/ParsingMarkupTask.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.task; 2 | 3 | import android.os.AsyncTask; 4 | 5 | import java.lang.ref.WeakReference; 6 | import java.util.List; 7 | 8 | import io.ikws4.codeeditor.api.editor.Editor; 9 | import io.ikws4.codeeditor.api.configuration.SyntaxColorScheme; 10 | import io.ikws4.codeeditor.api.document.markup.Markup; 11 | import io.ikws4.codeeditor.api.language.LanguageStyler; 12 | 13 | public class ParsingMarkupTask extends AsyncTask> { 14 | private final WeakReference mEditor; 15 | private final TaskFinishedListener> mListener; 16 | 17 | public ParsingMarkupTask(Editor editor, TaskFinishedListener> listener) { 18 | super(); 19 | mEditor = new WeakReference<>(editor); 20 | mListener = listener; 21 | } 22 | 23 | @Override 24 | protected List doInBackground(Void... voids) { 25 | Editor editor = mEditor.get(); 26 | String text = editor.getDocument().toString(); 27 | LanguageStyler highlighter = editor.getLanguage().getStyler(); 28 | SyntaxColorScheme syntaxScheme = editor.getConfiguration().getColorScheme().getSyntaxColorScheme(); 29 | return highlighter.process(text, syntaxScheme); 30 | } 31 | 32 | @Override 33 | protected void onPostExecute(List spans) { 34 | mListener.onFinished(spans); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/task/TaskFinishedListener.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.task; 2 | 3 | public interface TaskFinishedListener { 4 | void onFinished(T data); 5 | } 6 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/widget/HScrollView.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.HorizontalScrollView; 6 | 7 | public class HScrollView extends HorizontalScrollView { 8 | public HScrollView(Context context) { 9 | this(context, null); 10 | } 11 | 12 | public HScrollView(Context context, AttributeSet attrs) { 13 | this(context, attrs, 0); 14 | } 15 | 16 | public HScrollView(Context context, AttributeSet attrs, int defStyleAttr) { 17 | super(context, attrs, defStyleAttr); 18 | setHorizontalFadingEdgeEnabled(false); 19 | setHorizontalScrollBarEnabled(false); 20 | setOverScrollMode(OVER_SCROLL_NEVER); 21 | setFillViewport(true); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/widget/KeyButton.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.util.Log; 6 | import android.util.TypedValue; 7 | import android.view.MotionEvent; 8 | import android.view.View; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.Nullable; 12 | import androidx.appcompat.widget.AppCompatImageButton; 13 | 14 | /** 15 | * Simulates a physical keyboard button that is repeatedly call {@link OnPressedListener#onPressed()} when long pressed. 16 | * Disable long press, simplly {@link #setLongClickable(boolean)} to be false. 17 | */ 18 | public class KeyButton extends AppCompatImageButton implements Runnable, View.OnLongClickListener, View.OnClickListener { 19 | private boolean mLongPressed; 20 | private OnPressedListener mOnPressedListener; 21 | 22 | public KeyButton(@NonNull Context context) { 23 | this(context, null); 24 | } 25 | 26 | public KeyButton(@NonNull Context context, @Nullable AttributeSet attrs) { 27 | this(context, attrs, android.R.attr.imageButtonStyle); 28 | } 29 | 30 | public KeyButton(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 31 | super(context, attrs, defStyleAttr); 32 | TypedValue value = new TypedValue(); 33 | getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, value, true); 34 | setBackgroundResource(value.resourceId); 35 | setOnClickListener(this); 36 | if (isLongClickable()) { 37 | setOnLongClickListener(this); 38 | } 39 | } 40 | 41 | @Override 42 | public boolean onTouchEvent(MotionEvent event) { 43 | switch (event.getAction()) { 44 | case MotionEvent.ACTION_UP: 45 | case MotionEvent.ACTION_CANCEL: 46 | mLongPressed = false; 47 | break; 48 | } 49 | return super.onTouchEvent(event); 50 | } 51 | 52 | @Override 53 | public void run() { 54 | if (mOnPressedListener != null) { 55 | mOnPressedListener.onPressed(); 56 | if (mLongPressed) { 57 | postDelayed(this, 50); 58 | } 59 | } 60 | } 61 | 62 | @Override 63 | public void onClick(View v) { 64 | post(this); 65 | } 66 | 67 | @Override 68 | public boolean onLongClick(View v) { 69 | mLongPressed = true; 70 | post(this); 71 | return true; 72 | } 73 | 74 | public void setOnPressedListener(OnPressedListener l) { 75 | mOnPressedListener = l; 76 | } 77 | 78 | public interface OnPressedListener { 79 | void onPressed(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /editor/src/main/java/io/ikws4/codeeditor/widget/VScrollView.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.codeeditor.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.ScrollView; 6 | 7 | public class VScrollView extends ScrollView { 8 | public VScrollView(Context context) { 9 | this(context, null); 10 | } 11 | 12 | public VScrollView(Context context, AttributeSet attrs) { 13 | this(context, attrs, 0); 14 | } 15 | 16 | public VScrollView(Context context, AttributeSet attrs, int defStyleAttr) { 17 | super(context, attrs, defStyleAttr); 18 | setVerticalFadingEdgeEnabled(false); 19 | setVerticalScrollBarEnabled(false); 20 | setOverScrollMode(OVER_SCROLL_NEVER); 21 | setFillViewport(true); 22 | } 23 | 24 | @Override 25 | public void fling(int velocityY) { 26 | super.fling(velocityY); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /editor/src/main/res/drawable/cursor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /editor/src/main/res/drawable/ic_copy.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /editor/src/main/res/drawable/ic_cut.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /editor/src/main/res/drawable/ic_keyboard.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /editor/src/main/res/drawable/ic_keyboard_arrow_down.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /editor/src/main/res/drawable/ic_keyboard_arrow_left.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /editor/src/main/res/drawable/ic_keyboard_arrow_right.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /editor/src/main/res/drawable/ic_keyboard_arrow_up.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /editor/src/main/res/drawable/ic_keyboard_hide.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /editor/src/main/res/drawable/ic_keyboard_tab.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /editor/src/main/res/drawable/ic_paste.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /editor/src/main/res/drawable/ic_select_all.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /editor/src/main/res/layout/editor.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 21 | 22 | 26 | 27 | 31 | 32 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /editor/src/main/res/layout/item_suggestion.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | 18 | 26 | 27 | -------------------------------------------------------------------------------- /editor/src/main/res/layout/toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 20 | 21 | 28 | 29 | 36 | 37 | 44 | 45 | 52 | 53 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /editor/src/main/res/menu/clipboard_panel_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 15 | 20 | 25 | -------------------------------------------------------------------------------- /editor/src/test/java/io/ikws4/codeeditor/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.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=-Xmx2048m -Dfile.encoding=UTF-8 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 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikws4/CodeEditor/ec300715612665fa8dc9d3ba8dd4f7416679c3f7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Mar 11 21:13:07 CST 2021 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-6.5-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 | -------------------------------------------------------------------------------- /jsitter/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /jsitter/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /jsitter/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | jsitter 4 | Project jsitter created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | 25 | 1615984813551 26 | 27 | 30 28 | 29 | org.eclipse.core.resources.regexFilterMatcher 30 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /jsitter/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=.. 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /jsitter/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | android { 6 | compileSdkVersion 30 7 | buildToolsVersion "30.0.3" 8 | 9 | defaultConfig { 10 | minSdkVersion 21 11 | targetSdkVersion 30 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | consumerProguardFiles "consumer-rules.pro" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | externalNativeBuild { 30 | cmake { 31 | path file('src/main/cpp/CMakeLists.txt') 32 | } 33 | } 34 | } 35 | 36 | dependencies { 37 | implementation 'androidx.appcompat:appcompat:1.2.0' 38 | } -------------------------------------------------------------------------------- /jsitter/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikws4/CodeEditor/ec300715612665fa8dc9d3ba8dd4f7416679c3f7/jsitter/consumer-rules.pro -------------------------------------------------------------------------------- /jsitter/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 -------------------------------------------------------------------------------- /jsitter/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /jsitter/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.1) 2 | 3 | project(jsitter) 4 | 5 | include_directories(src) 6 | include_directories(tree-sitter/lib/src tree-sitter/lib/include) 7 | 8 | find_library(log-lib log) 9 | 10 | add_library(jsitter SHARED src/jsitter.cc tree-sitter/lib/src/lib.c) 11 | 12 | # Grammars 13 | add_library(tsjava SHARED grammars/tree-sitter-java/src/parser.c) 14 | 15 | target_link_libraries(jsitter tsjava ${log-lib}) -------------------------------------------------------------------------------- /jsitter/src/main/java/io/ikws4/jsitter/TSLanguages.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.jsitter; 2 | 3 | public class TSLanguages { 4 | public static native long java(); 5 | } 6 | -------------------------------------------------------------------------------- /jsitter/src/main/java/io/ikws4/jsitter/TSNode.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.jsitter; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Iterator; 8 | import java.util.List; 9 | import java.util.Objects; 10 | 11 | public class TSNode { 12 | long id; 13 | long treePtr; 14 | int context0; 15 | int context1; 16 | int context2; 17 | int context3; 18 | private int endByte; 19 | private int endRow; 20 | private int endColumn; 21 | 22 | public int getStartByte() { 23 | return context0; 24 | } 25 | 26 | public int getEndByte() { 27 | return endByte; 28 | } 29 | 30 | public int getStartRow() { 31 | return context1; 32 | } 33 | 34 | public int getStartColumn() { 35 | return context2; 36 | } 37 | 38 | public int getEndRow() { 39 | return endRow; 40 | } 41 | 42 | public int getEndColumn() { 43 | return endColumn; 44 | } 45 | 46 | @Nullable 47 | public TSNode getParent() { 48 | return TreeSitter.nodeParant(id, treePtr, context0, context1, context2, context3); 49 | } 50 | 51 | @Nullable 52 | public TSNode getChild(int index) { 53 | return TreeSitter.nodeChild(id, treePtr, context0, context1, context2, context3, index); 54 | } 55 | 56 | public int getChildCount() { 57 | return TreeSitter.nodeChildCount(id, treePtr, context0, context1, context2, context3); 58 | } 59 | 60 | @Nullable 61 | public TSNode getNamedChild(int index) { 62 | return TreeSitter.nodeNamedChild(id, treePtr, context0, context1, context2, context3, index); 63 | } 64 | 65 | public int getNamedChildCount() { 66 | return TreeSitter.nodeNamedChildCount(id, treePtr, context0, context1, context2, context3); 67 | } 68 | 69 | public String getType() { 70 | return TreeSitter.nodeType(id, treePtr, context0, context1, context2, context3); 71 | } 72 | 73 | public int getSymbol() { 74 | return TreeSitter.nodeSymbol(id, treePtr, context0, context1, context2, context3); 75 | } 76 | 77 | public boolean isNamed() { 78 | return TreeSitter.nodeIsNamed(id, treePtr, context0, context1, context2, context3); 79 | } 80 | 81 | public boolean isMissing() { 82 | return TreeSitter.nodeIsMissing(id, treePtr, context0, context1, context2, context3); 83 | } 84 | 85 | public boolean hasError() { 86 | return TreeSitter.nodeHasError(id, treePtr, context0, context1, context2, context3); 87 | } 88 | 89 | public boolean hasChildren() { 90 | return getChildCount() > 0; 91 | } 92 | 93 | public String getSExpr() { 94 | return TreeSitter.nodeString(id, treePtr, context0, context1, context2, context3); 95 | } 96 | 97 | @Nullable 98 | public TSNode decendantForRange(int startRow, int startColumn, int endRow, int endColumn) { 99 | return TreeSitter.nodeDescendantForRange(id, treePtr, context0, context1, context2, context3, startRow, startColumn, endRow, endColumn); 100 | } 101 | 102 | @Nullable 103 | public TSNode namedDecendantForRange(int startRow, int startColumn, int endRow, int endColumn) { 104 | return TreeSitter.nodeNamedDescendantForRange(id, treePtr, context0, context1, context2, context3, startRow, startColumn, endRow, endColumn); 105 | } 106 | 107 | public Iterable childrenIter() { 108 | return new Iterable() { 109 | @NonNull 110 | @Override 111 | public Iterator iterator() { 112 | return new Iterator() { 113 | private int index = 0; 114 | private final int size = getChildCount(); 115 | 116 | @Override 117 | public boolean hasNext() { 118 | return index < size; 119 | } 120 | 121 | @Override 122 | public TSNode next() { 123 | return getChild(index++); 124 | } 125 | }; 126 | } 127 | }; 128 | } 129 | 130 | public TSTreeCursor cursor() { 131 | return new TSTreeCursor(this); 132 | } 133 | 134 | @Override 135 | public boolean equals(Object o) { 136 | if (this == o) return true; 137 | if (o == null || getClass() != o.getClass()) return false; 138 | TSNode tsNode = (TSNode) o; 139 | return id == tsNode.id && 140 | treePtr == tsNode.treePtr; 141 | } 142 | 143 | @Override 144 | public int hashCode() { 145 | return Objects.hash(id, treePtr); 146 | } 147 | 148 | @Override 149 | public String toString() { 150 | return "TSNode{" + 151 | "startByte=" + context0 + 152 | ", endByte=" + endByte + 153 | ", startRow=" + context1 + 154 | ", endRow=" + endRow + 155 | ", startColumn=" + context2 + 156 | ", endColumn=" + endColumn + 157 | ", type='" + getType() + '\'' + 158 | '}'; 159 | } 160 | } -------------------------------------------------------------------------------- /jsitter/src/main/java/io/ikws4/jsitter/TSParser.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.jsitter; 2 | 3 | public class TSParser implements AutoCloseable { 4 | private final long ptr; 5 | private long language; 6 | 7 | public TSParser() { 8 | this.ptr = TreeSitter.parserNew(); 9 | } 10 | 11 | public TSParser(long language) { 12 | this(); 13 | setLanguage(language); 14 | } 15 | 16 | public void setLanguage(long language) { 17 | this.language = language; 18 | TreeSitter.parserSetLanguage(ptr, language); 19 | } 20 | 21 | public long getLanguage() { 22 | return language; 23 | } 24 | 25 | public TSTree parse(String source, TSTree oldTree) { 26 | long oldTreePtr = oldTree == null ? 0 : oldTree.ptr; 27 | return new TSTree(TreeSitter.parserParseString(ptr, oldTreePtr, source, source.length())); 28 | } 29 | 30 | @Override 31 | public void close() { 32 | TreeSitter.parserDelete(ptr); 33 | } 34 | } -------------------------------------------------------------------------------- /jsitter/src/main/java/io/ikws4/jsitter/TSQuery.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.jsitter; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import java.util.Iterator; 6 | 7 | public class TSQuery implements AutoCloseable { 8 | private final long ptr; 9 | private final long queryCursorPtr; 10 | 11 | /** 12 | * Create a new query from a string containing one or more S-expression 13 | * patterns. The query is associated with a particular language, and can 14 | * only be run on syntax nodes parsed with that language. 15 | * 16 | * @param language {@link TSLanguages} 17 | * @param source S-expression 18 | */ 19 | public TSQuery(long language, String source) { 20 | ptr = TreeSitter.queryNew(language, source, source.length()); 21 | queryCursorPtr = TreeSitter.queryCursorNew(); 22 | } 23 | 24 | public Iterable captureIter(TSNode node) { 25 | return captureIter(node, 0, 0); 26 | } 27 | 28 | public Iterable captureIter(TSNode node, int startRow, int endRow) { 29 | final Iterator matchIterator = matchIter(node, startRow, endRow).iterator(); 30 | 31 | return new Iterable() { 32 | @NonNull 33 | @Override 34 | public Iterator iterator() { 35 | return new Iterator() { 36 | private TSQueryMatch match = matchIterator.next(); 37 | private int index; 38 | 39 | @Override 40 | public boolean hasNext() { 41 | return match != null && index < match.captureCount(); 42 | } 43 | 44 | @Override 45 | public TSQueryCapture next() { 46 | TSQueryCapture capture = match.getCapture(index++); 47 | if (index >= match.captureCount()) { 48 | match = matchIterator.next(); 49 | index = 0; 50 | } 51 | return capture; 52 | } 53 | }; 54 | } 55 | }; 56 | } 57 | 58 | public Iterable matchIter(TSNode node) { 59 | return matchIter(node, 0, 0); 60 | } 61 | 62 | public Iterable matchIter(TSNode node, int startRow, int endRow) { 63 | TreeSitter.queryCursorSetPointRange(queryCursorPtr, startRow, 0, endRow, -0); 64 | TreeSitter.queryCursorExec(queryCursorPtr, ptr, node.id, node.treePtr, node.context0, node.context1, node.context2, node.context3); 65 | 66 | return new Iterable() { 67 | @NonNull 68 | @Override 69 | public Iterator iterator() { 70 | return new Iterator() { 71 | private TSQueryMatch match = nextMatch(); 72 | 73 | @Override 74 | public boolean hasNext() { 75 | return match != null; 76 | } 77 | 78 | @Override 79 | public TSQueryMatch next() { 80 | return match = nextMatch(); 81 | } 82 | }; 83 | } 84 | }; 85 | } 86 | 87 | private TSQueryMatch nextMatch() { 88 | return TreeSitter.queryCursorNextMatch(queryCursorPtr, ptr); 89 | } 90 | 91 | @Override 92 | public void close() { 93 | TreeSitter.queryDelete(ptr); 94 | TreeSitter.queryCursorDelete(queryCursorPtr); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /jsitter/src/main/java/io/ikws4/jsitter/TSQueryCapture.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.jsitter; 2 | 3 | public class TSQueryCapture { 4 | private String name; 5 | private TSNode node; 6 | 7 | public String getName() { 8 | return name; 9 | } 10 | 11 | public TSNode getNode() { 12 | return node; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /jsitter/src/main/java/io/ikws4/jsitter/TSQueryMatch.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.jsitter; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import java.util.Arrays; 6 | import java.util.Iterator; 7 | 8 | public class TSQueryMatch implements Iterable { 9 | private int id; 10 | private int patternIndex; 11 | private TSQueryCapture[] captures; 12 | 13 | public TSQueryCapture getCapture(int index) { 14 | return captures[index]; 15 | } 16 | 17 | public int captureCount() { 18 | return captures.length; 19 | } 20 | 21 | public int getId() { 22 | return id; 23 | } 24 | 25 | public int getPatternIndex() { 26 | return patternIndex; 27 | } 28 | 29 | @NonNull 30 | @Override 31 | public Iterator iterator() { 32 | return new Iterator() { 33 | private int index = 0; 34 | 35 | @Override 36 | public boolean hasNext() { 37 | return index < captures.length; 38 | } 39 | 40 | @Override 41 | public TSQueryCapture next() { 42 | return captures[index++]; 43 | } 44 | }; 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return "TSQueryMatch{" + 50 | "id=" + id + 51 | ", captures=" + Arrays.toString(captures) + 52 | '}'; 53 | } 54 | } -------------------------------------------------------------------------------- /jsitter/src/main/java/io/ikws4/jsitter/TSTree.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.jsitter; 2 | 3 | public class TSTree implements AutoCloseable { 4 | final long ptr; 5 | 6 | TSTree(long ptr) { 7 | this.ptr = ptr; 8 | } 9 | 10 | public void edit(int startByte, int oldEndByte, int newEndByte, int startRow, int startColumn, int oldEndRow, int oldEndColumn, int newEndRow, int newEndColumn) { 11 | TreeSitter.treeEdit(ptr, 12 | startByte, 13 | oldEndByte, 14 | newEndByte, 15 | startRow, startColumn, 16 | oldEndRow, oldEndColumn, 17 | newEndRow, newEndColumn); 18 | } 19 | 20 | public TSNode getRoot() { 21 | return TreeSitter.treeRootNode(ptr); 22 | } 23 | 24 | public long getLanguage() { 25 | return TreeSitter.treeLanguage(ptr); 26 | } 27 | 28 | public TSTree copy() { 29 | return new TSTree(TreeSitter.treeCopy(ptr)); 30 | } 31 | 32 | @Override 33 | public void close() { 34 | TreeSitter.treeDelete(ptr); 35 | } 36 | } -------------------------------------------------------------------------------- /jsitter/src/main/java/io/ikws4/jsitter/TSTreeCursor.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.jsitter; 2 | 3 | public class TSTreeCursor implements AutoCloseable { 4 | private final long ptr; 5 | private long id; 6 | private long tree; 7 | private int context0; 8 | private int context1; 9 | 10 | TSTreeCursor(TSNode node) { 11 | this.ptr = TreeSitter.treeCursorNew(node.id, node.treePtr, node.context0, node.context1, node.context2, node.context3); 12 | } 13 | 14 | public TSNode getCurrentNode() { 15 | return TreeSitter.treeCursorCurrentNode(ptr); 16 | } 17 | 18 | public boolean gotoFirstChild() { 19 | return TreeSitter.treeCursorGotoFirstChild(ptr); 20 | } 21 | 22 | public boolean gotoNextSibling() { 23 | return TreeSitter.treeCursorGotoNextSibling(ptr); 24 | } 25 | 26 | public boolean gotoParent() { 27 | return TreeSitter.treeCursorGotoParent(ptr); 28 | } 29 | 30 | @Override 31 | public void close() { 32 | TreeSitter.treeCursorDelete(ptr); 33 | } 34 | } -------------------------------------------------------------------------------- /jsitter/src/main/java/io/ikws4/jsitter/TreeSitter.java: -------------------------------------------------------------------------------- 1 | package io.ikws4.jsitter; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | class TreeSitter { 6 | 7 | //************************************************************************* 8 | //* Section - Parser 9 | //************************************************************************* 10 | 11 | /** 12 | * Create a new parser. 13 | * 14 | * @return parser pointer 15 | */ 16 | static native long parserNew(); 17 | 18 | /** 19 | * Delete the parser, freeing all of the memory that it used. 20 | * 21 | * @param parserPtr parser pointer 22 | */ 23 | static native void parserDelete(long parserPtr); 24 | 25 | /** 26 | * Set the language that the parser should use for parsing. 27 | * 28 | * @param parserPtr paser pointer 29 | * @param languagePtr language pointer 30 | * @return Returns a boolean indicating wheter or not the language was 31 | * successfully assigned. 32 | */ 33 | static native boolean parserSetLanguage(long parserPtr, long languagePtr); 34 | 35 | /** 36 | * Use the parser to parse some source code stored in one contiguous buffer (string). 37 | * 38 | * @param parserPtr parser pointer 39 | * @param oldTreePtr If you are parsing this document for the first time, pass 0 (NULL). 40 | * Otherwise, if you have already parsed an earlier previous syntax tree 41 | * so that unchanged parts of it can be reused. This will save time and 42 | * memory. For this to work correctly, you must have way that exactly matches 43 | * the source code changes. 44 | * @param source source code 45 | * @param length source code length 46 | * @return tree pointer 47 | */ 48 | static native long parserParseString(long parserPtr, long oldTreePtr, String source, int length); 49 | 50 | 51 | //************************************************************************* 52 | //* Section - Tree 53 | //************************************************************************* 54 | 55 | /** 56 | * Create a shallow copy of the syntax tree. This is very fast. 57 | *

58 | * You need to copy a syntax tree in order to use it on more than 59 | * one thred at a time, as syntax trees are not thread safe. 60 | * 61 | * @param treePtr tree pointer 62 | * @return copyed tree pointer 63 | */ 64 | static native long treeCopy(long treePtr); 65 | 66 | /** 67 | * Delete the syntax tree, freeing all of the memory that it used. 68 | * 69 | * @param treePtr tree pointer 70 | */ 71 | static native void treeDelete(long treePtr); 72 | 73 | /** 74 | * Get the root node of the syntax tree. 75 | * 76 | * @param treePtr tree pointer 77 | * @return {@link TSNode} 78 | */ 79 | static native TSNode treeRootNode(long treePtr); 80 | 81 | /** 82 | * Get the langauge that was used to parse the syntax tree. 83 | * 84 | * @param treePtr tree pointer 85 | * @return langauge pointer see {@link TSLanguages} 86 | */ 87 | static native long treeLanguage(long treePtr); 88 | 89 | /** 90 | * Edit the syntax tree to keep it in sync with source code that 91 | * has been edited. 92 | *

93 | * You mut describe the edit both in terms of byte offsets and in terms 94 | * of (row, column) coordinates. 95 | * 96 | *

 97 |      * typedef struct {
 98 |      *   uint32_t start_byte;
 99 |      *   uint32_t old_end_byte;
100 |      *   uint32_t new_end_byte;
101 |      *   TSPoint start_point;
102 |      *   TSPoint old_end_point;
103 |      *   TSPoint new_end_point;
104 |      * } TSInputEdit;
105 |      *
106 |      * typedef struct {
107 |      *   uint32_t row;
108 |      *   uint32_t column;
109 |      * } TSPoint;
110 |      * 
111 | * 112 | * @param treePtr tree pointer 113 | */ 114 | static native void treeEdit(long treePtr, 115 | int startByte, 116 | int oldEndByte, 117 | int newEndByte, 118 | int startRow, int startColumn, 119 | int oldEndRow, int oldEndColumn, 120 | int newEndRow, int newEndColumn); 121 | 122 | 123 | //************************************************************************* 124 | //* Section - TSNode 125 | //************************************************************************* 126 | 127 | /** 128 | * Get the node's immediate parent. 129 | */ 130 | @Nullable 131 | static native TSNode nodeParant(long id, long tree_ptr, int context0, int context1, int context2, int context3); 132 | 133 | /** 134 | * Get the node's child at the given index, where zero represents the first 135 | * child. 136 | */ 137 | @Nullable 138 | static native TSNode nodeChild(long id, long tree_ptr, int context0, int context1, int context2, int context3, int index); 139 | 140 | /** 141 | * get the node's number of children. 142 | */ 143 | static native int nodeChildCount(long id, long tree_ptr, int context0, int context1, int context2, int context3); 144 | 145 | /** 146 | * Get the node's named child at the given index, where zero represents 147 | * the first child. 148 | */ 149 | @Nullable 150 | static native TSNode nodeNamedChild(long id, long tree_ptr, int context0, int context1, int context2, int context3, int index); 151 | 152 | /** 153 | * get the node's number of children. 154 | */ 155 | static native int nodeNamedChildCount(long id, long tree_ptr, int context0, int context1, int context2, int context3); 156 | 157 | /** 158 | * Get the node's type as a null-terminated string. 159 | */ 160 | static native String nodeType(long id, long tree_ptr, int context0, int context1, int context2, int context3); 161 | 162 | /** 163 | * Get the node's type as a numberial id. 164 | */ 165 | static native int nodeSymbol(long id, long tree_ptr, int context0, int context1, int context2, int context3); 166 | 167 | /** 168 | * Check is the node is named Named nodes correspond to named rules in the 169 | * grammar, whereas anonymous nodes correspond to string literals in the grammar. 170 | */ 171 | static native boolean nodeIsNamed(long id, long tree_ptr, int context0, int context1, int context2, int context3); 172 | 173 | /** 174 | * Check if the node is missing. Missing nodes are inserted by the parser in 175 | * order to recover from certain kinds of syntax errors. 176 | */ 177 | static native boolean nodeIsMissing(long id, long tree_ptr, int context0, int context1, int context2, int context3); 178 | 179 | /** 180 | * Check if the node is syntax error or contains any syntax errors. 181 | */ 182 | static native boolean nodeHasError(long id, long tree_ptr, int context0, int context1, int context2, int context3); 183 | 184 | /** 185 | * Get an S-expression representing the node as a string. 186 | */ 187 | static native String nodeString(long id, long tree_ptr, int context0, int context1, int context2, int context3); 188 | 189 | /** 190 | * Get the smallest node within this node that spans the given range of (row, column) positions. 191 | */ 192 | @Nullable 193 | static native TSNode nodeDescendantForRange(long id, long tree_ptr, int context0, int context1, int context2, int context3, int startRow, int startColumn, int endRow, int endColumn); 194 | 195 | /** 196 | * Get the smallest named node within this node that spans the given range of (row, column) positions. 197 | */ 198 | @Nullable 199 | static native TSNode nodeNamedDescendantForRange(long id, long tree_ptr, int context0, int context1, int context2, int context3, int startRow, int startColumn, int endRow, int endColumn); 200 | 201 | 202 | //************************************************************************* 203 | //* Section - TreeCursor 204 | //************************************************************************* 205 | 206 | /** 207 | * Create a new tree cursor starting from the given node. 208 | *

209 | * A tree cursor allows you to walk a syntax tree more efficiently 210 | * than is possible using the {@link TSNode} functions. It is a mutable 211 | * object that is always on a certain syntax node, and can be moved 212 | * imperatively to different nodes. 213 | * 214 | * @return TSTreeCursor pointer 215 | */ 216 | static native long treeCursorNew(long id, long tree_ptr, int context0, int context1, int context2, int context3); 217 | 218 | /** 219 | * Delete a tree cursor, freeing all of the memory that it used. 220 | * 221 | * @param treeCursorPtr TSTreeCursor pointer 222 | */ 223 | static native void treeCursorDelete(long treeCursorPtr); 224 | 225 | /** 226 | * Get the tree cursor's current node. 227 | * 228 | * @param treeCursorPtr TSTreeCursor pointer 229 | * @return Current tree node 230 | */ 231 | static native TSNode treeCursorCurrentNode(long treeCursorPtr); 232 | 233 | /** 234 | * Move the cursor to the parent of its current node. 235 | * 236 | * @param treeCursorPtr TSTreeCursor pointer 237 | * @return true if the cursor successfully moved, 238 | * false if there was no parent node. 239 | */ 240 | static native boolean treeCursorGotoParent(long treeCursorPtr); 241 | 242 | /** 243 | * Move the cursor to the next sibling of its current node. 244 | * 245 | * @param treeCursorPtr TSTreeCursor pointer 246 | * @return true if the cursor successfully moved, 247 | * false if there was no the sibling node. 248 | */ 249 | static native boolean treeCursorGotoNextSibling(long treeCursorPtr); 250 | 251 | /** 252 | * Move the cursor to the first child of its current ndoe. 253 | * 254 | * @param treeCursorPtr TSTreeCursor painter 255 | * @return true if the cursor successfully moved, 256 | * false if there were no children. 257 | */ 258 | static native boolean treeCursorGotoFirstChild(long treeCursorPtr); 259 | 260 | 261 | //************************************************************************* 262 | //* Section - Query 263 | //************************************************************************* 264 | 265 | /** 266 | * Create a new query from a string containing one or more S-expression 267 | * patterns. The query is associated with particular langauge, and can 268 | * only be run on syntax nodes parsed with that langauge. 269 | *

270 | * If all of the given patterns are valid, this return a {@link TSQuery} pointer. 271 | * If a pattern is invalid, this return 0 (NULL) 272 | * 273 | * @param languagePtr see {@link TSLanguages} 274 | * @param source S-expression 275 | * @param length S-expression length 276 | * @return TSQuery pointer 277 | */ 278 | static native long queryNew(long languagePtr, String source, int length); 279 | 280 | /** 281 | * Delete a query, freeing all of the memory that is used. 282 | * 283 | * @param queryPtr TSQuery pointer 284 | */ 285 | static native void queryDelete(long queryPtr); 286 | 287 | /** 288 | * Create a new cursor for executing a given query. 289 | *

290 | * The cursor stores the state that is needed to iteratively search 291 | * for matches. To use the query cursor, first call {@link #queryCursorExec(long, long, long, long, int, int, int, int)} 292 | * to start running a given query on a given syntax node. 293 | * 294 | * @return TSQueryCursor pointer 295 | */ 296 | static native long queryCursorNew(); 297 | 298 | /** 299 | * Delete a query cursor, freeing all of the memory that is used. 300 | * 301 | * @param queryCursorPtr TSQueryCursor pointer 302 | */ 303 | static native void queryCursorDelete(long queryCursorPtr); 304 | 305 | /** 306 | * Start running a give query on a given {@link TSNode}. 307 | * 308 | * @param queryCursorPtr TSQueryCursor pointer 309 | * @param queryPtr TSQuery pointer 310 | */ 311 | static native void queryCursorExec(long queryCursorPtr, long queryPtr, long id, long treePtr, int context0, int context1, int context2, int context3); 312 | 313 | /** 314 | * Set the range of bytes in whitch the query will be executed. 315 | * @param queryCursorPtr TSQueryCursor pointer 316 | */ 317 | static native void queryCursorSetByteRange(long queryCursorPtr, int startByte, int endByte); 318 | 319 | /** 320 | * Set the range of (row, column) in whitch the query will be executed. 321 | * @param queryCursorPtr TSQueryCursor pointer 322 | */ 323 | static native void queryCursorSetPointRange(long queryCursorPtr, int startRow, int startColumn, int endRow, int endColumn); 324 | 325 | /** 326 | * Adcance to the next match of the currently running query. 327 | * 328 | * @param queryCursorPtr TSQueryCursor pointer 329 | * @param queryPtr TSQuery pointer 330 | * @return {@link TSQueryMatch} if there is a match. Otherwise, return null. 331 | */ 332 | @Nullable 333 | static native TSQueryMatch queryCursorNextMatch(long queryCursorPtr, long queryPtr); 334 | } -------------------------------------------------------------------------------- /screenshot/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikws4/CodeEditor/ec300715612665fa8dc9d3ba8dd4f7416679c3f7/screenshot/1.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':jsitter' 2 | include ':editor' 3 | include ':app' 4 | rootProject.name = "CodeEditor" --------------------------------------------------------------------------------