# 文本编辑器操作接口(TextEditor)

TextEditor 接口提供了文本编辑器的核心操作能力,包括文本编辑、光标控制、选择操作等功能。所有文本编辑器扩展功能(TextEditorFunctionTextEditorFloatingMenuTextEditorToolMenu)都依赖此接口进行文本操作。

# 快速开始

下面是一个简单的文本操作示例:

public void onMenuClick(@NonNull PluginUI pluginUI, @NonNull TextEditor editor) {
    // 获取选中的位置
    int start = editor.getSelectionStart();
    int end = editor.getSelectionEnd();

    // 检查是否有选中文本
    if (start == end) {
        pluginUI.showToast("请先选中文本");
        return;
    }

    // 获取选中的文本
    String selectedText = editor.subText(start, end);

    // 转换为大写
    String upperCase = selectedText.toUpperCase();

    // 替换选中的文本
    editor.replaceText(start, end, upperCase);
}

# 接口概览

TextEditor 接口提供以下方法:

方法 说明
光标与选中位置
int getSelectionStart() 获取选中文本的起始位置(经过处理,确保 start ≤ end)
int getSelectionEnd() 获取选中文本的结束位置(经过处理,确保 end ≥ start)
int getRawSelectionStart() 获取原始的选中文本起始位置(未经处理)
int getRawSelectionEnd() 获取原始的选中文本结束位置(未经处理)
boolean hasTextSelected() 判断当前是否选中了文本
void setSelection(int, int) 设置选中文本位置
void setSelection(int) 设置光标位置
void showCursor() 显示光标
void pushSelectionToUndoBuffer() 将当前选中位置推送到撤销缓冲区
位置可见相关
void ensurePositionVisible(int, boolean) 确保指定位置在屏幕可见(可指定是否使用动画)
void ensurePositionVisible(int) 确保指定位置在屏幕可见(使用平滑滚动动画)
void ensureSelectionVisible() 确保选中位置在屏幕可见
文本操作
BufferedText getBufferedText() 获取文本编辑器的缓冲文本对象
int length() 获取文本总长度
void replaceText(int, int, CharSequence, int, int) 将指定范围的文本替换为指定文本的子串
void replaceText(int, int, CharSequence) 将指定范围的文本替换为新文本
void insertText(int, CharSequence) 向指定位置插入文本
void deleteText(int, int) 删除指定范围的文本
String subText(int, int) 截取指定范围的文本
String getSelectedText() 获取当前选中的文本内容
大批量编辑模式
void startLargeBatchEditingMode() 开始大批量编辑模式
void finishLargeBatchEditingMode() 结束大批量编辑模式
文件相关
String getFileName() 获取当前编辑的文件名
String getFilePath() 获取当前编辑的文件路径
boolean isChanged() 判断内容是否有未保存的更改
void save(ResultCallback) 保存文件(异步)
分页相关
boolean isPaginationMode() 判断是否处于分页模式
int getPageCount() 获取分页总数
int getCurrentPageIndex() 获取当前分页索引
void switchToPage(int, ResultCallback) 切换到指定分页(异步)
杂项
boolean isReadOnly() 判断当前文本编辑器是否处于只读模式
int getTabSize() 获取设置中配置的缩进大小
boolean isIndentWithTabs() 判断是否使用Tab字符进行缩进
int[] getBracketPositions() 获取光标处括号对的位置信息
int getAnotherBracketPosition() 获取光标处括号所对应的另一个括号的位置
void showFloatingMenu() 显示浮动菜单
void showFloatingMenu(int) 在指定位置显示浮动菜单
String getSyntaxName() 获取文本编辑器当前的高亮语法名称
boolean isFocused() 判断编辑框是否拥有焦点
boolean requestFocus() 为编辑框请求获取焦点
boolean requestFocusAndShowIME() 为编辑框请求获取焦点并弹出输入法
boolean isEditTextView() 判断是否为输入框模式

# 光标与选中位置

# 获取选中位置

# getSelectionStart

int getSelectionStart()

获取选中文本的起始位置。此方法返回的是经过处理的起始位置,确保返回值始终小于等于结束位置。实际上返回的是原始 selectionStart 和 selectionEnd 中的较小值。

当没有选中文本时(单选状态),此方法的返回值等于 getSelectionEnd() 的返回值,表示光标所在的位置。

