Skip to content

Add plugin event bus code-language-update and code-language-change - #15610

Merged
Vanessa219 merged 1 commit into
siyuan-note:devfrom
TCOTC:feat/code-languages
Aug 28, 2025
Merged

Add plugin event bus code-language-update and code-language-change#15610
Vanessa219 merged 1 commit into
siyuan-note:devfrom
TCOTC:feat/code-languages

Conversation

@TCOTC

@TCOTC TCOTC commented Aug 17, 2025

Copy link
Copy Markdown
Contributor

Add plugin event bus code-languages-prepare and code-languages-change

本 PR 有几个改进:

  1. 给选项添加 data-id 属性( 给常用菜单的 button 添加 data-id 属性 #12518

  2. 给选项添加 b3-list-item--focus 类名之前先检查元素是否被隐藏,兼容用户使用 CSS 隐藏了选项的情况

  3. 把搜索时的自定义语言移动到列表末尾,因为使用自定义语言是更低频的

  4. 增加事件 code-languages-preparecode-languages-change,能够通过插件实现这几个需求:根据最近使用来调整代码块语言选择列表 #6479 代码块 选择语言时,能否将最近使用过的语言放在列表最上方? #9964 代码块语言选择列表,应添加已使用的代码风格 #11507

    p.s. 还需要修改 https://github.com/siyuan-note/petal

    我简单试了一下可以实现这样的效果:

    import {Plugin} from "siyuan";
    export default class PluginSample extends Plugin {
        onload() {
            // 监听代码语言列表准备事件
            this.eventBus.on("code-languages-prepare", (event) => {
                console.log("code-languages-prepare", event);
                const { languages } = event.detail;
    
                // 定义自定义语言列表(排序在最前面)
                const customLanguages = ['abcdefg'];
                
                // 定义优先级语言列表
                const priorityLanguages = ['css', 'js'];
                
                // 重新排序语言列表:优先级语言在前,其他语言在后
                const sortedLanguages = [
                    ...customLanguages,
                    ...priorityLanguages.filter(lang => languages.includes(lang)),
                    ...languages.filter(lang => !priorityLanguages.includes(lang) && !customLanguages.includes(lang))
                ];
                
                // 将排序后的语言列表赋值回 event.detail.languages
                event.detail.languages = sortedLanguages;
                
                console.log("重新排序后的语言列表:", sortedLanguages);
            });
            
            // 监听代码语言变更事件
            this.eventBus.on("code-languages-change", (event) => {
                console.log("code-languages-change", event);
                const { language, languageChanges } = event.detail;
                
                // 执行相应的操作
                console.log(`代码语言从 ${languageChanges[0].oldLanguage} 更改为: ${language}`);
            });
        }
    }
    video.webm

另外,对于第 4 点,还有一份 AI 的总结:

代码语言钩子 API

概述

思源笔记提供了两个代码语言相关的钩子:

  1. code-languages-prepare: 在显示代码语言列表前触发,允许插件修改可用的代码语言列表
  2. code-languages-change: 在用户更改代码语言后触发,允许插件执行相应的操作

事件详情

code-languages-prepare 钩子

  • 事件类型: code-languages-prepare
  • 事件详情: { languages: string[] }
    • languages: 包含所有可用代码语言的数组

code-languages-change 钩子

  • 事件类型: code-languages-change
  • 事件详情: { language: string, languageChanges: Array<{oldLanguage: string, languageElement: HTMLElement}>, protyle: IProtyle }
    • language: 新的语言名称(空字符串表示清除语言)
    • languageChanges: 语言变更信息数组,每个元素包含 oldLanguage 和 languageElement
    • protyle: 编辑器实例

使用方法

1. 基本用法

export default class MyPlugin extends Plugin {
    onload() {
        // 监听代码语言列表准备事件
        this.eventBus.on("code-languages-prepare", (event) => {
            const { languages } = event.detail;
            
            // 修改语言列表
            // 注意:直接修改 languages 数组即可
        });
        
        // 监听代码语言变更事件
        this.eventBus.on("code-languages-change", (event) => {
            const { language, languageChanges, protyle } = event.detail;
            
            // 执行相应的操作
            languageChanges.forEach(change => {
                console.log(`代码语言从 ${change.oldLanguage} 更改为: ${language}`);
            });
        });
    }
}

2. 添加自定义语言

this.eventBus.on("code-languages-prepare", (event) => {
    const { languages } = event.detail;
    
    // 添加自定义的代码语言
    if (!languages.includes("my-custom-lang")) {
        languages.push("my-custom-lang");
    }
});

3. 移除特定语言

this.eventBus.on("code-languages-prepare", (event) => {
    const { languages } = event.detail;
    
    // 移除不需要的语言
    const index = languages.indexOf("unwanted-language");
    if (index > -1) {
        languages.splice(index, 1);
    }
});

4. 重新排序语言

this.eventBus.on("code-languages-prepare", (event) => {
    const { languages } = event.detail;
    
    // 按字母顺序排序
    languages.sort();
    
    // 或者自定义排序逻辑
    languages.sort((a, b) => {
        // 将常用语言排在前面
        const commonLanguages = ["javascript", "typescript", "html", "css"];
        const aIndex = commonLanguages.indexOf(a);
        const bIndex = commonLanguages.indexOf(b);
        
        if (aIndex === -1 && bIndex === -1) return a.localeCompare(b);
        if (aIndex === -1) return 1;
        if (bIndex === -1) return -1;
        return aIndex - bIndex;
    });
});

5. 过滤语言

this.eventBus.on("code-languages-prepare", (event) => {
    const { languages } = event.detail;
    
    // 过滤掉以特定前缀开头的语言
    const filteredLanguages = languages.filter(lang => 
        !lang.startsWith("deprecated-") && 
        !lang.startsWith("legacy-")
    );
    
    // 将过滤后的结果赋值回事件详情
    event.detail.languages = filteredLanguages;
});

6. 使用 code-languages-change 钩子

this.eventBus.on("code-languages-change", (event) => {
    const { language, languageChanges, protyle } = event.detail;
    
    // 示例:当设置特定语言时执行操作
    if (language === "javascript") {
        // 为 JavaScript 代码块添加特殊样式
        languageChanges.forEach(change => {
            change.languageElement.classList.add("js-code-block");
        });
    }
    
    // 示例:记录语言使用统计
    if (language && language !== "") {
        const stats = window.siyuan.storage["code-languages-stats"] || {};
        stats[language] = (stats[language] || 0) + 1;
        window.siyuan.storage["code-languages-stats"] = stats;
    }
    
    // 示例:根据语言变更执行不同的处理逻辑
    languageChanges.forEach(change => {
        const { oldLanguage, languageElement } = change;
        
        if (oldLanguage === "python" && language === "javascript") {
            console.log("从 Python 切换到 JavaScript");
        } else if (oldLanguage === "" && language !== "") {
            console.log(`新设置了 ${language} 语言`);
        } else if (oldLanguage !== "" && language === "") {
            console.log(`清除了 ${oldLanguage} 语言`);
        }
        
        // 示例:根据语言执行不同的处理逻辑
        switch (language) {
            case "python":
                // Python 代码的特殊处理
                console.log("Python 代码块已设置");
                break;
            case "html":
                // HTML 代码的特殊处理
                console.log("HTML 代码块已设置");
                break;
            case "":
                // 清除语言时的处理
                console.log("代码语言已清除");
                break;
        }
    });
});

注意事项

  1. 直接修改: 插件应该直接修改 languages 数组,而不是创建新的数组
  2. 性能考虑: 避免在钩子中执行耗时的操作,因为这会影响用户体验
  3. 错误处理: 在修改语言列表时添加适当的错误处理

完整示例

import { Plugin } from "./index";

export default class CodeLanguageEnhancerPlugin extends Plugin {
    onload() {
        // 监听代码语言列表准备事件
        this.eventBus.on("code-languages-prepare", (event) => {
            try {
                const { languages } = event.detail;
                
                // 添加自定义语言
                const customLanguages = ["my-special-lang", "internal-format"];
                customLanguages.forEach(lang => {
                    if (!languages.includes(lang)) {
                        languages.push(lang);
                    }
                });
                
                // 移除过时的语言
                const deprecatedLanguages = ["old-format", "legacy-syntax"];
                deprecatedLanguages.forEach(lang => {
                    const index = languages.indexOf(lang);
                    if (index > -1) {
                        languages.splice(index, 1);
                    }
                });
                
                // 重新排序,将常用语言排在前面
                const commonLanguages = ["javascript", "typescript", "html", "css", "python"];
                languages.sort((a, b) => {
                    const aIndex = commonLanguages.indexOf(a);
                    const bIndex = commonLanguages.indexOf(b);
                    
                    if (aIndex === -1 && bIndex === -1) return a.localeCompare(b);
                    if (aIndex === -1) return 1;
                    if (bIndex === -1) return -1;
                    return aIndex - bIndex;
                });
                
            } catch (error) {
                console.error("修改代码语言列表时出错:", error);
            }
        });
        
        // 监听代码语言变更事件
        this.eventBus.on("code-languages-change", (event) => {
            try {
                const { language, languageChanges, protyle } = event.detail;
                
                // 记录语言使用统计
                if (language && language !== "") {
                    const stats = window.siyuan.storage["code-languages-stats"] || {};
                    stats[language] = (stats[language] || 0) + 1;
                    window.siyuan.storage["code-languages-stats"] = stats;
                }
                
                // 遍历每个语言变更
                languageChanges.forEach(change => {
                    const { oldLanguage, languageElement } = change;
                    
                    // 根据语言变更执行特定操作
                    if (oldLanguage === "python" && language === "javascript") {
                        console.log("从 Python 切换到 JavaScript");
                    } else if (oldLanguage === "" && language !== "") {
                        console.log(`新设置了 ${language} 语言`);
                    } else if (oldLanguage !== "" && language === "") {
                        console.log(`清除了 ${oldLanguage} 语言`);
                    }
                    
                    // 根据语言执行特定操作
                    if (language === "javascript") {
                        languageElement.classList.add("js-code-block");
                    } else if (language === "python") {
                        languageElement.classList.add("python-code-block");
                    }
                });
                
            } catch (error) {
                console.error("处理代码语言变更时出错:", error);
            }
        });
    }
}

相关文件

  • app/src/protyle/toolbar/index.ts - 钩子触发位置
  • app/src/types/index.d.ts - 事件类型定义
  • app/src/plugin/EventBus.ts - 事件总线实现

@88250 88250 assigned Vanessa219 and unassigned Vanessa219 Aug 18, 2025
@Vanessa219
Vanessa219 merged commit f0f1567 into siyuan-note:dev Aug 28, 2025
4 checks passed
@Vanessa219
Vanessa219 self-requested a review August 28, 2025 08:38
@Vanessa219 Vanessa219 added this to the 3.3.1 milestone Aug 28, 2025
@TCOTC
TCOTC deleted the feat/code-languages branch August 28, 2025 08:40
Vanessa219 added a commit that referenced this pull request Aug 28, 2025
@Vanessa219

Copy link
Copy Markdown
Member

把原来的逻辑改坏了,我还原了部分。

image image

@Vanessa219 Vanessa219 self-assigned this Aug 28, 2025
@Vanessa219 Vanessa219 changed the title Add plugin event bus code-languages-prepare/change Add plugin event bus code-language-before and code-language-change Aug 28, 2025
Vanessa219 added a commit to siyuan-note/petal that referenced this pull request Aug 28, 2025
@TCOTC

TCOTC commented Aug 28, 2025

Copy link
Copy Markdown
Contributor Author

@Vanessa219 哪里改坏了?

下次改之前先跟我说一下嘛,这样基本全都改回去了,改回来好麻烦的

@TCOTC

TCOTC commented Aug 28, 2025

Copy link
Copy Markdown
Contributor Author

还有,叫 code-languages-prepare 是因为等后面需要的时候会再加一个 code-language-before 用来修改代码语言元素,现在用了 before 以后添加的就不知道叫什么了

@Vanessa219

Copy link
Copy Markdown
Member

改坏了的地方参见截图对比。

这个看上去就是修改代码语言的

if (protyle.app && protyle.app.plugins) {
            protyle.app.plugins.forEach((plugin: any) => {
                plugin.eventBus.emit("code-language-before", eventDetail);
            });
        }

@TCOTC

TCOTC commented Aug 28, 2025

Copy link
Copy Markdown
Contributor Author

截图没看出来什么问题

@Vanessa219

Copy link
Copy Markdown
Member

顺序和默认选项不对,相关的是 #15404

@TCOTC

TCOTC commented Aug 28, 2025

Copy link
Copy Markdown
Contributor Author

你是说这个吗:

image

@Vanessa219

Copy link
Copy Markdown
Member

不是,图里没有自定义语言。是标注的123的顺序不一致,还有修改前是第三行高亮,修改后是第二行高亮。这个需求是#15404 里面修改的,感觉不仅被改回去了,还改的有点不太对。

image

@TCOTC

TCOTC commented Aug 29, 2025

Copy link
Copy Markdown
Contributor Author

jav 是自定义语言,我移动到列表末尾了

我觉得一般来说用户都是输入内置的语言,毕竟只有内置的语言有高亮,所以自定义语言我就丢到最后了,没必要放前面占地方

@Vanessa219

Copy link
Copy Markdown
Member

这个是用户输入的搜索关键字,自定义语言应该是通过插件 "code-language-before" 添加的。

@TCOTC

TCOTC commented Aug 29, 2025

Copy link
Copy Markdown
Contributor Author

我的意思就是用户如果将代码块设置为 jav 语言,那么 jav 就是用户使用的自定义语言,我的这个概念是这么理解。

当然通过 code-language-before 添加的也可以叫自定义语言。

总之 jav 在末尾是符合预期的。

@Vanessa219

Copy link
Copy Markdown
Member

这样会有一个比较严重的问题,如果我要输入 j ,就需要不停的按下。但是现在按上就可以了。

image

@TCOTC

TCOTC commented Aug 29, 2025

Copy link
Copy Markdown
Contributor Author

但是一般来说都不会有人只输入一个 j 的吧,而且按两三下键盘方向键 ↑ 就行了,不用一直按 ↓

我觉得把非内置语言放在顶部不合理,没什么人用的选项占了最明显的位置

@Vanessa219

Copy link
Copy Markdown
Member

就目前这样,我觉得挺好的。

@TCOTC

TCOTC commented Aug 29, 2025

Copy link
Copy Markdown
Contributor Author

要不我发个投票贴看看其他用户的想法?

@Vanessa219

Copy link
Copy Markdown
Member

现在这个设计也是你建议的,为什么要推翻呢。感觉用下来挺方便的,不管用户要输入什么都很快捷和方便。因为当前选中的是最合理的,如果想要搜索的关键字也可以很快插入。用上键翻到最后一条,一般用户没这个思维。

@Achuan-2

Achuan-2 commented Aug 29, 2025

Copy link
Copy Markdown
Member

现在的设计就挺好的,没必要改
用不到自定义语言自行忽略就好了,反正目前直接按enter就是列表里的语言
把自定义语言移到后面会导致需要自定义语言的用户使用很麻烦,目前也没有把highlight.js所有语言的别名都展示出来,只加了 "js", "ts", "html", "toml", "c#", "bat",等别名,有些别名只能通过自定义语言方式输入,有些别名输入时或许和现有语言一起出现
除了自定义语言,比如我需要输入大写的代码语言,比如“R“,如果改成你那样,就跑到最下面去了(R语言就是生物专业和统计专业最常用的语言之一)

Vanessa219 added a commit to siyuan-note/petal that referenced this pull request Sep 1, 2025
@TCOTC

TCOTC commented Sep 1, 2025

Copy link
Copy Markdown
Contributor Author

我很难接受搜索语言的时候要跨两个我用不着的选项去看选中的语言:

image

底线是最多有一个选项,要么把“清空”干掉,要么把自定义语言放到末尾去

上次提 搜索代码块语言,默认选择首个内置语言候选项 #15404 就是别扭得受不了了,但我发现自定义语言还在前面的话就依旧别扭

@Vanessa219

Vanessa219 commented Sep 1, 2025

Copy link
Copy Markdown
Member

这样其他用户不好用啊

@TCOTC

TCOTC commented Sep 1, 2025

Copy link
Copy Markdown
Contributor Author

90% 的用户认为不放在开头更符合习惯:

image

@TCOTC

TCOTC commented Sep 1, 2025

Copy link
Copy Markdown
Contributor Author

以及还有一个问题是目前插件没法修改“清空”选项和自定义语言选项的排序,只能固定在最前面

@TCOTC

TCOTC commented Sep 1, 2025

Copy link
Copy Markdown
Contributor Author

菜单成生后再去操作列表元素有点复杂了,性能也不太好。我改成 code-language-update 你再看看?

code-language-update 命名感觉没问题, @88250 需要修改一下版本日志

@emptylight370

Copy link
Copy Markdown
Contributor

低频使用我认可,同时我也认为:

自定义语言应替换清空按钮放在最前,首先选中第一个匹配的语言

扩写一下就是:在输入框有文本的时候,使用自定义语言那个选项替代“清空”选项,删光输入框文本之后又显示清空按钮。默认选中的选项是“清空”按钮下的第一个选项。

我认为这个方案和以下情况不冲突:

  1. 如果用户输入文本筛选语言了,那么就说明是要填充语言,这个时候就没有必要显示“清空”选项=>(输入时不显示清空按钮)
  2. 清空不能移除,这个是有需求的。就是觉得什么语言都不合适的话,把以前的语言清空。要不然无法清空。=>(不输入时显示清空按钮)
  3. 底线是最多有一个选项=>(清空和自定义语言同时只显示一个)
  4. 以及还有一个问题是目前插件没法修改“清空”选项和自定义语言选项的排序,只能固定在最前面=>()

我应当是尽量把描述与之前保持一致了,如果仍需进一步解释我可以稍微补充。看了一下15404的修改,感觉现在这个方案应该是还有一个问题:输入的语言与某个内置语言一致时应当如何显示。

@TCOTC

TCOTC commented Sep 1, 2025

Copy link
Copy Markdown
Contributor Author

输入的语言与某个内置语言一致时应当如何显示

把自定义语言放到末尾的话倒是可以直接隐藏

@88250 88250 changed the title Add plugin event bus code-language-update and code-language-change Add plugin event bus code-language-before and code-language-change Sep 2, 2025
@88250 88250 changed the title Add plugin event bus code-language-before and code-language-change Add plugin event bus code-language-update and code-language-change Sep 2, 2025
@Vanessa219

Vanessa219 commented Sep 2, 2025

Copy link
Copy Markdown
Member

要不就像 notion 和 wolai 那样都不显示了,把清空换成纯文本。这个可以新启一个 issue 了。

EightDoor added a commit to EightDoor/siyuan that referenced this pull request Sep 2, 2025
* commit 'b699675b9726e23a5b7286fdae814016d4713616': (61 commits)
  🔖 Release v3.3.1
  🔖 Release v3.3.1
  🎨 Clean code
  🎨 siyuan-note#15736
  📝 Update changelogs
  📝 Move changelogs v3.2.x
  🎨 siyuan-note#15610
  🎨 Improve database template field grouping siyuan-note#15687
  🎨 Improve database rollup template rendering siyuan-note#15722
  🎨 反链面板移除元素后,文档分屏且为空会重复添加空块
  🎨 Improve database rollup template rendering siyuan-note#15722
  🎨 Improve av
  ⚡ siyuan-note#15734
  🐛 siyuan-note#14269
  🎨 Improve av
  🎨 Block ref search and global search results display reference counts siyuan-note#15721
  🎨 siyuan-note#15693
  🚨
  🚨
  ✨ dynamic icon use current date  as default (siyuan-note#15693)
  ...
@TCOTC

TCOTC commented Sep 2, 2025

Copy link
Copy Markdown
Contributor Author

自定义语言选项是要有的,毕竟有人用。我还是觉得把自定义语言放到末尾更合适

@TCOTC

TCOTC commented Sep 2, 2025

Copy link
Copy Markdown
Contributor Author

有些别名只能通过自定义语言方式输入,有些别名输入时或许和现有语言一起出现 除了自定义语言,比如我需要输入大写的代码语言,比如“R“,如果改成你那样,就跑到最下面去了(R语言就是生物专业和统计专业最常用的语言之一)

@Achuan-2 我刚刚看了一下代码确实没有处理别名,大写的 R 不算自定义语言。我会 PR 改进一下,如果输入 "R" 的话, R 语言应该是默认选中的首个选项。这样的话你能接受把自定义语言排序在最后吗?

@Achuan-2

Achuan-2 commented Sep 3, 2025

Copy link
Copy Markdown
Member

目前自定义语言排序放在前面,不管是输入大写(有些人可能喜欢输入大写的代码语言),还是自定义语言和备注都可以,我觉得这种交互方式就是比较好的,目前用了这么久都很舒服

用不到的人,直接按enter就是选定语言列表里的语言了,也完全没影响,只不过现在你觉得自定义语言碍眼而已

要减少前面的选项可以把清空按钮放在输入框右侧,变成一个按钮

@TCOTC

TCOTC commented Sep 3, 2025

Copy link
Copy Markdown
Contributor Author

要减少前面的选项可以把清空按钮放在输入框右侧,变成一个按钮

这样的话就没法用键盘方向键选中了,所以不行

@TCOTC

TCOTC commented Sep 3, 2025

Copy link
Copy Markdown
Contributor Author

你个人平时需要输入什么自定义语言吗?

@emptylight370

Copy link
Copy Markdown
Contributor

要减少前面的选项可以把清空按钮放在输入框右侧,变成一个按钮

这样的话就没法用键盘方向键选中了,所以不行

那就把清空按钮和自定义语言按钮做成一个按钮,动态替换

你个人平时需要输入什么自定义语言吗?

自定义语言是无法穷尽的,需求也是会变的,不如保留直接自定义的入口,而不是藏起来。自定义语言功能是什么探索款吗,像电子设备一样加钱看电路板(需要花费额外精力了解到自定义语言功能在列表最下方)

@TCOTC

TCOTC commented Sep 3, 2025

Copy link
Copy Markdown
Contributor Author

还是要看实际的用户需求,理论上有但实际不会用的就不应该考虑了

boyiwan pushed a commit to boyiwan/siyuan that referenced this pull request Sep 10, 2025
boyiwan pushed a commit to boyiwan/siyuan that referenced this pull request Sep 10, 2025
boyiwan pushed a commit to boyiwan/siyuan that referenced this pull request Sep 10, 2025
leolee9086 pushed a commit to leolee9086/siyuan that referenced this pull request Oct 25, 2025
leolee9086 pushed a commit to leolee9086/siyuan that referenced this pull request Oct 25, 2025
leolee9086 pushed a commit to leolee9086/siyuan that referenced this pull request Oct 25, 2025
@TCOTC

TCOTC commented Oct 29, 2025

Copy link
Copy Markdown
Contributor Author

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Jul 5, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants