MoreRSS

site iconMumulhl修改

00后,分享编程、技巧、生活
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Mumulhl的 RSS 预览

Jujutsu入门教程:比Git更好用的VCS

2025-07-10 21:59:21

Jujutsu 是啥?

Jujutsu(简称 jj)是一款功能强大的软件项目版本控制系统。你可以用它来获取代码副本,跟踪代码变更,最后发布这些变更供他人查看和使用。无论你是新手还是老手,无论你是独自开发全新的项目,还是拥有庞大历史和团队的大型软件项目,它都能让你轻松上手。

  • 与 Git 兼容
  • 匿名分支
  • 高度自动化的操作(如自动提交)
  • 使用中会有很多友好的提示和指引

Jujutsu 目前基本可用,还缺乏少量功能,如 git submodule 的兼容、tag 的创建。

我会写一个系列的 Jujutsu 教程,这篇教程是本系列教程的第一篇,只讲一些满足日常操作的东西,学了本篇教程,日常就可以正常使用 Jujutsu 了。在使用 Jujutsu 的过程中,如果出了什么问题,优先看 Jujutsu 自己给的解决方案。

安装

安装教程见 Jujutsu docs - Installation and setup,我用 Fedora,只能用 carogo-binstall 安装,用这个工具安装不需要从源码编译一遍,会直接从 GitHub 上下载已经编译好的二进制文件,并且 cargo-install-update 也支持通过这个工具更新安装的二进制文件。

初始化仓库

无论你是从零开始,还是在现有 Git 仓库的基础上进行初始化,都推荐用下面这个命令:

1
jj git init --colocate .

该命令可以从 .git 初始化 .jj 或者同时初始化 .jj.git。如果用 jj init 初始化,会只有 .jj,但 Jujutsu 目前又无法从 .jj 初始化 .git,用 git init 自己手动初始化一个也没用,会没法从 .jj 同步。Jujutsu 目前不支持创建 tag,如果要创建 tag,就要用到 Git,如果没有 .git,就会很抓狂,虽然可以解决,但要麻烦一点,别问,问就是踩坑了。

配置名称和邮箱

1
2
$jj config set --user user.name "Example"
$ jj config set --user user.email "[email protected]"

第一个提交

Jujutsu 中你不需要 提交(commit) ,但是需要 describe ,在仓库下做了一些改动后,或改动之前,或改动中,都可以用 describe 命令来解释改动,这个命令的简写是 desc:

1
2
3
$ jj desc -m "First commit"
Working copy (@) now at: pwquumlq ae3142c8 First commit
Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)

Working copy 指的是当前正在工作的 copy,可以任意更改,如果是 Git 的话,如果要修改一个 commit,就要手动操作下,这就体现了 Jujutsu 的自动化。pwquumlq 是 Jujutsu 的 id,ae3142c8 是对应的 Git 的 id。在之后的操作中可以用 @ 指代。

Parent commit 指的是上一个提交(父提交)。这里的 zzzzzzzz 00000000 说明父提交是 根(root)(empty) 表示这个 commit 是空的。(no description set) 表示这个 commit 没有 description。在之后的操作中可以用 @- 指代(多了一个减号)。

在终端中你会看到 zzzzzzzz 中的第一个 z 是高亮的,working copy 的 id 的第一个字母也是高亮的,高亮的字母 是 commit id 的简写,可以用来指代该 commit。直接高亮出来,方便很多,用终端操作的要比 GUI 快。

移动到新的 working copy

一个 commit 已经完成了,然后我们要移动到新的 working copy 进行接下来的工作:

1
2
3
$ jj new
Working copy (@) now at: qzmssurw 17a91560 (empty) (no description set)
Parent commit (@-) : pwquumlq ae3142c8 First commit

或者你可以在改动完后把 describenew 操作用 commit 命令一起完成,有多种方案,很灵活:

1
$ jj commit -m "First commit"

分叉

new 命令还可以从一个提交 分叉 出去,在用下面讲到的查看历史提交中就会看到分成了两条链:

1
$ jj new [jj id]

查看历史提交

可以直接运行 jj 来查看历史提交,相当于 jj log,输出的结果会省略一部分提交,这里提交数量太少了,Jujutsu 不会省略:

1
2
3
4
5
6
$ jj
@ qzmssurw [email protected] 2025-07-10 21:04:39 17a91560
(empty) (no description set)
○ pwquumlq [email protected] 2025-07-10 20:56:41 git_head() ae3142c8
│ First commit
◆ zzzzzzzz root() 00000000

这是省略的样子,(elided revisions) 会替代掉一部分提交:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ jj
@ ovqtlklr [email protected] 2025-07-09 21:07:36 030cb30c
(empty) (no description set)
◆ ruvroqot [email protected] 2025-07-09 21:07:26 main git_head() 59603f61
│ Sync vibe coding rules
~ (elided revisions)
│ ○ zsxqkykv [email protected] 2025-06-28 16:38:36 edf292f1
╭─┤ (empty) Merge pull request #413 from mumu-lhl/push-xpztozvsopvq
◆ │ xpztozvs [email protected] 2025-06-28 16:30:59 9fcc6e8e
│ │ Add changelog button to about page
~ │ (elided revisions)
│ ○ uvknsurt [email protected] 2025-06-28 15:48:48 70913779
│ ├─╮ (empty) Merge pull request #411 from weblate/weblate-ciyue-app
......

查看完整的历史提交也是没问题的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$ jj log -r 'all()'
@ ovqtlklr [email protected] 2025-07-09 21:07:36 030cb30c
(empty) (no description set)
◆ ruvroqot [email protected] 2025-07-09 21:07:26 main git_head() 59603f61
│ Sync vibe coding rules
◆ smoxoopp [email protected] 2025-07-08 21:56:35 4403614c
├─╮ (empty) Merge pull request #431 from mumu-lhl/push-xmlopqwnzwuo
│ ◆ xmlopqwn [email protected] 2025-07-08 21:52:31 261fa48b
│ │ Refactor Consumer to Selector on wordbook page
│ ◆ tmklvsky [email protected] 2025-07-08 21:31:40 af9826b7
├─╯ Add multiselectmode on wordbook page
◆ wlprsvrq [email protected] 2025-07-08 13:44:04 d31b7cf5
├─╮ (empty) Merge pull request #430 from mumu-lhl/push-kkmymxosqwvr
│ ◆ kkmymxos [email protected] 2025-07-08 11:17:33 0245d789
├─╯ Add storage management
◆ unquwrst [email protected] 2025-07-08 08:42:11 7e6a3a76
├─╮ (empty) Merge pull request #428 from mumu-lhl/dependabot/pub/gpt_markdown-1.1.2
│ ◆ svqpznzp 49699333+dependabot[bot]@users.noreply.github.com 2025-07-08 08:28:35 e70cca3f
│ │ chore(deps): bump gpt_markdown from 1.1.1 to 1.1.2
......

编辑提交

Jujutsu 中提交可以被轻松的编辑:

1
$ jj edit @- # 编辑父提交

编辑完后回去:

1
$ jj next --edit

是不是要比 Git 简单得多?

Squash

我们想要编辑的提交,往往都是父提交,例如忘了 format 或写错了一小点东西的时候,我们可以用 squash 更快的完成这步操作,这个命令是把当前 workng copy 的更改合并到父提交上:

1
$ jj squash

运行完这行命令后,当前 working copy 就会变成空的,因为合并到了父提交中。

推送提交

先加 remote,因为 Jujutsu 是与 Git 兼容的,所以可以直接推送到 Github 上,不需要 JJhub

1
$ jj git remote add origin [远程仓库地址]

Jujutsu 中有 分支(Branch) 的平替概念—— 书签(Bookmark),但是书签一旦被 set,不会跟随当前的 working copy 的变动而改变,需要手动再次 set,才会变。