返回值: 选中文本的起始位置,如果没有选中文本则返回光标位置


# getSelectionEnd

int getSelectionEnd()

获取选中文本的结束位置。此方法返回的是经过处理的结束位置,确保返回值始终大于等于起始位置。实际上返回的是原始 selectionStart 和 selectionEnd 中的较大值。

当没有选中文本时(单选状态),此方法的返回值等于 getSelectionStart() 的返回值,表示光标所在的位置。

返回值: 选中文本的结束位置,如果没有选中文本则返回光标位置


# getRawSelectionStart

int getRawSelectionStart()

获取原始的选中文本起始位置。此方法返回的是真正的 Selection.getSelectionStart() 的值,不进行任何处理。在某些情况下,原始的 selectionStart 可能大于 selectionEnd。

当没有选中文本时(单选状态),此方法的返回值等于 getRawSelectionEnd() 的返回值。

返回值: 原始的选中文本起始位置


# getRawSelectionEnd

int getRawSelectionEnd()

获取原始的选中文本结束位置。此方法返回的是真正的 Selection.getSelectionEnd() 的值,不进行任何处理。在某些情况下,原始的 selectionEnd 可能小于 selectionStart。

当没有选中文本时(单选状态),此方法的返回值等于 getRawSelectionStart() 的返回值。

返回值: 原始的选中文本结束位置

注意事项:

getSelectionStart/End 与 getRawSelectionStart/End 的区别:

  • getSelectionStart()getSelectionEnd() 返回经过处理的位置,确保 start ≤ end,适合大多数场景使用
  • getRawSelectionStart()getRawSelectionEnd() 返回原始位置,可能出现 start > end 的情况,仅在需要区分选择方向时使用

# 判断是否选中文本

# hasTextSelected

boolean hasTextSelected()

判断当前是否选中了文本。此方法通过比较选中文本的起始位置和结束位置来判断是否有文本被选中:

  • getSelectionStart() != getSelectionEnd() 时,表示选中了文本
  • getSelectionStart() == getSelectionEnd() 时,表示只是光标定位(单选状态)

返回值: 如果选中了文本返回 true,如果只是光标定位返回 false

示例:

if (editor.hasTextSelected()) {
    // 有选中文本,执行处理
    String selectedText = editor.getSelectedText();
    // ...
} else {
    pluginUI.showToast("请先选中文本");
}

# 设置选中位置

# setSelection (两个参数)

void setSelection(int selectionStart, int selectionEnd)

设置选中文本位置。

参数:

  • selectionStart - 选中文本的起始位置
  • selectionEnd - 选中文本的结束位置

注意事项:

如果设置的选中位置在屏幕外,文本编辑器不会自动滚动,需要调用 ensureSelectionVisible() 方法来滚动到选中位置。

示例:

// 选中前10个字符
editor.setSelection(0, 10);
// 确保选中位置可见
editor.ensureSelectionVisible();

# setSelection (单个参数)

void setSelection(int selection)

设置光标位置,等同于调用 setSelection(selection, selection)

参数:

  • selection - 光标位置

注意事项:

如果设置的光标位置在屏幕外,文本编辑器不会自动滚动,需要调用 ensureSelectionVisible() 方法来滚动到光标位置。


# 显示光标

# showCursor

void showCursor()

显示光标。光标会周期性隐藏和显示,调用该方法后会使光标立刻处于显示状态。

通常用于需要立即显示光标的场景,如用户交互后。


# 推送选中位置到撤销缓冲区

# pushSelectionToUndoBuffer

void pushSelectionToUndoBuffer()

将当前选中位置推送到撤销缓冲区。此方法用于自定义撤销或重做后的选中位置。

调用时机的影响:

  • 在修改文本前调用此方法:点击撤销菜单,选中位置将恢复到调用此方法时的状态
  • 在修改文本后调用此方法:点击撤销菜单然后再点击重做菜单,选中位置将恢复到调用此方法时的状态
  • 可以先手动修改选中位置再调用此方法,这样就可以自定义撤销或重做后的选中位置

不调用此方法的默认行为:

操作 撤销后 重做后
插入文本 光标跳转到开始插入的位置 光标跳转到插入文本的末尾
删除文本 选中被删除的文本 光标跳转到删除点的位置
替换文本 选中被替换掉的文本 光标跳转新替换文本的末尾

