diff --git a/app/src/main/java/com/itsaky/androidide/fragments/EmptyStateFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/EmptyStateFragment.kt index ad19f86ec9..6754ef416b 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/EmptyStateFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/EmptyStateFragment.kt @@ -54,7 +54,7 @@ abstract class EmptyStateFragment : FragmentWithBinding { open fun onFragmentLongPressed() { val currentEditor = currentEditor ?: return - currentEditor.selectCurrentWord() + currentEditor.selectWordOrOperatorAtCursor() } private val gestureListener = 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 0a4ab5f6c8..16a8c553f8 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 @@ -44,6 +44,7 @@ import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguage import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguageProvider import com.itsaky.androidide.editor.processing.ProcessContext import com.itsaky.androidide.editor.processing.TextProcessorEngine +import com.itsaky.androidide.editor.utils.getOperatorRangeAt import com.itsaky.androidide.editor.schemes.IDEColorScheme import com.itsaky.androidide.editor.schemes.IDEColorSchemeProvider import com.itsaky.androidide.editor.snippets.AbstractSnippetVariableResolver @@ -1215,4 +1216,21 @@ constructor( log.error("Error setting selection from point", e) } } + + /** + * Selects the word at the cursor, or if none (e.g. on an operator), selects + * the operator at the cursor so the code-action toolbar can be shown. + */ + fun selectWordOrOperatorAtCursor() { + if (isReleased) return + selectCurrentWord() + if (cursor.isSelected) return + val line = cursor.leftLine + val column = cursor.leftColumn + val columnCount = text.getColumnCount(line) + if (column < 0 || column >= columnCount) return + val range = text.getOperatorRangeAt(line, column) ?: return + val (startCol, endCol) = range + setSelectionRegion(line, startCol, line, endCol) + } } diff --git a/editor/src/main/java/com/itsaky/androidide/editor/utils/OperatorSelection.kt b/editor/src/main/java/com/itsaky/androidide/editor/utils/OperatorSelection.kt new file mode 100644 index 0000000000..63f4d6247f --- /dev/null +++ b/editor/src/main/java/com/itsaky/androidide/editor/utils/OperatorSelection.kt @@ -0,0 +1,118 @@ +/* + * 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.utils + +import io.github.rosemoe.sora.text.Content + +/** + * Java/Kotlin operators for long-press selection, ordered by length descending + * so longer matches are tried first (e.g. `>>>` before `>>` before `>`). + */ +private val OPERATORS: List = + listOf( + // 3-char + ">>>", + "<<=", + ">>=", + ">>>=", + // 2-char + "==", + "!=", + "<=", + ">=", + "+=", + "-=", + "*=", + "/=", + "%=", + "&=", + "|=", + "^=", + "++", + "--", + "&&", + "||", + "<<", + ">>", + "->", + "?.", + "?:", + "..", + "!!", + "::", + // 1-char + "+", + "-", + "*", + "/", + "%", + "=", + "<", + ">", + "!", + "&", + "|", + "^", + "~", + "?", + ":", + ";", + ",", + ".", + "@", + "(", + ")", + "[", + "]", + "{", + "}", + ) + +/** + * Returns the column range of the operator at the given position, if any. + * Columns are 0-based; endColumn is exclusive (one past the last character). + * + * @param lineContent The full line text. + * @param column Cursor column (0-based). + * @return (startColumn, endColumnExclusive) or null if no operator at this position. + */ +fun getOperatorRangeAt(lineContent: CharSequence, column: Int): Pair? { + if (column < 0 || column >= lineContent.length) return null + val suffix = lineContent.subSequence(column, lineContent.length) + for (op in OPERATORS) { + if (op.length <= suffix.length && suffix.subSequence(0, op.length) == op) { + return column to (column + op.length) + } + } + return null +} + +/** + * Returns the column range of the operator at (line, column) in [content], if any. + * Columns are 0-based; endColumn is exclusive. + */ +fun Content.getOperatorRangeAt(line: Int, column: Int): Pair? { + if (line < 0 || line >= lineCount) return null + val lineContent = getLine(line) + val maxColumn = lineContent.length + if (column < 0 || column > maxColumn) return null + val range = getOperatorRangeAt(lineContent, column) ?: return null + val (start, end) = range + if (end > maxColumn) return null + return range +}