diff --git a/editor/src/main/java/com/itsaky/androidide/editor/ui/EditorFeatures.kt b/editor/src/main/java/com/itsaky/androidide/editor/ui/EditorFeatures.kt
new file mode 100644
index 000000000..a33955adc
--- /dev/null
+++ b/editor/src/main/java/com/itsaky/androidide/editor/ui/EditorFeatures.kt
@@ -0,0 +1,149 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.editor.ui
+
+import com.itsaky.androidide.editor.api.IEditor
+import com.itsaky.androidide.editor.ui.IDEEditor.Companion.log
+import com.itsaky.androidide.models.Position
+import com.itsaky.androidide.models.Range
+import java.io.File
+
+/**
+ * Handler which implements various features in [IEditor].
+ *
+ * @author Akash Yadav
+ */
+class EditorFeatures(
+ var editor: IDEEditor? = null
+) : IEditor {
+
+ override fun getFile(): File? = withEditor { _file }
+
+ override fun isModified(): Boolean = withEditor { this.isModified } ?: false
+
+ override fun setSelection(position: Position) {
+ withEditor {
+ setSelection(position.line, position.column)
+ }
+ }
+
+ override fun setSelection(start: Position, end: Position) {
+ withEditor {
+ if (!isValidPosition(start, true) || !isValidPosition(end, true)) {
+ log.warn("Invalid selection range: start=$start end=$end")
+ return@withEditor
+ }
+
+ setSelectionRegion(start.line, start.column, end.line, end.column)
+ }
+ }
+
+ override fun getCursorLSPRange(): Range = withEditor {
+ val end = cursor.right().let {
+ Position(line = it.line, column = it.column, index = it.index)
+ }
+ return@withEditor Range(cursorLSPPosition, end)
+ } ?: Range.NONE
+
+ override fun getCursorLSPPosition(): Position = withEditor {
+ return@withEditor cursor.left().let {
+ Position(line = it.line, column = it.column, index = it.index)
+ }
+ } ?: Position.NONE
+
+ override fun validateRange(range: Range) {
+ withEditor {
+ val start = range.start
+ val end = range.end
+ val text = text
+ val lineCount = text.lineCount
+
+ start.line = 0.coerceAtLeast(start.line).coerceAtMost(lineCount - 1)
+ start.column = 0.coerceAtLeast(start.column).coerceAtMost(text.getColumnCount(start.line))
+
+ end.line = 0.coerceAtLeast(end.line).coerceAtMost(lineCount - 1)
+ end.column = 0.coerceAtLeast(end.column).coerceAtMost(text.getColumnCount(end.line))
+ }
+ }
+
+ override fun isValidRange(range: Range?, allowColumnEqual: Boolean): Boolean = withEditor {
+ if (range == null) {
+ return@withEditor false
+ }
+ val start = range.start
+ val end = range.end
+ return@withEditor isValidPosition(start, allowColumnEqual)
+ // make sure start position is before end position
+ && isValidPosition(end, allowColumnEqual) && start < end
+ } ?: false
+
+ override fun isValidPosition(position: Position?, allowColumnEqual: Boolean): Boolean =
+ withEditor {
+ return@withEditor if (position == null) {
+ false
+ } else isValidLine(position.line) &&
+ isValidColumn(position.line, position.column, allowColumnEqual)
+ } ?: false
+
+ override fun isValidLine(line: Int): Boolean =
+ withEditor { line >= 0 && line < text.lineCount } ?: false
+
+ override fun isValidColumn(line: Int, column: Int, allowColumnEqual: Boolean): Boolean =
+ withEditor {
+ val columnCount = text.getColumnCount(line)
+ return@withEditor column >= 0 && (column < columnCount || allowColumnEqual && column == columnCount)
+ } ?: false
+
+ override fun append(text: CharSequence?): Int = withEditor {
+ val content = getText()
+ if (lineCount <= 0) {
+ return@withEditor 0
+ }
+
+ val line = lineCount - 1
+ var col = content.getColumnCount(line)
+ if (col < 0) {
+ col = 0
+ }
+ content.insert(line, col, text)
+ return@withEditor line
+ } ?: -1
+
+ override fun replaceContent(newContent: CharSequence?) {
+ withEditor {
+ val lastLine = text.lineCount - 1
+ val lastColumn = text.getColumnCount(lastLine)
+ text.replace(0, 0, lastLine, lastColumn, newContent ?: "")
+ }
+ }
+
+ override fun goToEnd() {
+ withEditor {
+ val line = text.lineCount - 1
+ setSelection(line, 0)
+ }
+ }
+
+ private inline fun withEditor(crossinline action: IDEEditor.() -> T): T? {
+ return this.editor?.run {
+ if (isReleased) {
+ null
+ } else action()
+ }
+ }
+}
\ No newline at end of file
diff --git a/editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.kt b/editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.kt
index e6017ae0d..6ec290679 100644
--- a/editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.kt
+++ b/editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.kt
@@ -20,13 +20,11 @@ package com.itsaky.androidide.editor.ui
import android.content.Context
import android.graphics.Rect
import android.os.Bundle
-import android.os.Debug
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet
import android.view.inputmethod.EditorInfo
import androidx.annotation.StringRes
-import androidx.annotation.UiThread
import com.blankj.utilcode.util.FileUtils
import com.itsaky.androidide.editor.R.string
import com.itsaky.androidide.editor.adapters.CompletionListAdapter
@@ -80,7 +78,6 @@ import io.github.rosemoe.sora.event.ContentChangeEvent
import io.github.rosemoe.sora.event.SelectionChangeEvent
import io.github.rosemoe.sora.lang.EmptyLanguage
import io.github.rosemoe.sora.lang.Language
-import io.github.rosemoe.sora.text.Content
import io.github.rosemoe.sora.widget.CodeEditor
import io.github.rosemoe.sora.widget.EditorSearcher
import io.github.rosemoe.sora.widget.IDEEditorSearcher
@@ -108,15 +105,17 @@ open class IDEEditor @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
- defStyleRes: Int = 0
-) : CodeEditor(context, attrs, defStyleAttr, defStyleRes), IEditor, ILspEditor {
+ defStyleRes: Int = 0,
+ private val editorFeatures: EditorFeatures = EditorFeatures()
+) : CodeEditor(context, attrs, defStyleAttr, defStyleRes), IEditor by editorFeatures, ILspEditor {
+
+ internal var _file: File? = null
private var _actionsMenu: EditorActionsMenu? = null
private var _signatureHelpWindow: SignatureHelpWindow? = null
private var _diagnosticWindow: DiagnosticWindow? = null
- private var file: File? = null
private var fileVersion = 0
- private var isModified = false
+ internal var isModified = false
private val selectionChangeHandler = Handler(Looper.getMainLooper())
private var selectionChangeRunner: Runnable? = Runnable {
@@ -172,7 +171,7 @@ open class IDEEditor @JvmOverloads constructor(
private const val SELECTION_CHANGE_DELAY = 500L
- private val log = ILogger.newInstance("IDEEditor")
+ internal val log = ILogger.newInstance("IDEEditor")
/**
* Create input type flags for the editor.
@@ -188,11 +187,10 @@ open class IDEEditor @JvmOverloads constructor(
}
init {
- initEditor()
- }
-
- override fun getFile(): File? {
- return this.file
+ run {
+ editorFeatures.editor = this
+ initEditor()
+ }
}
/**
@@ -203,135 +201,12 @@ open class IDEEditor @JvmOverloads constructor(
return
}
- this.file = file
+ this._file = file
file?.also {
dispatchDocumentOpenEvent()
}
}
- override fun isModified(): Boolean {
- return this.isModified
- }
-
- override fun setSelection(position: Position) {
- if (isReleased) {
- return
- }
- setSelection(position.line, position.column)
- }
-
- override fun setSelection(start: Position, end: Position) {
- if (isReleased) {
- return
- }
- if (!isValidPosition(start, true) || !isValidPosition(end, true)) {
- log.warn("Invalid selection range: start=$start end=$end")
- return
- }
-
- setSelectionRegion(start.line, start.column, end.line, end.column)
- }
-
- override fun getCursorLSPRange(): Range {
- val end = cursor.right().let {
- Position(line = it.line, column = it.column, index = it.index)
- }
- return Range(cursorLSPPosition, end)
- }
-
- override fun getCursorLSPPosition(): Position {
- return cursor.left().let {
- Position(line = it.line, column = it.column, index = it.index)
- }
- }
-
- override fun validateRange(range: Range) {
- if (isReleased) {
- return
- }
- val start = range.start
- val end = range.end
- val text = text
- val lineCount = text.lineCount
-
- start.line = 0.coerceAtLeast(start.line).coerceAtMost(lineCount - 1)
- start.column = 0.coerceAtLeast(start.column).coerceAtMost(text.getColumnCount(start.line))
-
- end.line = 0.coerceAtLeast(end.line).coerceAtMost(lineCount - 1)
- end.column = 0.coerceAtLeast(end.column).coerceAtMost(text.getColumnCount(end.line))
- }
-
- override fun isValidRange(range: Range?, allowColumnEqual: Boolean): Boolean {
- if (isReleased) {
- return false
- }
- if (range == null) {
- return false
- }
- val start = range.start
- val end = range.end
- return isValidPosition(start, allowColumnEqual)
- // make sure start position is before end position
- && isValidPosition(end, allowColumnEqual) && start < end
- }
-
- override fun isValidPosition(position: Position?, allowColumnEqual: Boolean): Boolean {
- if (isReleased) {
- return false
- }
- return if (position == null) {
- false
- } else isValidLine(position.line) &&
- isValidColumn(position.line, position.column, allowColumnEqual)
- }
-
- override fun isValidLine(line: Int): Boolean {
- if (isReleased) {
- return false
- }
- return line >= 0 && line < text.lineCount
- }
-
- override fun isValidColumn(line: Int, column: Int, allowEqual: Boolean): Boolean {
- val columnCount = text.getColumnCount(line)
- return column >= 0 && (column < columnCount || allowEqual && column == columnCount)
- }
-
- override fun append(text: CharSequence?): Int {
- if (isReleased) {
- return 0
- }
- val content = getText()
- if (lineCount <= 0) {
- return 0
- }
- val line = lineCount - 1
- var col = content.getColumnCount(line)
- if (col < 0) {
- col = 0
- }
- content.insert(line, col, text)
- return line
- }
-
- @UiThread
- override fun replaceContent(newContent: CharSequence?) {
- if (isReleased) {
- return
- }
- val lastLine = text.lineCount - 1
- val lastColumn = text.getColumnCount(lastLine)
- text.replace(0, 0, lastLine, lastColumn, newContent ?: "")
- }
-
- override fun goToEnd() {
- if (isReleased) {
- return
- }
- val line = text.lineCount - 1
- setSelection(line, 0)
- }
-
override fun setLanguageServer(server: ILanguageServer?) {
if (isReleased) {
return
@@ -498,10 +373,12 @@ open class IDEEditor @JvmOverloads constructor(
languageServer = null
languageClient = null
- file = null
+ _file = null
fileVersion = 0
markUnmodified()
+ editorFeatures.editor = null
+
selectionChangeRunner?.also { selectionChangeHandler.removeCallbacks(it) }
selectionChangeRunner = null
@@ -622,10 +499,10 @@ open class IDEEditor @JvmOverloads constructor(
if (isReleased) {
return
}
- if (getFile() == null) {
+ if (file == null) {
return
}
- val saveEvent = DocumentSaveEvent(getFile()!!.toPath())
+ val saveEvent = DocumentSaveEvent(file!!.toPath())
EventBus.getDefault().post(saveEvent)
}
@@ -636,7 +513,7 @@ open class IDEEditor @JvmOverloads constructor(
@Suppress("unused")
@Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
open fun onColorSchemeInvalidated(event: ColorSchemeInvalidatedEvent?) {
- val file = getFile() ?: return
+ val file = file ?: return
setupLanguage(file)
}
@@ -817,7 +694,7 @@ open class IDEEditor @JvmOverloads constructor(
}
val (file1, range) = locations[0]
- if (DocumentUtils.isSameFile(file1, getFile()!!.toPath())) {
+ if (DocumentUtils.isSameFile(file1, file!!.toPath())) {
setSelection(range)
return@withContext
}