示例:

// 删除当前行,撤销时光标定位到行首而不是选中被删除的行
int cursorPos = editor.getSelectionStart();
BufferedText text = editor.getBufferedText();

// 查找当前行的范围
int lineStart = text.lastIndexOf('\n', cursorPos - 1) + 1;
int lineEnd = text.indexOf('\n', cursorPos);
if (lineEnd == -1) {
    lineEnd = text.length();
}

// 设置选中位置为行首(光标状态),而不是默认的选中整行
editor.setSelection(lineStart);
// 推送到撤销缓冲区
editor.pushSelectionToUndoBuffer();

// 删除整行(包括换行符)
if (lineEnd < text.length()) {
    editor.deleteText(lineStart, lineEnd + 1);
} else {
    editor.deleteText(lineStart, lineEnd);
}

// 现在如果用户点击撤销,光标将定位到行首,而不是默认的选中被删除的整行

# 位置可见相关

# ensurePositionVisible (两个参数)

void ensurePositionVisible(int position, boolean immediately)

确保指定位置在屏幕可见,如果不可见会自动进行跳转。

参数:

  • position - 需要确保可见的文本位置索引
  • immediately - 是否立即跳转。true 时会立即跳转并禁用动画效果,false 时使用平滑滚动动画

# ensurePositionVisible (单个参数)

void ensurePositionVisible(int position)

确保指定位置在屏幕可见,如果不可见会自动进行跳转。使用平滑滚动动画效果。

参数:

  • position - 需要确保可见的文本位置索引

# ensureSelectionVisible

void ensureSelectionVisible()

确保选中位置在屏幕可见,如果不可见会自动进行跳转。

  • 单选模式:确保光标位置在屏幕可见
  • 多选模式:优先保证 selectionStart 在屏幕可见,并尽可能向 selectionEnd 所在位置滚动

示例:

// 选中文本后确保可见
editor.setSelection(100, 200);
editor.ensureSelectionVisible();

# 文本操作

# 获取缓冲文本对象

# getBufferedText

BufferedText getBufferedText()

获取文本编辑器的缓冲文本对象。返回一个 BufferedText 实例,该实例提供了高效的文本访问和操作能力。BufferedText 通过内置缓存机制优化了字符获取、文本搜索、行定位等操作的性能。

返回值: 文本编辑器的缓冲文本对象,永不为 null

注意事项:

关于缓存:

  • 返回的 BufferedText 对象会缓存文本数据以提高性能
  • 当编辑器文本发生变化时,可能需要调用 BufferedText.reset() 来刷新缓存

示例:

BufferedText text = editor.getBufferedText();
// 查找换行符位置
int lineStart = text.lastIndexOf('\n', cursorPos - 1) + 1;
int lineEnd = text.indexOf('\n', cursorPos);
if (lineEnd == -1) {
    lineEnd = text.length();
}

# 获取文本长度

# length

int length()

获取文本总长度。

返回值: 当前编辑器中文本的字符数量


# 替换文本

# replaceText (五个参数)

void replaceText(int start, int end, CharSequence text, int textStart, int textEnd)

将指定范围的文本替换为指定文本的子串。这是最基础的文本替换方法,其他文本操作方法都基于此实现。

参数:

  • start - 要替换的文本起始位置
  • end - 要替换的文本结束位置
  • text - 用于替换的文本
  • textStart - 替换文本的起始位置
  • textEnd - 替换文本的结束位置

# replaceText (三个参数)

void replaceText(int start, int end, CharSequence text)

将指定范围的文本替换为新文本。

参数:

  • start - 要替换的文本起始位置
  • end - 要替换的文本结束位置
  • text - 用于替换的完整文本

示例:

int start = editor.getSelectionStart();
int end = editor.getSelectionEnd();
String newText = "新文本";
editor.replaceText(start, end, newText);

# 插入文本

# insertText

void insertText(int position, CharSequence text)

向指定位置插入文本。不会删除任何现有文本,仅在指定位置插入新文本。

参数:

  • position - 插入位置
  • text - 要插入的文本内容

示例:

// 在光标位置插入文本
int cursorPos = editor.getSelectionStart();
editor.insertText(cursorPos, "插入的内容");