现在我们的仓库连 main 书签都没有,这个命令可以把书签 set 到当前 commit 上,如果不存在会自动创建。确保当前的 working copy 不是空的,如果是的话可以用 jj edit @- 编辑父提交,使当前的 working copy 变成父提交:

1
$ jj b s main # b 是 bookmark 的缩写,s 是 set 的缩写

推送到远程仓库:

1
$ jj git push

当对某个过去的提交更改后,推送是不需要加 –force 之类的参数的,爽了很多。

抛弃一个提交

如果你不想要当前 working copy 的更改或最新的提交,可以抛弃掉:

1
$ jj abandon [jj id]

注意:不要随意抛弃过去的提交,会导致冲突,很难处理,如果要合并过去的提交,可以用 squash 命令,这个命令更强大的用法将在下一篇教程中讲到。

撤销

当你后悔了一个操作的时候,就需要撤销,撤销操作也是非常的简单:

1
$ jj undo

推送到额外的分支上

不是所有项目我们都会一律把提交 直接 推送到 main 分支上,按照 Github flow,至少要推送到其他分支上,然后发一个 PR 合并到 main 上。

只需要在推送到远程仓库的命令上多加几个字符就可以了:

1
$ jj git push -c@ # -c 后跟随要推送到仓库的提交,比该提交旧的提交也会一起推送上去,这里用 @ 指代当前 working copy

这种方法推送上去的分支名是 push-[jj id],当然可以自己在本地先 set 了书签,然后再用 jj git push 推送。我比较懒不太喜欢给分支命名,因为 PR 名称就可以诠释一堆的提交是干什么的了,减少心智负担。

在同一个仓库里(多个维护者),如果每个人提交的都是默认的那种 push-[jj id],完全会乱套,我们可以把默认的分支名变成 yourname/push-[jj id] 这样去稍微做一点区分。编辑配置文件 .config/jj/config.toml(用 jj config p 查看配置文件路径),加入以下内容:

1
2
[templates]
git_push_bookmark = '"example/push-" ++ change_id.short()'

拉取

远程仓库做了一些更改,需要把提交都拉取到本地仓库:

1
$ jj git fetch

main 书签 set 的提交也自动变化了,但是通过运行 jj 你会看到当前 working copy 并不在 main 的那条链上,所以需要我们自己手动一下去移动到 main 那条链上:

1
$ jj new main

结语

用了一段时间的 Jujutsu 后,Jujutsu 基本已经平替了 Git,只能用一个字来说用的体验——爽。

虽然没有多少有价值的学习资料,边用边学的过程中,基本也用个会了,因为操作起来真的很简单。

下一篇教程会深入讲解 Jujutsu 的使用,敬请期待 :)

番茄小说写作试水

2025-06-09 19:50:11

之前看见一名推友说自己用 AI 结对写小说,发表到番茄小说上,遇到了没有流量的问题,番茄小说的一些规则其实它都写了,但要细品,不然怎么让你在上面发小说呢?

这里我就来梳理一下番茄小说写作的一些规则,可能都是我比较在意的。

签约条件

年龄要求如下:

  • 16 岁以下不能签约
  • 16-18 岁需要监护人的身份证和手机号码
  • 18 岁以上不用说

为什么小说发表没有流量:推荐要求

番茄必须要签约后才会对小说进行推荐,在签约前想要有流量,只能自己想办法引流。

你有继续发小说吗?

没有了,我被挡在了签约条件第二条,发表的小说在签约前没有流量,我不知道值不值得签约,要是签约后不通过或是流量很差,我也没脸啊!会很后悔的!

审核严格吗?

审核似乎不是编辑在审,过不过审跟作品质量无关。审核速度非常快,快的话几分钟就过了(可怜勤勤恳恳的审核员…)。

能用 AI 写小说吗?

番茄官方的作家助手 app 里是有 AI 助写的功能的,使用有限制,也应该是番茄的一种态度吧,大概能用,但是不要全用 AI 给写完了,即使过了审,如果被编辑或读者看出了,那就不好说会怎么样了。

结尾

这篇文章很短,我猜你点了进来,可能对你有帮助吧。后续是我准备试试知乎的盐选小说的水,看了下规则,好像审核非常严,是编辑在审稿,每次投稿都会多次审核,不知道写出来能不能。但是现在,我得马上赶作业了…

没麦怎么办,把手机当作麦克风!(Scrcpy 篇)

2025-04-30 22:31:56

很久很久之前写了一篇在 Linux 上如何把 Android 作为麦克风的教程,不久前因为要用 Windows 上的一些软件,装上了 Windows,顺便折腾个在 Windows 上的把 Android作为麦克风的方案。

找了很久,没有找到既方便又开源的方案,一些免费的方案(如 WO Mic)音质不太行,自己摸索了一方,最后用 ScrcpyVB-CABLE 实现了。关键是 不需要在 Android 上安装任何软件!

使用 Scrcpy 把 Android 作为麦克风理论上在 Linux 和 macOS 上也可以,在音频驱动那里会有点不同。

确保 Android 版本 >= 11

安装

Scrcpy

首先是 Scrcpy,它将用于从 Android 转发音频,可以在 Github Release 页面 下载对应的压缩包再解压,32 位下载 scrcpy-win32-vX.X.zip,64 位下载 scrcpy-win64-vX.X.zip

最好的办法是直接通过 Windows 的包管理器 winget 安装:

1
winget install --id=Genymobile.scrcpy

VB-CABLE

VB-CABLE 的用途是将音频由输出转到输入。

前往 VB-CABLE官网,点击 VBCABLE_Driver_Pack45.zip 下载压缩包,解压后,双击执行 VBCABLE_Setup_x64.exe(64 位)或 VBCABLE_Setup.exe(32 位)安装。然后就好了很么都不用动。系统 > 声音 > 输入 应该会自己变成 CABLE Output。

正式步骤

Android 上开启 USB 调试

具体步骤见 Android Developer 文档

运行 Scrcpy 命令

如果通过压缩包获取 Scrcpy 点击解压后目录里的 open_a_terminal_here.bat 在该目录下打开终端。通过 winget 安装 Scrcpy 直接打开终端即可。

接着运行:

1
scrcpy --no-video --no-control --audio-source=mic-unprocessed

第一次运行 Android 上会要求授权,授权即可。

参数解释:

  • --no-video 不要画面
  • --no-control 不要控制
  • --audio-source=mic-unprocessed 将声音源指定为未处理的麦克风音频

声音源还有很多选项,完整选项见 Audio 文档,我觉得 mic-unprocessed 音质是最好的,也可以试试别的,简单列举几个:

  • mic: 捕获麦克风
  • mic-unprocessed: 捕获未处理(原始)的麦克风声音
  • mic-camcorder: 捕获为录制视频调优的麦克风声音,如果可用,其方向与摄像头相同
  • mic-voice-recognition: 捕获为语音识别调优的麦克风声音
  • mic-voice-communication: 捕获为语音通信调优的麦克风声音(例如,如果可用,它会利用回声消除或自动增益控制)

改变音谱输出设备

系统 > 声音 > 音量合成器 > 应用 将 Scrcpy 创建的窗口的 输出设备 更改为 CABLE Input,现在通过 Android 麦克风输入的音频就不会从扬声器里输出了,会从输入设备 CABLE Output 里输出。

Imagen 3: 免费文生图工具保持人物一致性

2025-02-16 11:08:28

Featured image of post Imagen 3: 免费文生图工具保持人物一致性

Imagen 3

Imagen 3 是 Google DeepMind 最新的文生图模型,可以在 ImageFx 免费使用。Imagen 3 在 Gemini 内也是可以使用的,Gemini 生成图片有时会不听话,不生成图片,所以本文采用 ImageFx。

如何实现人物一致性?

主要有两种方式:

  1. 对现有的人物图像进行“引用”,如 Midjourney
  2. 对人物描述相当详细的 Prompt,基本适用于各种文生图模型

第一种方式 Imagen 3 不支持,本文就采用第二种。

