From 540e33734ba9e34900852fecff89d2fb7b854fab Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Tue, 24 Oct 2023 18:07:11 +0530 Subject: [PATCH] refactor: extract IEditor impl to delegate class --- .../androidide/editor/ui/EditorFeatures.kt | 149 ++++++++++++++++ .../itsaky/androidide/editor/ui/IDEEditor.kt | 161 +++--------------- 2 files changed, 168 insertions(+), 142 deletions(-) create mode 100644 editor/src/main/java/com/itsaky/androidide/editor/ui/EditorFeatures.kt 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 }