mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Replaced ActionMode searcher with bottom bar
This commit is contained in:
@@ -105,10 +105,7 @@ interface ActionItem {
|
||||
* Location marker for action items shown in 'Code actions' submenu in editor's text action
|
||||
* menu.
|
||||
*/
|
||||
EDITOR_CODE_ACTIONS("editor.codeActions"),
|
||||
|
||||
/** Location marker for action items show in 'Find & Replace' action mode menu in editor. */
|
||||
EDITOR_SEARCH_ACTION_MODE("editor.searchActionMode");
|
||||
EDITOR_CODE_ACTIONS("editor.codeActions");
|
||||
|
||||
override fun toString(): String {
|
||||
return id
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor.find
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
|
||||
/**
|
||||
* 'Go to next' action in editor's search action mode.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class GoToNextAction() : SearchActionModeAction() {
|
||||
override val id: String = "editor.find.next"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.next)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_next)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)!!
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return editor.searcher.gotoNext()
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor.find
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.R.string
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
|
||||
/**
|
||||
* 'Go to previous' action in editor's search action mode.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class GoToPreviousAction() : SearchActionModeAction() {
|
||||
override val id: String = "editor.find.previous"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(string.previous)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_previous)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)!!
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return editor.searcher.gotoPrevious()
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor.find
|
||||
|
||||
import android.view.MenuItem
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem.Location
|
||||
import com.itsaky.androidide.actions.ActionItem.Location.EDITOR_SEARCH_ACTION_MODE
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class SearchActionModeAction : EditorRelatedAction() {
|
||||
override var location: Location = EDITOR_SEARCH_ACTION_MODE
|
||||
|
||||
override fun getShowAsActionFlags(data: ActionData): Int {
|
||||
return MenuItem.SHOW_AS_ACTION_IF_ROOM
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor.find
|
||||
|
||||
import android.app.SearchManager
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.app.IDEActivity
|
||||
import com.itsaky.androidide.views.editor.IDEEditor
|
||||
import io.github.rosemoe.sora.widget.EditorSearcher.SearchOptions
|
||||
|
||||
/**
|
||||
* The [SearchView] action in the search action mode.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class SearchViewActionItem() : SearchActionModeAction() {
|
||||
override val id: String = "editor.search.view"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.text_to_search)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
return Unit
|
||||
}
|
||||
|
||||
override fun createActionView(data: ActionData): View? {
|
||||
val editor = getEditor(data) ?: return null
|
||||
val searchManager = editor.context.getSystemService(Context.SEARCH_SERVICE) as SearchManager
|
||||
|
||||
val search = SearchView(editor.context)
|
||||
search.isIconified = false
|
||||
search.queryHint = editor.context.getString(R.string.text_to_search)
|
||||
search.setSearchableInfo(
|
||||
searchManager.getSearchableInfo((editor.context as IDEActivity).componentName)
|
||||
)
|
||||
search.setOnQueryTextListener(OnQueryListener(editor))
|
||||
search.performClick()
|
||||
search.setIconifiedByDefault(false)
|
||||
return search
|
||||
}
|
||||
|
||||
private class OnQueryListener(val editor: IDEEditor) : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
editor.searcher.gotoNext()
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
if (TextUtils.isEmpty(newText)) {
|
||||
editor.searcher.stopSearch()
|
||||
return false
|
||||
}
|
||||
|
||||
editor.searcher.search(newText!!, SearchOptions(false, false))
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,10 +28,6 @@ import com.itsaky.androidide.actions.editor.CutAction
|
||||
import com.itsaky.androidide.actions.editor.ExpandSelectionAction
|
||||
import com.itsaky.androidide.actions.editor.PasteAction
|
||||
import com.itsaky.androidide.actions.editor.SelectAllAction
|
||||
import com.itsaky.androidide.actions.editor.find.GoToNextAction
|
||||
import com.itsaky.androidide.actions.editor.find.GoToPreviousAction
|
||||
import com.itsaky.androidide.actions.editor.find.ReplaceAction
|
||||
import com.itsaky.androidide.actions.editor.find.SearchViewActionItem
|
||||
import com.itsaky.androidide.actions.etc.DaemonStatusAction
|
||||
import com.itsaky.androidide.actions.etc.FileTreeAction
|
||||
import com.itsaky.androidide.actions.etc.FindActionMenu
|
||||
@@ -72,12 +68,6 @@ class EditorActivityActions {
|
||||
registry.registerAction(CopyAction(context))
|
||||
registry.registerAction(PasteAction(context))
|
||||
registry.registerAction(FormatCodeAction(context))
|
||||
|
||||
// editor search actions
|
||||
registry.registerAction(SearchViewActionItem(context))
|
||||
registry.registerAction(GoToNextAction(context))
|
||||
registry.registerAction(GoToPreviousAction(context))
|
||||
registry.registerAction(ReplaceAction(context))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
||||
@@ -41,8 +41,11 @@ import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.FrameLayout;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -68,6 +71,7 @@ import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
|
||||
import com.itsaky.androidide.utils.FileUtil;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.LSPUtils;
|
||||
import com.itsaky.androidide.utils.SingleTextWatcher;
|
||||
import com.itsaky.androidide.utils.TypefaceUtils;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
@@ -83,6 +87,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import io.github.rosemoe.sora.lang.EmptyLanguage;
|
||||
import io.github.rosemoe.sora.lang.Language;
|
||||
import io.github.rosemoe.sora.text.LineSeparator;
|
||||
import io.github.rosemoe.sora.widget.EditorSearcher;
|
||||
import io.github.rosemoe.sora.widget.component.Magnifier;
|
||||
|
||||
/**
|
||||
@@ -91,18 +96,20 @@ import io.github.rosemoe.sora.widget.component.Magnifier;
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@SuppressLint("ViewConstructor") // This view is always dynamically created.
|
||||
public class CodeEditorView extends FrameLayout {
|
||||
public class CodeEditorView extends LinearLayout {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("CodeEditorView");
|
||||
private final File file;
|
||||
private final LayoutCodeEditorBinding binding;
|
||||
private final EditorSearchLayout searchLayout;
|
||||
|
||||
public CodeEditorView(
|
||||
@NonNull Context context, @NonNull File file, final @NonNull Range selection) {
|
||||
super(context);
|
||||
this.file = file;
|
||||
|
||||
this.binding = LayoutCodeEditorBinding.inflate(LayoutInflater.from(context));
|
||||
final var inflater = LayoutInflater.from(context);
|
||||
this.binding = LayoutCodeEditorBinding.inflate(inflater);
|
||||
this.binding.editor.setTypefaceText(TypefaceUtils.jetbrainsMono());
|
||||
this.binding.editor.setHighlightCurrentBlock(true);
|
||||
this.binding.editor.getProps().autoCompletionOnComposing = true;
|
||||
@@ -110,10 +117,16 @@ public class CodeEditorView extends FrameLayout {
|
||||
this.binding.editor.setColorScheme(new SchemeAndroidIDE());
|
||||
this.binding.editor.setLineSeparator(LineSeparator.LF);
|
||||
|
||||
this.searchLayout = new EditorSearchLayout(context, this.binding.editor);
|
||||
|
||||
this.binding.diagnosticTextContainer.setVisibility(GONE);
|
||||
|
||||
removeAllViews();
|
||||
addView(this.binding.getRoot());
|
||||
setOrientation(VERTICAL);
|
||||
|
||||
addView(this.binding.getRoot(), new LayoutParams(LayoutParams.MATCH_PARENT, 0, 1f));
|
||||
addView(
|
||||
this.searchLayout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
|
||||
|
||||
selection.validate();
|
||||
CompletableFuture.runAsync(
|
||||
@@ -399,7 +412,12 @@ public class CodeEditorView extends FrameLayout {
|
||||
}
|
||||
|
||||
public void beginSearch() {
|
||||
binding.editor.beginSearchMode();
|
||||
if (this.binding == null || searchLayout == null) {
|
||||
LOG.warn("Editor layout is null content=" + binding + ", searchLayout=" + searchLayout);
|
||||
return;
|
||||
}
|
||||
|
||||
searchLayout.beginSearchMode();
|
||||
}
|
||||
|
||||
/** Mark this files as saved. Even if it not saved. */
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.views.editor
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.text.TextWatcher
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.PopupMenu
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.databinding.LayoutFindInFileBinding
|
||||
import com.itsaky.androidide.utils.SingleTextWatcher
|
||||
import com.itsaky.androidide.views.editor.ReplaceAction.doReplace
|
||||
import io.github.rosemoe.sora.widget.EditorSearcher.SearchOptions
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* The search layout in [IDEEditor].
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@SuppressLint("ViewConstructor") // Always created dynamically
|
||||
class EditorSearchLayout(context: Context, val editor: IDEEditor) : FrameLayout(context) {
|
||||
|
||||
private var searchInputTextWatcher: TextWatcher? = null
|
||||
private var searchOptions = SearchOptions(true, false)
|
||||
private val findInFileBinding: LayoutFindInFileBinding
|
||||
private val optionsMenu: PopupMenu
|
||||
|
||||
init {
|
||||
findInFileBinding = LayoutFindInFileBinding.inflate(LayoutInflater.from(context))
|
||||
findInFileBinding.prev.setOnClickListener(::onSearchActionClick)
|
||||
findInFileBinding.next.setOnClickListener(::onSearchActionClick)
|
||||
findInFileBinding.replace.setOnClickListener(::onSearchActionClick)
|
||||
findInFileBinding.close.setOnClickListener(::onSearchActionClick)
|
||||
|
||||
optionsMenu = PopupMenu(context, findInFileBinding.moreOptions, Gravity.TOP)
|
||||
optionsMenu.menu.add(0, 0, 0, R.string.msg_ignore_case).apply {
|
||||
isCheckable = true
|
||||
isChecked = true
|
||||
}
|
||||
|
||||
optionsMenu.menu.add(0, 1, 0, R.string.msg_use_regex).apply {
|
||||
isCheckable = true
|
||||
isChecked = false
|
||||
}
|
||||
|
||||
optionsMenu.setOnMenuItemClickListener {
|
||||
return@setOnMenuItemClickListener if (it.isCheckable) {
|
||||
it.isChecked = !it.isChecked
|
||||
|
||||
val ignoreCase = searchOptions.ignoreCase
|
||||
val regex = searchOptions.useRegex
|
||||
searchOptions =
|
||||
when (it.itemId) {
|
||||
0 -> SearchOptions(it.isChecked, regex)
|
||||
1 -> SearchOptions(ignoreCase, it.isChecked)
|
||||
else -> searchOptions
|
||||
}
|
||||
editor.searcher?.updateSearchOptions(searchOptions)
|
||||
|
||||
true
|
||||
} else false
|
||||
}
|
||||
|
||||
findInFileBinding.root.visibility = GONE
|
||||
findInFileBinding.moreOptions.setOnClickListener { optionsMenu.show() }
|
||||
|
||||
addView(
|
||||
findInFileBinding.root,
|
||||
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
)
|
||||
}
|
||||
|
||||
fun beginSearchMode() {
|
||||
searchInputTextWatcher = SearchInputTextChangeListener(editor)
|
||||
findInFileBinding.searchInput.addTextChangedListener(searchInputTextWatcher)
|
||||
findInFileBinding.searchInput.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_NEXT) {
|
||||
onSearchActionClick(findInFileBinding.next)
|
||||
}
|
||||
false
|
||||
}
|
||||
findInFileBinding.root.visibility = VISIBLE
|
||||
}
|
||||
|
||||
private fun onSearchActionClick(v: View) {
|
||||
val searcher = editor.searcher
|
||||
if (v.id == findInFileBinding.close.id) {
|
||||
if (this.searchInputTextWatcher == null) {
|
||||
return
|
||||
}
|
||||
findInFileBinding.searchInput.removeTextChangedListener(this.searchInputTextWatcher)
|
||||
findInFileBinding.root.visibility = GONE
|
||||
this.searchInputTextWatcher = null
|
||||
}
|
||||
if (!searcher.hasQuery()) {
|
||||
return
|
||||
}
|
||||
if (v.id == findInFileBinding.prev.id) {
|
||||
searcher.gotoPrevious()
|
||||
return
|
||||
}
|
||||
if (v.id == findInFileBinding.next.id) {
|
||||
searcher.gotoNext()
|
||||
return
|
||||
}
|
||||
if (v.id == findInFileBinding.replace.id) {
|
||||
doReplace(editor)
|
||||
}
|
||||
}
|
||||
|
||||
inner class SearchInputTextChangeListener(val editor: IDEEditor?) : SingleTextWatcher() {
|
||||
|
||||
override fun onTextChanged(
|
||||
s: CharSequence,
|
||||
start: Int,
|
||||
before: Int,
|
||||
count: Int,
|
||||
) {
|
||||
if (editor == null) {
|
||||
return
|
||||
}
|
||||
if (TextUtils.isEmpty(s)) {
|
||||
editor.searcher.stopSearch()
|
||||
return
|
||||
}
|
||||
|
||||
// Handle bad regexp
|
||||
val query =
|
||||
s.toString().let {
|
||||
if (searchOptions.useRegex) {
|
||||
try {
|
||||
Pattern.compile(it)
|
||||
it
|
||||
} catch (error: Throwable) {
|
||||
""
|
||||
}
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
if (query.isNotBlank()) {
|
||||
editor.searcher.search(query, searchOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -870,12 +870,7 @@ public class IDEEditor extends CodeEditor implements com.itsaky.androidide.edito
|
||||
|
||||
@Override
|
||||
public void beginSearchMode() {
|
||||
final var callback = new SearchActionMode(this);
|
||||
if (getContext() instanceof AppCompatActivity) {
|
||||
((AppCompatActivity)getContext()).startSupportActionMode(callback);
|
||||
} else {
|
||||
LOG.error("Unable start search action mode. Activity must inherit AppCompatActivity.");
|
||||
}
|
||||
throw new UnsupportedOperationException("Search ActionMode is not supported. Use CodeEditorView.beginSearch() instead.");
|
||||
}
|
||||
|
||||
protected void dispatchDocumentSaveEvent() {
|
||||
|
||||
+69
-87
@@ -1,87 +1,69 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor.find
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.databinding.LayoutEditorFindReplaceBinding
|
||||
import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/**
|
||||
* Shows dialog to replace one or all the matched text in editor searcher.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ReplaceAction() : SearchActionModeAction() {
|
||||
|
||||
override val id: String = "editor.search.replace"
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.replace)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_replace)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val context = data[Context::class.java]!!
|
||||
val editor = getEditor(data)!!
|
||||
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
val binding = LayoutEditorFindReplaceBinding.inflate(LayoutInflater.from(context))
|
||||
val builder = DialogUtils.newMaterialDialogBuilder(context)
|
||||
builder.setTitle(R.string.replace)
|
||||
builder.setView(binding.root)
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
builder.setPositiveButton(R.string.replace) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
val input = binding.replacementInput.editText
|
||||
if (input == null) {
|
||||
log.error("Unable to perform replace action. Input field is null")
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
editor.searcher.replaceThis(input.text.toString())
|
||||
}
|
||||
builder.setNeutralButton(R.string.replaceAll) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
val input = binding.replacementInput.editText
|
||||
if (input == null) {
|
||||
log.error("Unable to perform replace action. Input field is null")
|
||||
return@setNeutralButton
|
||||
}
|
||||
|
||||
editor.searcher.replaceAllAsync(input.text.toString()).whenComplete { _, error ->
|
||||
if (error != null) {
|
||||
log.error("Unable to replace all matched text", error)
|
||||
} else {
|
||||
editor.post { data[ActionMode::class.java]?.finish() }
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.show()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.views.editor
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.databinding.LayoutEditorFindReplaceBinding
|
||||
import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/**
|
||||
* Handles the replace action while searching in file.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
object ReplaceAction {
|
||||
|
||||
private val log = ILogger.newInstance("ReplaceAction")
|
||||
|
||||
@JvmStatic
|
||||
fun doReplace(editor: IDEEditor) {
|
||||
val context = editor.context
|
||||
val binding = LayoutEditorFindReplaceBinding.inflate(LayoutInflater.from(context))
|
||||
val builder = DialogUtils.newMaterialDialogBuilder(context)
|
||||
builder.setTitle(R.string.replace)
|
||||
builder.setView(binding.root)
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
builder.setPositiveButton(R.string.replace) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
val input = binding.replacementInput.editText
|
||||
if (input == null) {
|
||||
log.error("Unable to perform replace action. Input field is null")
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
editor.searcher.replaceThis(input.text.toString())
|
||||
}
|
||||
builder.setNeutralButton(R.string.replaceAll) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
val input = binding.replacementInput.editText
|
||||
if (input == null) {
|
||||
log.error("Unable to perform replace action. Input field is null")
|
||||
return@setNeutralButton
|
||||
}
|
||||
|
||||
editor.searcher.replaceAllAsync(input.text.toString()).whenComplete { _, error ->
|
||||
if (error != null) {
|
||||
log.error("Unable to replace all matched text", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.views.editor
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.view.menu.MenuBuilder
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionsRegistry
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
|
||||
/**
|
||||
* Action mode for find and replace functions in editor.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class SearchActionMode(val editor: IDEEditor) : ActionMode.Callback {
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
if (mode == null) {
|
||||
return false
|
||||
}
|
||||
if (menu is MenuBuilder) {
|
||||
menu.setOptionalIconsVisible(true)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
if (mode == null || menu == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
menu.clear()
|
||||
val registry = ActionsRegistry.getInstance()
|
||||
registry.fillMenu(createData(mode), ActionItem.Location.EDITOR_SEARCH_ACTION_MODE, menu)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
editor.searcher.stopSearch()
|
||||
}
|
||||
|
||||
private fun createData(mode: ActionMode): ActionData {
|
||||
return ActionData().apply {
|
||||
put(Context::class.java, editor.context)
|
||||
put(IDEEditor::class.java, editor)
|
||||
put(CodeEditor::class.java, editor)
|
||||
put(ActionMode::class.java, mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@ open class IDEEditorSearcher(editor: IDEEditor) : EditorSearcher(editor) {
|
||||
throw RuntimeException("Unable get instance of editor", error)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSearchOptions(searchOptions: SearchOptions) {
|
||||
this.searchOptions = searchOptions
|
||||
}
|
||||
|
||||
fun replaceAllAsync(replacement: String): CompletableFuture<Void> {
|
||||
markSearching()
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/primaryDarkColor">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/search_input"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:hint="@string/text_to_search"
|
||||
android:imeOptions="actionNext"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
app:layout_constraintEnd_toStartOf="@id/more_options"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/more_options"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:src="@drawable/ic_more_vert_black_20dp"
|
||||
android:tint="@color/primaryIconColor"
|
||||
app:layout_constraintBottom_toBottomOf="@id/search_input"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/search_input"
|
||||
app:layout_constraintTop_toTopOf="@id/search_input" />
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/prev"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/previous"
|
||||
android:textAllCaps="true"
|
||||
|
||||
android:textColor="@color/primaryTextColor"
|
||||
app:layout_constraintEnd_toStartOf="@+id/next"
|
||||
app:layout_constraintHorizontal_weight="0.25"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/search_input"
|
||||
app:rippleColor="@color/secondaryColor" />
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/next"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/next"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/primaryTextColor"
|
||||
app:layout_constraintEnd_toStartOf="@+id/replace"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_weight="0.25"
|
||||
app:layout_constraintStart_toEndOf="@+id/prev"
|
||||
app:layout_constraintTop_toBottomOf="@+id/search_input"
|
||||
app:rippleColor="@color/secondaryColor" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/replace"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/replace"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/primaryTextColor"
|
||||
app:layout_constraintEnd_toStartOf="@+id/close"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_weight="0.25"
|
||||
app:layout_constraintStart_toEndOf="@+id/next"
|
||||
app:layout_constraintTop_toBottomOf="@+id/search_input"
|
||||
app:rippleColor="@color/secondaryColor" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/close"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/btn_close"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/primaryTextColor"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_weight="0.25"
|
||||
app:layout_constraintStart_toEndOf="@+id/replace"
|
||||
app:layout_constraintTop_toBottomOf="@+id/search_input"
|
||||
app:rippleColor="@color/secondaryColor" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -361,4 +361,7 @@
|
||||
<string name="idepref_editor_useSoftTabs_summary">Choose whether to use spaces instead of tab character (\\t).</string>
|
||||
<string name="btn_sponsor">Sponsor</string>
|
||||
<string name="btn_docs">Documentation</string>
|
||||
<string name="btn_close">Close</string>
|
||||
<string name="msg_ignore_case">Ignore case</string>
|
||||
<string name="msg_use_regex">Use regex</string>
|
||||
</resources>
|
||||
Binary file not shown.
Reference in New Issue
Block a user