# 删除文本

# deleteText

void deleteText(int start, int end)

删除指定范围的文本。

参数:

  • start - 删除范围的起始位置(包含)
  • end - 删除范围的结束位置(不包含)

# 截取文本

# subText

String subText(int start, int end)

截取指定范围的文本。

参数:

  • start - 起始位置(包含)
  • end - 结束位置(不包含)

返回值: 截取的文本内容

示例:

int start = editor.getSelectionStart();
int end = editor.getSelectionEnd();
String selectedText = editor.subText(start, end);

# 获取选中的文本

# getSelectedText

String getSelectedText()

获取当前选中的文本内容。

返回值: 当前选中的文本,如果没有选中文本(光标状态)则返回空字符串

示例:

String selectedText = editor.getSelectedText();
if (selectedText.isEmpty()) {
    pluginUI.showToast("请先选中文本");
    return;
}

# 大批量编辑模式

# 开始大批量编辑模式

# startLargeBatchEditingMode

void startLargeBatchEditingMode()

开始大批量编辑模式。

由于每次修改文本时,编辑器都会对布局、代码高亮等进行计算与调整,如果在短时间内进行了多次修改,编辑器就会多次进行计算,计算量太大可能导致界面卡顿。(小文本正常感觉不出来,超大文本比较明显)

因此如果实现某个功能需要多次修改文本,那么可调用 startLargeBatchEditingMode() 方法进入大批量编辑模式,此时编辑器会暂停所有耗时计算,等修改完成后再调用 finishLargeBatchEditingMode() 方法退出大批量编辑模式。

重要提醒:

调用 startLargeBatchEditingMode()必须调用 finishLargeBatchEditingMode(),否则会导致异常,建议使用 try-finally 语法

示例:

editor.startLargeBatchEditingMode();
try {
    // 执行大量文本修改操作
    editor.insertText(0, "text1");
    editor.insertText(10, "text2");
    // ... 更多操作
} finally {
    editor.finishLargeBatchEditingMode();
}

# 结束大批量编辑模式

# finishLargeBatchEditingMode

void finishLargeBatchEditingMode()

结束大批量编辑模式。必须与 startLargeBatchEditingMode() 配对使用。调用后编辑器会恢复正常的计算和渲染,并对批量修改的内容进行统一处理。

# 文件相关

# 获取文件名

# getFileName

String getFileName()

获取文本编辑器当前的文件名。

返回值: 当前编辑的文件名,例如 "test.txt"

注意事项:

处于输入框模式时不支持获取文件名,此时将返回空字符串(非 null)。可通过 isEditTextView() 判断是否为输入框模式。


# 获取文件路径

# getFilePath

String getFilePath()

获取文本编辑器当前的文件路径。

返回值: 当前编辑的文件绝对路径,不支持时返回 null

注意事项:

部分情况下不支持获取文件路径,例如处于输入框模式、仅在内存中查看/编辑的数据,此时将返回 null。


# 判断是否已修改

# isChanged

boolean isChanged()

判断文本编辑器当前内容是否未保存,即顶部的保存按钮是否为可点击状态。

返回值: 如果有未保存的更改返回 true,否则返回 false

注意事项:

若不支持保存功能,则该方法总是返回 false。


# 保存文件

# save

void save(ResultCallback callback)

保存文件。此操作为异步执行,调用后会立即返回。

参数:

  • callback - 操作结果回调,可为 null

保存逻辑:

  • 若不支持保存功能,则直接触发失败回调
  • 若支持保存功能但 isChanged() 返回 false(无更改),则直接触发成功回调
  • 若支持保存功能且有未保存的更改,则执行保存操作,完成后触发相应回调

示例:

// 保存文件
editor.save(new ResultCallback() {
    @Override
    public void onSuccess() {
        pluginUI.showToast("保存成功");
    }

    @Override
    public void onFailure(String message) {
        pluginUI.showToast("保存失败: " + message);
    }
});

# 分页相关

分页模式用于查看/编辑超大文本文件,将文件分割成多个页面以提升性能。仅主文本编辑器支持该功能。

# 判断是否处于分页模式

# isPaginationMode

boolean isPaginationMode()

判断当前是否处于分页模式。

