The editorkit module provides code editor without any support for programming languages. If you are upgrading from any older version, please have a look at the migration guide. Please note that this library only supports Kotlin.
The editorkit module does not provide support for syntax highlighting, you need to add specific language dependency. You can see list of available languages here.
The Basics
First, you need to add TextProcessor in your layout:
You can change the default code editor behavior by using Plugin DSL as shown below:
val pluginSupplier =PluginSupplier.create {
pinchZoom { // whether the zoom gesture enabled
minTextSize =10f
maxTextSize =20f
}
lineNumbers {
lineNumbers =true// line numbers visibility
highlightCurrentLine =true// whether the current line will be highlighted
}
highlightDelimiters() // highlight open/closed brackets beside the cursor
autoIndentation {
autoIndentLines =true// whether the auto indentation enabled
autoCloseBrackets =true// automatically close open parenthesis/bracket/brace
autoCloseQuotes =true// automatically close single/double quote when typing
}
}
editor.plugins(pluginSupplier)
To enable/disable plugins in runtime, surround necessary methods with if (enabled) { ... } operator:
val pluginSupplier =PluginSupplier.create {
if (preferences.isLineNumbersEnabled) {
lineNumbers()
}
if (preferences.isPinchZoomEnabled) {
pinchZoom()
}
// ...
}
editor.plugins(pluginSupplier)
Remember: everytime you call editor.plugins(pluginSupplier) it compares current plugin list with the new one, and then detaches plugins that doesn't exists in the PluginSupplier.
Text Scroller
To attach the text scroller you need to add TextScroller in layout:
Now you need to pass a reference to a view inside attachTo method:
val editor = findViewById<TextProcessor>(R.id.editor)
val scroller = findViewById<TextScroller>(R.id.scroller)
scroller.attachTo(editor)
// or using Plugin DSL:val pluginSupplier =PluginSupplier.create {
...
textScroller {
scroller = findViewById<TextScroller>(R.id.scroller)
}
}
Code Suggestions
When you working with a code editor you want to see the list of code suggestion. (Note that you have to provide a Language object before start using it.)
First, you need to create a layout file that will represent the suggestion item inside dropdown menu:
Also you may have a use case when you want to update undo/redo buttons visibility or other UI after the text replacements is done, this can be achieved by adding OnUndoRedoChangedListener:
editor.onUndoRedoChangedListener =object:OnUndoRedoChangedListener {
overridefunonUndoRedoChanged() {
val canUndo = editor.canUndo()
val canRedo = editor.canRedo()
// ...
}
}
Navigation
Text Navigation
You can use these extension methods to navigate in text:
...or use «Go to Line» feature to place the caret at the specific line:
importcom.blacksquircle.ui.editorkit.exception.LineExceptiontry {
editor.gotoLine(lineNumber)
} catch (e:LineException) {
Toast.makeText(this, "Line does not exists", Toast.LENGTH_SHORT).show()
}
Find and Replace
The TextProcessor has built-in support for search and replace operations, including:
Search forward or backward
Regular Expressions
Match Case
Words Only
The class itself contains self-explanatory methods for all your searching needs:
find(params) - Find all possible results in text with provided options.
replaceFindResult(replaceText) - Finds current match and replaces it with new text.
replaceAllFindResults(replaceText) - Finds all matches and replaces them with the new text.
findNext() - Finds the next match and scrolls to it.
findPrevious() - Finds the previous match and scrolls to it.
clearFindResultSpans() - Clears all find spans on the screen. Call this method when you're done searching.
importcom.blacksquircle.ui.editorkit.model.FindParamsval params =FindParams(
query ="function", // text to find
regex =false, // regular expressions
matchCase =true, // case sensitive
wordsOnly =true// words only
)
editor.find(params)
// To navigate between results use findNext() and findPrevious()
Shortcuts
If you're using bluetooth keyboard you probably want to use keyboard shortcuts to write your code faster. To support the keyboard shortcuts you need to enable the shortcuts plugin and set OnShortcutListener:
The onShortcut method will be invoked only if at least one of following keys is pressed: ctrl, shift, alt. You might already noticed that you have to return a Boolean value as the result of onShortcut method. Return true if the listener has consumed the shortcut event, false otherwise.
Theming
The editorkit module includes some default themes in the EditorTheme class:
editor.colorScheme =EditorTheme.DARCULA// default// or you can use one of these:EditorTheme.MONOKAIEditorTheme.OBSIDIANEditorTheme.LADIES_NIGHTEditorTheme.TOMORROW_NIGHTEditorTheme.VISUAL_STUDIO_2013
You can also write your own theme by changing the ColorScheme properties. The example below shows how you can programmatically load the color scheme:
Since v2.1.0 the EditorKit library supports writing custom plugins to extend it's functionality. If you're using the latest version, you might be familiar with PluginSupplier and know how to use it's DSL. See More Options for info.
First, you need to create a class which extends the EditorPlugin and provide it's id in the constructor:
classCustomPlugin : EditorPlugin("custom-plugin-id") {
var publicProperty =trueoverridefunonAttached(editText:TextProcessor) {
super.onAttached(editText)
// TODO enable your feature here
}
overridefunonDetached(editText:TextProcessor) {
super.onDetached(editText)
// TODO disable your feature here
}
}
Second, you can override lifecycle methods, for example afterDraw, which invoked immediately after onDraw(Canvas) in code editor:
classCustomPlugin : EditorPlugin("custom-plugin-id") {
var publicProperty =trueprivateval dividerPaint =Paint().apply {
color =Color.GRAY
}
overridefunafterDraw(canvas:Canvas?) {
super.afterDraw(canvas)
if (publicProperty) {
var i = editText.topVisibleLine
while (i <= editText.bottomVisibleLine) {
val startX = editText.paddingStart + editText.scrollX
val startY = editText.paddingTop + editText.layout.getLineBottom(i)
val stopX = editText.paddingLeft + editText.layout.width + editText.paddingRight
val stopY = editText.paddingTop + editText.layout.getLineBottom(i)
canvas?.drawLine( // draw divider for each visible line
startX.toFloat(), startY.toFloat(),
stopX.toFloat(), stopY.toFloat(),
dividerPaint
)
i++
}
}
}
}
Third, create an extension function to improve code readability when adding your plugin to a PluginSupplier:
fun PluginSupplier.lineDividers(block:CustomPlugin.() ->Unit = {}) {
plugin(CustomPlugin(), block)
}
Finally, you can attach your plugin using DSL:
val pluginSupplier =PluginSupplier.create {
lineDividers {
publicProperty =true// whether should draw the dividers
}
...
}
editor.plugins(pluginSupplier)
Languages
The language modules provides support for programming languages. This includes syntax highlighting, code suggestions and source code parser. (Note that source code parser currently works only in language-javascript module, but it will be implemented for more languages in the future)
Gradle Dependency
Select your language and add it's dependency to your module's build.gradle file:
LanguageParser is responsible for analyzing the source code. The code editor does not use this component directly.
SuggestionProvider is responsible for collecting the names of functions, fields, and keywords within your file scope. The code editor use this component to display the list of code suggestions.
LanguageStyler is responsible for syntax highlighting. The code editor use this component to display syntax highlight spans on the screen.
LanguageParser
LanguageParser is an interface which detects syntax errors so you can display them in the TextProcessor later.
To create a custom parser you need to implement execute method that will return a ParseResult. If ParseResult contains an exception it means that the source code can't compile and contains syntax errors. You can highlight an error line by calling editor.setErrorLine(lineNumber) method.
Remember that you shouldn't use this method on the main thread.
SuggestionProvider is an interface which provides code suggestions to display them in the TextProcessor.
The text scanning is done on a per-line basis. When the user edits code on a single line, that line is re-scanned by the current SuggestionsProvider implementation, so you can keep your suggestions list up to date. This is done by calling the processLine method. This method is responsible for parsing a line of text and saving the code suggestions for that line.
After calling setTextContent the code editor will call processLine for each line to find all possible code suggestions.
classCustomProvider : SuggestionProvider {
// You can use WordsManager// if you don't want to write the language-specific implementationprivateval wordsManager =WordsManager()
overridefungetAll(): Set<Suggestion> {
return wordsManager.getWords()
}
overridefunprocessLine(lineNumber:Int, text:String) {
wordsManager.processLine(lineNumber, text)
}
overridefundeleteLine(lineNumber:Int) {
wordsManager.deleteLine(lineNumber)
}
overridefunclearLines() {
wordsManager.clearLines()
}
}
LanguageStyler
LanguageStyler is an interface which provides syntax highlight spans to display them in the TextProcessor.
The execute method will be executed on the background thread every time the text changes. You can use regex or lexer to find the keywords in text.
Remember: the more spans you add, the more time it takes to render on the main thread.
classCustomStyler : LanguageStyler {
overridefunexecute(source:String, scheme:ColorScheme): List<SyntaxHighlightSpan> {
val syntaxHighlightSpans = mutableListOf<SyntaxHighlightSpan>()
// TODO Implement syntax highlightingreturn syntaxHighlightSpans
}
}
Someone opened an RFP with F-Droid to get your app listed there. F-Droid highly recommends that summary & description are maintained in the app's repo (here) itself – so changes/updates/adjustments can be made by those knowing best, without the need of opening merge requests whenever that happens. Fastlane can be used with other "stores" as well, so it gives you "one place to rule them all".
You might wish to build on this (e.g. adding screenshots, a featureGraphic, per-release changelogs etc). For orientation, you might find my Fastlane Cheat-Sheet useful.
PS: The format I've used for full_description.txt is what I call "Markdown lite", and has proven to work best when using different targets (like F-Droid, my repo, Play Store). Some pointers on this can be found in my wiki. It's quite simple, basically: separate paragraphs and lists by "empty lines", use "simple HTML" tags (b,i,u,a) where appropriate, avoid using headers (h1,h2,h3,..); that's all.
Describe the bug
No files can be opened when selecting "Open with Squircle IDE" from several external file explorers.
To Reproduce
Steps to reproduce the behavior:
Navigate to a file, tap it.
Select "open with Squircle IDE"
Error is always returned: "File not found"
Expected behavior
File will open in in the app to be edited.
I am trying to edit bash shell scripts in the termux .shortcuts folder. I can open the files with several managers (Amaze, Material Files, Ghost Commander, Files etc.) however when I tap on the file and select open with Squircle IDE. I get an error "File not found" I have tried several files, they all result in the same error.
All of these files can be opened and edited in several other editors. Only Squircle returns this error.
Please consider making a Pull Request if you are capable of doing so.
App Version:
2020.2.2 Develop branch, means all version has the bug.
Affected Device(s):
ViVo x27 with Android 10
Describe the bug
java.lang.ArrayIndexOutOfBoundsException: length=10; index=-1
at java.util.ArrayList.get(ArrayList.java:439)
at com.lightteam.editorkit.feature.undoredo.UndoStack.pop(UndoStack.kt:37)
at com.lightteam.editorkit.internal.UndoRedoEditText.redo(UndoRedoEditText.kt:119)
at com.lightteam.modpeide.ui.editor.fragments.EditorFragment.onRedoButton(EditorFragment.kt:616)
at com.lightteam.modpeide.ui.editor.utils.ToolbarManager$bind$5.onClick(ToolbarManager.kt:129)
at android.view.View.performClick(View.java:7187)
at android.view.View.performClickInternal(View.java:7164)
at android.view.View.access$3500(View.java:813)
at android.view.View$PerformClick.run(View.java:27649)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:230)
at android.app.ActivityThread.main(ActivityThread.java:7742)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:508)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1034)
fun pop(): TextChange {
// TODO: size must >= 1
val item = stack[size - 1]
stack.removeAt(size - 1)
currentSize -= item.newText.length + item.oldText.length
return item
}
Would you mind publishing the APK here at Github (e.g. via releases/, see Creating releases in the Github Help) for folks without Playstore to grab it? I'd then offer to serve it via my F-Droid compatible repo, including automated updates.
I'd like to include your app to F-Droid, which is a catalogue of FOSS apps and client and server software for its distribution. You can read more about F-Droid at http://f-droid.org/. As per inclusion policy, I must ask for your permission to do so. As for inclusion progress, you can track it here: https://gitlab.com/fdroid/fdroiddata/-/merge_requests/8118.
If you agree, I also must ask you for a separate gradle flavor without com.google.android.play.core library, which is not FOSS.
Hi, I have a suggestion for your code editor app from the Google Play Store and F-Droid app- Please add the ability to open a text file or source code file in this app when the user is selecting an app to open a text file or source code file.
And make it so that this app can be used to open any source code file in any computer programming language, like PY files (files with ‘.py’ filename suffixes) for Python, JS filesfor JavaScript and so on.
I would rate your app 5 stars on the Google Play Store when my suggestion is implemented. I also suggest adding the ability to edit text files and source code files saved in a cloud storage like Google Drive
Please consider making a Pull Request if you are capable of doing so.
Is your feature request related to a problem? Please describe.
What makes me frustrated is that every time I finish my code, I have to save my files myself. Sometimes I forget to, and next time I open this app, seeing my unsaved code, I feel upset for having to write the code again.
Describe the solution you'd like
Automatically save files once in a period of time, e.g. 1min. The time should be adjustable.
Describe alternatives you've considered
Save files myself. But it's inconvenient.
Change in screen orientation/rotation causes new tab to be opened with active file.
App Version:
2022.2.1 (10013)
Affected Device(s):
Oneplus 8T, Android 11
Describe the bug
If a file/document is opened into Squircle by using the "Open With" dialog, changing the screen orientation (ie rotating the device) causes a new tab to be created with the active file. This only occurs when the Squircle app is not already open in the background.
To Reproduce
Close running instance of Squircle and remove it from the recent apps list.
From any file manager app select a text file and "Open with" Squircle
Rotate the screen
Result: A new tab is created with the recently opened file each time screen orientation changes.
Expected behavior
Screen rotation should not create a new tab with the recently opened file.
Affected Device(s):
Samsung A037_su with Android 12
Describe the bug
Its relatively minor and avoidable, as it only happens if you use keyboard keys (hold shift + move arrows) AND move UPwards. Doing the same but moving down in the file as you select: when you hit Ctrl-C: no problems.
To Reproduce
Steps to reproduce the behavior:
select text, moving from low to high
copy or cut
immediate window close, no auto-save happens.
Stack trace I got from logcat
FATAL EXCEPTION: main
Process: com.blacksquircle.ui, PID: 7249
java.lang.StringIndexOutOfBoundsException
at android.text.SpannableStringBuilder.(SpannableStringBuilder.java:64)
at androidx.emoji2.text.SpannableBuilder.(SpannableBuilder.java:86)
at androidx.emoji2.text.SpannableBuilder.subSequence(SpannableBuilder.java:125)
at com.blacksquircle.ui.editorkit.ExtensionsKt.getSelectedText(Extensions.kt:29)
at com.blacksquircle.ui.editorkit.ExtensionsKt.cut(Extensions.kt:37)
at com.blacksquircle.ui.feature.editor.ui.fragment.EditorFragment.onCutButton(EditorFragment.kt:522)
at com.blacksquircle.ui.feature.editor.ui.fragment.EditorFragment$observeViewModel$8$pluginSupplier$1$4.invoke$lambda-0(EditorFragment.kt:302)
at com.blacksquircle.ui.feature.editor.ui.fragment.EditorFragment$observeViewModel$8$pluginSupplier$1$4.$r8$lambda$K7_57HbTZi17QI2z9RQVsedwYj0(Unknown Source:0)
at com.blacksquircle.ui.feature.editor.ui.fragment.EditorFragment$observeViewModel$8$pluginSupplier$1$4$$ExternalSyntheticLambda0.onShortcut(Unknown Source:2)
at com.blacksquircle.ui.editorkit.plugin.shortcuts.ShortcutsPlugin.onKeyDown(ShortcutsPlugin.kt:50)
at com.blacksquircle.ui.editorkit.widget.TextProcessor.onKeyDown(TextProcessor.kt:128)
at android.view.KeyEvent.dispatch(KeyEvent.java:3664)
at android.view.View.dispatchKeyEvent(View.java:14927)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1991)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1991)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1991)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1991)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1991)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1991)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1991)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1991)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1991)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1991)
at com.android.internal.policy.DecorView.superDispatchKeyEvent(DecorView.java:1004)
at com.android.internal.policy.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1952)
at android.app.Activity.dispatchKeyEvent(Activity.java:4225)
at androidx.core.app.ComponentActivity.superDispatchKeyEvent(ComponentActivity.java:126)
at androidx.core.view.KeyEventDispatcher.dispatchKeyEvent(KeyEventDispatcher.java:86)
at androidx.core.app.ComponentActivity.dispatchKeyEvent(ComponentActivity.java:144)
at androidx.appcompat.app.AppCompatActivity.dispatchKeyEvent(AppCompatActivity.java:601)
at androidx.appcompat.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:60)
at androidx.appcompat.app.AppCompatDelegateImpl$AppCompatWindowCallback.dispatchKeyEvent(AppCompatDelegateImpl.java:3106)
at com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:823)
at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:7722)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:7545)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6922)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6979)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6945)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:7143)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6953)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:7200)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6926)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6979)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6945)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6953)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6926)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6979)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6945)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:7176)
at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:7363)
at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:3411)
at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:2972)
at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:2963)
at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:3388)
at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:154)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:335)
at android.os.Looper.loopOnce(Looper.java:186)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
Problem: Google's text selection pin is really awkward, and there exists a solution in this app, it just needs to be expanded upon.
Two things: selecting the end of a line requires my finger to be just perfectly lined up at the very right edge of my screen. Further, once I get there, the last character is visible only at the very edge, and I would like to scroll a bit further to the right.
Then, there is the zoom handler that already exists in this app. It's wonderful, however being able to zoom out even further would be fantastic.
The solution here to resolve both and make this text editor even more powerful, fast, and efficient would be to 1) allow a user to zoom out much more than the current cap. and 2) if a user touches a part of the screen not containing text, the app responds by scrolling instead of selecting. If it's possible for the app to detect immediate movement of a finger on the screen, then let that be the trigger. As an example iOS's Textastic developer got this down pretty nicely. I have attached a video displaying the functionality.
I would also like to add that this text editor is absolutely hands down the best and I tested over 20 of them. Beautiful job, I will be contributing a couple gallons of coffee to this project especially if this little hiccup can be fixed, as it will in turn allow me to make this my primary text editor and have fun with some projects.
Added: Support for Groovy language (78db8084aff3d87f310883ea3bb7275f823c98bc)
Fixed: Bug when the running task in file explorer wasn't cancelled by clicking «Cancel» (incl. Delete, Copy, Cut) (ba6d6e3d54f8e184acdb7c74bd2699c612168dc4)
Completely reworked internal application logic and data flow
You can now open unknown files in editor by default (1ff5d71893b62b5695ec2cfb0924f6127885e9b1)
Bugfixes and minor improvements
Download via Google Play:
https://play.google.com/store/apps/details?id=com.brackeys.ui
Brackeys IDE – rebranding 🎉 (99479dc7ee07da91b88d62e666909bf3654af8fe)
Added: Support for following programming languages: ActionScript, C, C++, C#, HTML, Java, Kotlin, Lisp, Lua, Markdown, Python, SQL, Visual Basic and XML
Bugfixes and minor improvements
Download via Google Play:
https://play.google.com/store/apps/details?id=com.brackeys.ui
MarkdownView is an Android webview with the capablity of loading Markdown text or file and display it as HTML, it uses MarkdownJ and extends Android webview.