创造人物形象

想要有一个对人物描述相当详细的 Prompt,由人来写就太繁琐了,可以提供一些人物的特征让大语言模型帮我们写,这里采用 Gemini 2.0 Flash Thinking 模型。给出的特征越详细越能生成符合自己想要的人物。

示范 Prompt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
你是一名文生图prompt专家,请写一个英文prompt去生成一个人物,将prompt放入代码块中。

prompt要求:

1. 无背景
2. 全身图

人物:

1. 白色头发
2. 穿白里带红的和服
3. 20岁左右的女性
4. 日本动漫风格

注:不要想着生成loli什么的,生成图片时会被判定为“儿童”而无法生成!

先用大语言模型写的 Prompt 生成一下,如果效果不错,把种子锁定下,最好把种子保存下,方便以后生成尽可能相似的人物。

Example 1

根据人物创造详细的 Prompt

下载一张图片,发给支持图像的大语言模型,并让它根据图像创造出详细的 Prompt。

示范 Prompt:

1
你是一名文生图prompt专家,请写一个英文prompt去描述图像人物,将prompt放入代码块中。

把人物嵌入图像中

首先先生成一个生成人物坐在桌前写作业的图片的Prompt。

示范 Prompt:

1
2
3
4
5
6
7
你是一名文生图prompt专家,请写一个英文prompt去生成一张图片,将prompt放入代码块中。

要求:

1. 用 [CHARACTER] 代替人物
2. 人物坐在一张桌子前写作业
3. 氛围温馨

我们将使用 [CHARACTER] 代指人物,然后把上一个步骤生成的 Prompt 拼接进去,如:

1
2
3
[CHARACTER] sitting at a wooden desk, writing homework in a softly lit room, warm atmosphere, cozy, desk lamp, books, study, home, peaceful, detailed, realistic.

[CHARACTER]: 1girl, full body, Tomoe Gozen, white hair, long hair, white kimono, red and gold floral pattern, standing, arms outstretched, black background

最终效果图:

2024年度总结:AI原来能这么好用

2025-01-01 10:07:26

Featured image of post 2024年度总结:AI原来能这么好用

(封面来自 Unsplash,作者 BoliviaInteligente)

2024年结束了,2025年来了,转眼又是一年了。2024给我最大的震撼就是AI的辅助带来的便捷,过去稍有接触AI,但是没有太深入地使用,导致也用不好。

真正对 AI 的接触

看到 X 上有很多大佬用 Cursor 开发出了一些项目,就有点手痒痒了。过去我使用的编辑器是 Helix,一个全键盘编辑器,感觉效率会高一些。如果要用 Cursor 这样的 AI 编辑器就要脱离全键盘的操作了,但是 AI 能提高我的开发效率的话,没有了全键盘操作也不算亏。于是 Cursor 就取代 Helix 在我的电脑上的地位。

Cursor 让我震撼的是,在编辑一块代码的时候,居然可以按 Tab 键去补全其他部分相关的代码,以前我以为 AI 代码补全就只能像补全变量名、函数名那样补全,这完全打破了我的认知啊!

最早使用 AI 我都倾向于想用一句话让 AI 完成我想要做的有点复杂的事,但在理论上是不可行的,事实上也是不行的。只用一句话去表述一件较为复杂的事,信息量很少,难以去描述细节,AI 自然无法做到和自己所想的相符。和人说件事,说话只说一句,得让对方多疯狂,更别说是 AI 了。在我的使用体验来说,AI 更适合去做小任务,而非是复杂的大任务,如果要让 AI 去做大任务,将它分解为多个小任务效果会更好。

AI 的使用体验总结起来就是一个字——“爽”,每周最多一天的时间里我可以开发得更高效。

开源让我学到了很多

2024年新开发的项目主要实在暑假里开发的,好几个我都觉得不太行,所以只挑出我最经常维护的词悦来说吧。

词悦是一个开源的支持 mdict 格式的词典,是我觉得 Android 上没有简洁、好用的开源的 mdict 词典而开发的。虽然最初发布的时候很简陋,不过现在经过几个月的开发,已经完善了许多了。

最初词悦用 git-cliff 生成版本发布时的更新日志,后来我才慢慢在词悦中把 Github 的 label、milestones 功能好好用上,现在词悦的版本更新日志是由 Github 生成的,这样可以把这个版本贡献者直接显示在更新日志中。

词悦还提交到了 F-Droid 上,我还提交过几个 MR,但是水平不太够,没办法,犯了很多低级错误,都由 linsui 大佬纠正了,感谢 linsui 大佬不知疲倦地修改我的 MR!

在开源中,我学会了许多以前不知道的技巧、技术,开源不仅利他,还能利己。

重启博客

2024,我重启了我的博客,一年的文章就是2023的十几倍了(2023只有一篇文章)。2024我写了15篇博文,其中大部分都是在暑假写的。暑假闲来无事重启了博客,还把博客主题换成了 Stack。我还把博客提交到 V2EX VXNA、博友圈等平台上,我的博客也终于不再是我自娱自乐的地方了

对2025的展望

希望我可以在2025把 AI 使用得更加得心应手吧;希望我能在2025凭借自己的能力去赚到人生第一桶金,哪怕没几块也行哪。

给Flutter Android App支持全局上下文菜单

2024-11-30 23:16:10

最近在开发词悦(一个 mdict 词典)的时候,需要支持全局上下文菜单,查了很多资料都没有找到合适的方法,问了下 cursor,得到了初步方案,经过稍微的改动就有了这篇教程。

本文开发环境在 Linux 下。

初始化项目

1
2
flutter create example
cd example

写代码

Manifest

编辑 android/app/src/main/AndroidManifest.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 省略... -->
 <meta-data
 android:name="io.flutter.embedding.android.NormalTheme"
 android:resource="@style/NormalTheme"
 />
 <intent-filter>
 <action android:name="android.intent.action.MAIN"/>
 <category android:name="android.intent.category.LAUNCHER"/>
 </intent-filter>
</activity>
<!-- 新增的部分 -->
<!-- android:label 是上下文菜单中显示的名称 -->
<activity
 android:name=".ProcessTextActivity"
 android:label="example"
 android:exported="true">
 <intent-filter>
 <action android:name="android.intent.action.PROCESS_TEXT" />
 <data android:mimeType="text/plain" />
 <category android:name="android.intent.category.DEFAULT" />
 </intent-filter>
</activity>

原生 Android

创建 android/app/src/main/kotlin/com/example/example/ProcessTextActivity.kt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.example.example

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity

class ProcessTextActivity : Activity() {
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)

 val text = intent?.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)?.toString() ?: ""

 val intent = Intent(this, MainActivity::class.java).apply {
 action = Intent.ACTION_PROCESS_TEXT
 putExtra(Intent.EXTRA_PROCESS_TEXT, text)
 addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) // 如果不加这个 flag,app 在后台运行时无法把选中的文本传给 Flutter
 }

 startActivity(intent)
 finish()
 }
}

编辑 android/app/src/main/kotlin/com/example/example/MainActivity.kt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.example.example

import android.content.Intent
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
 private val CHANNEL = "com.example.example/process_text"
 private var methodChannel: MethodChannel? = null

 override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
 super.configureFlutterEngine(flutterEngine)

 methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
 }

 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)

 if (intent?.action == Intent.ACTION_PROCESS_TEXT) {
 val text = intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)?.toString() ?: ""
 methodChannel?.invokeMethod("processText", text)
 }
 }
}

Flutter 部分

编辑 lib/main.dart:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
 WidgetsFlutterBinding.ensureInitialized();

 platform.setMethodCallHandler((call) async {
 if (call.method == "processText") {
 final text = call.arguments as String; // call.arguments 里就是选中的文本了
 print(text);
 }
 });

 runApp(const MyApp());
}

const platform = MethodChannel("com.example.example/process_text");

结尾

接下来怎么样就靠你的想象力了 :)