返回值: 如果处于分页模式返回 true,否则返回 false


# 获取分页总数

# getPageCount

int getPageCount()

获取分页总数。仅在分页模式下有效。

返回值: 分页总数,非分页模式返回 0


# 获取当前分页索引

# getCurrentPageIndex

int getCurrentPageIndex()

获取当前所在的分页索引。仅在分页模式下有效。

返回值: 当前分页索引(从 0 开始),非分页模式返回 0


# 切换到指定分页

# switchToPage

void switchToPage(int pageIndex, ResultCallback callback)

切换到指定分页。此操作为异步执行,调用后会立即返回。仅在分页模式下有效。

参数:

  • pageIndex - 目标分页索引(从 0 开始)
  • callback - 操作结果回调,可为 null

示例:

// 检查是否为分页模式
if (editor.isPaginationMode()) {
    int pageCount = editor.getPageCount();
    int currentPage = editor.getCurrentPageIndex();

    // 切换到下一页
    if (currentPage < pageCount - 1) {
        editor.switchToPage(currentPage + 1, new ResultCallback() {
            @Override
            public void onSuccess() {
                pluginUI.showToast("已切换到第 " + (currentPage + 2) + " 页");
            }

            @Override
            public void onFailure(String message) {
                pluginUI.showToast("切换失败: " + message);
            }
        });
    }
}

# 杂项

# 只读模式

# isReadOnly

boolean isReadOnly()

判断当前文本编辑器是否处于只读模式。

返回值: true 表示只读,无法调用修改文本的相关方法


# 缩进设置

# getTabSize

int getTabSize()

获取设置中配置的缩进大小。

返回值: 缩进的字符数量,通常为 2、4 或 8


# isIndentWithTabs

boolean isIndentWithTabs()

判断是否使用Tab字符进行缩进。

返回值: true 表示使用 \t 字符进行缩进,false 表示使用空格字符进行缩进


# 括号匹配

# getBracketPositions

int[] getBracketPositions()

获取光标处括号对的位置信息。

返回值:

  • 成功时返回长度为2的int数组,[0]为第一个括号位置,[1]为第二个括号位置
  • 失败时返回null(光标不在括号上或找不到匹配的括号)

括号对的介绍可查看 语法文件开发 (opens new window)


# getAnotherBracketPosition

int getAnotherBracketPosition()

获取光标处括号所对应的另一个括号的位置。

返回值: 匹配括号的位置索引,失败时返回-1(光标不在括号上或找不到匹配的括号)


# 浮动菜单

# showFloatingMenu (无参数)

void showFloatingMenu()

显示浮动菜单。浮动菜单将会自动选择一个合适的位置进行显示,尽量避免遮挡当前选中的文本内容。菜单的显示位置基于当前的文本选择范围自动计算。


# showFloatingMenu (一个参数)

void showFloatingMenu(int position)

在指定位置显示浮动菜单。与无参方法 showFloatingMenu() 不同,该方法允许手动指定浮动菜单的显示位置。菜单将显示在指定文本位置的附近。

参数:

  • position - 文本位置索引,指定菜单显示的参考位置。例如可以传入 getSelectionEnd() 在选区末尾显示菜单

# 语法高亮

# getSyntaxName

String getSyntaxName()

获取文本编辑器当前的高亮语法名称。

返回值: 高亮语法名称,如果未开启高亮,则返回 "Text"


# 焦点相关

# isFocused

boolean isFocused()

判断编辑框是否拥有焦点。

返回值: 是否拥有焦点


# requestFocus

boolean requestFocus()

为编辑框请求获取焦点。

返回值: 是否成功获取焦点


# requestFocusAndShowIME

boolean requestFocusAndShowIME()

为编辑框请求获取焦点并弹出输入法。

返回值: 是否请求成功

示例:

// 替换文本后,请求焦点并显示输入法
editor.replaceText(start, end, newText);
editor.setSelection(start + newText.length());
editor.requestFocusAndShowIME();

# 输入框模式

# isEditTextView

boolean isEditTextView()

判断是否为输入框模式。MT 内部将文本编辑器控件进行二次包装,并在许多界面中用作文本输入框去取代系统的 EditText,例如文本编辑器界面的搜索和替换输入框。

返回值: 当前编辑器控件是否作为输入框使用

# 完整示例

# 示例一:文本查找和替换

实现一个完整的查找和替换功能:

public void findAndReplace(PluginUI pluginUI, TextEditor editor,
                          String find, String replace, boolean regex) {
    // 构建正则表达式
    int flags = regex ? Pattern.MULTILINE : Pattern.LITERAL;
    Pattern pattern = Regex.compile(find, flags);

    // 获取缓冲文本对象
    BufferedText text = editor.getBufferedText();

    // 获取选中范围
    int start = editor.getSelectionStart();
    int end = editor.getSelectionEnd();

    // 执行查找
    Matcher matcher = text.matcher(pattern);
    matcher.region(start, end);
    ArrayList<MatcherSnapshot> snapshots = new ArrayList<>();

    while (matcher.find()) {
        snapshots.add(matcher.toSnapshot());
    }

    if (!snapshots.isEmpty()) {
        // 准备替换
        if (regex) {
            for (MatcherSnapshot snapshot : snapshots) {
                snapshot.prepareReplacement(replace);
            }
        }

        // 批量替换文本
        editor.startLargeBatchEditingMode();
        try {
            for (int i = snapshots.size() - 1; i >= 0; i--) {
                MatcherSnapshot snapshot = snapshots.get(i);
                String replacement = regex ? snapshot.getComputedReplacement() : replace;
                editor.replaceText(snapshot.start(), snapshot.end(), replacement);
            }
        } finally {
            editor.finishLargeBatchEditingMode();
        }

        // 设置光标位置并确保可见
        editor.setSelection(snapshots.get(snapshots.size() - 1).end());
        editor.pushSelectionToUndoBuffer();
        editor.ensureSelectionVisible();

        pluginUI.showToast("已替换 " + snapshots.size() + " 处");
    } else {
        pluginUI.showToast("未找到匹配的文本");
    }
}

# 示例二:批量文本处理

对选中的每一行文本进行处理:

public void processLines(TextEditor editor) {
    // 获取选中位置
    int start = editor.getSelectionStart();
    int end = editor.getSelectionEnd();

    if (start == end) {
        return; // 没有选中文本
    }

    // 获取缓冲文本
    BufferedText text = editor.getBufferedText();

    // 查找起始行的开头
    int lineStart = TextUtils.lastIndexOf(text, '\n', start - 1) + 1;

    // 查找结束行的末尾
    int lineEnd = TextUtils.indexOf(text, '\n', end);
    if (lineEnd == -1) {
        lineEnd = text.length();
    }

    // 提取每一行
    String content = editor.subText(lineStart, lineEnd);
    String[] lines = content.split("\n");

    // 处理每一行
    StringBuilder result = new StringBuilder();
    for (int i = 0; i < lines.length; i++) {
        String line = lines[i];
        // 在每行开头添加行号
        result.append(String.format("%3d: %s", i + 1, line));
        if (i < lines.length - 1) {
            result.append("\n");
        }
    }

    // 替换文本
    editor.replaceText(lineStart, lineEnd, result.toString());
}

# 示例三:光标和选区控制

实现智能选择功能,逐步扩大选区:

public void smartSelect(TextEditor editor) {
    int start = editor.getSelectionStart();
    int end = editor.getSelectionEnd();
    BufferedText text = editor.getBufferedText();

    if (start == end) {
        // 当前是光标状态,选中当前单词
        // 向前查找单词边界
        int wordStart = start;
        while (wordStart > 0 && Character.isLetterOrDigit(text.charAt(wordStart - 1))) {
            wordStart--;
        }

        // 向后查找单词边界
        int wordEnd = end;
        while (wordEnd < text.length() && Character.isLetterOrDigit(text.charAt(wordEnd))) {
            wordEnd++;
        }

        if (wordStart < wordEnd) {
            editor.setSelection(wordStart, wordEnd);
            editor.ensureSelectionVisible();
        }
    } else {
        // 已经有选区,扩展到整行
        int lineStart = TextUtils.lastIndexOf(text, '\n', start - 1) + 1;
        int lineEnd = TextUtils.indexOf(text, '\n', end);
        if (lineEnd == -1) {
            lineEnd = text.length();
        }

        editor.setSelection(lineStart, lineEnd);
        editor.ensureSelectionVisible();
    }
}

# 相关接口