Obsidian 全局搜索增强及搜索结果可视化 - 基于 QuickAdd 实现

Obsidian 全局搜索转换为高级搜索界面,灵感来自知网高级检索界面 [1]

功能特点

:green_circle:Tip:推荐配合 better search views 使用,自带搜索结果并不支持渲染,安装这个插件后可以渲染检索结果。

实现过程

参考知网的高级检索的控件,强化 Obsidian 自带的全局搜索,设计草图以及 html 中实现。

最终合并到自带的全局搜索元素上,Obsidian 中按钮这些样式就比较难调,而且很受主题的影响,所以比原计划的要难看点。

搜索运算符类型

检索的运算符类型主要有这几种:

搜索运算符 描述
file: 查找文件名中的文本。匹配 Vault 中的任何文件。
示例: file:.jpgfile:202209
path: 在文件路径中查找文本。匹配 Vault 中的任何文件。
示例: path:"Daily notes/2022-07"
content: 在文件内容中查找文本。
示例: content:"happy cat"
match-case: 区分大小写的匹配。
示例: match-case:HappyCat
ignore-case: 不区分大小写的匹配。
示例: ignore-case:ikea
tag: 在文件中查找标签。
示例: tag:#work
请记住,搜索 tag:#work 不会返回 #myjob/work 的结果。
注意:由于 tag: 忽略代码块和非 Markdown 内容中的匹配,因此它通常比 #work 的普通全文搜索更快、更准确。
line: 在同一行查找匹配项。
示例: line:(mix flour)
block: 在同一块中查找匹配项。
示例: block:(dog cat)
注意:由于 block: 需要搜索来解析每个文件中的 Markdown 内容,因此可能会导致您的搜索词需要更长的时间才能完成。
section: 在同一部分(两个标题之间的文本)中查找匹配项。
示例: section:(dog cat)
task: 按照块的方式在任务中查找匹配项。
示例: task:call
task-todo: 按照区块的方式在未完成的任务中找到匹配项。
示例: task-todo:call
task-done: 按照区块的方式在已完成的任务中找到匹配项。
示例: task-done:call

基于 Obsidian 原生搜索运算符实现,搜索案例:

类型 使用场景 示例
文件名搜索 查找特定文件名 file:.jpg
路径搜索 定位特定目录下的文件 path:"Daily notes"
内容搜索 全文检索 content:"关键词"
标签搜索 通过标签筛选 tag:#work
任务搜索 查找待办事项 task-todo:call

脚本源码

/*
 * @Author: 熊猫别熬夜 
 * @Date: 2025-01-15 00:13:57 
 * @Last Modified by: 熊猫别熬夜
 * @Last Modified time: 2025-01-19 00:40:21
 */
module.exports = async () => {
  (function () {
    // 创建并添加样式
    const styleElement = document.createElement("style");
    styleElement.textContent = `
.search-params{
  display: flex;
  flex-flow: row wrap;
  justify-content: space-between;
}
.search-form-container {
  width: 100%;
  padding: 10px;
  margin: auto;
  background-color: var(--background-primary);

  button {
    background-color: var(--background-primary);
    border-radius: 4px;
    border: 1px solid var(--background-modifier-border);
  }

  select, label, button {
    padding: 4px;
  }

  /* 隐藏第一行操作符和删除按钮 */
  .form-row:first-child .operator,
  .form-row:first-child .remove-row {
    display: none;
  }

  .form-row {
    display: flex;
    gap: 0 5px;
    align-items: center;
    margin-bottom: 10px;

    input[type="text"] {
      flex: 1;
      border-width: 1px;
    }
  }

}


.input-group {
  display: flex;
  width: 100%;
  align-items: center;
  height: 20px;


  input {
    margin-right: 0px !important;
    padding: 5px;
    border-radius: 4px 0 0 4px;
    box-shadow: none !important;
  }

  .icon-button {
    box-shadow: none;
    color: var(--text-normal);
    margin-left: 0px !important;
    border-left: none;
    border-radius: 0 4px 4px 0;
    cursor: pointer;

  }

  .icon-button[data-select-option=""] {
    display: none;
  }
}


/* 大小写和正则控件 */
.controls {
  display: flex;
  gap: 0 2px;

  .toggle.toggle {
    padding: 0px;
    margin: 0px;
    cursor: pointer;
    display: flex;

    input {
      display: none;
    }
  }

  .toggle-label {
    display: flex;
    align-items: center;
    justify-items: center;
    padding: 2px 2px;
    border-radius: 4px;
  }

  .toggle input:checked+.toggle-label {
    background: var(--background-modifier-hover);
  }
}

.button-group {
  display: flex;
  justify-content: space-between;

  button {
    padding: 5px 10px;
  }
}

.date-group {
  width: 100%;
  display: flex;
  justify-content: space-between;

  button {
    border-width: 1px;
    border-radius: 3px;
  }
}

.navigation-buttons {
  width: 100%;
  display: flex;
  justify-content: space-between;
  gap: 4px;

  button {
    border: none;
  }

  .import-button,
  .copy-button,
  .reset-button {
    flex: 1;
    font-size: large;
  }

  .graph-button,
  .search-button {
    flex: 1;
    color: var(--text-accent);
    font-size: large;
  }

  .reset-button {
    color: var(--text-error);
  }
}

.result {
  margin-top: 10px;

  textarea {
    width: 100%;
    height: 300px;
    resize: vertical;
  }
}
  `;
    document.head.appendChild(styleElement);

    // 移除已存在的 form-container
    const existingContainer = document.querySelector('.search-form-container');
    if (existingContainer) {
      existingContainer.remove();
    }

    // 创建 HTML 结构
    const queryControlsContainer = document.createElement("div");
    queryControlsContainer.className = "search-form-container";
    queryControlsContainer.innerHTML = `
<div class="search-section"><div class="form-row"><select class="operator"><option>AND</option><option>OR</option><option>NOT</option></select><select class="type"><option>all</option><option>file</option><option>tag</option><option>path</option></select><div class="input-group"><input type="text"name="file"><button class="icon-button"onclick="handleTypeIconClick(this)"></button></div><!--大小写和正则控件--><div class="controls"><label class="toggle"><input type="radio"name="search-mode"class="case-sensitive"><span class="toggle-label"><svg xmlns="http://www.w3.org/2000/svg"width="24"height="24"viewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="2"stroke-linecap="round"stroke-linejoin="round"class="svg-icon uppercase-lowercase-a"><path d="M10.5 14L4.5 14"></path><path d="M12.5 18L7.5 6"></path><path d="M3 18L7.5 6"></path><path d="M15.9526 10.8322C15.9526 10.8322 16.6259 10 18.3832 10C20.1406 9.99999 20.9986 11.0587 20.9986 11.9682V16.7018C20.9986 17.1624 21.2815 17.7461 21.7151 18"></path><path d="M20.7151 13.5C18.7151 13.5 15.7151 14.2837 15.7151 16C15.7151 17.7163 17.5908 18.2909 18.7151 18C19.5635 17.7804 20.5265 17.3116 20.889 16.6199"></path></svg></span></label><label class="toggle"><input type="radio"name="search-mode"class="regex"><span class="toggle-label"><svg xmlns="http://www.w3.org/2000/svg"width="24"height="24"viewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="2"stroke-linecap="round"stroke-linejoin="round"class="lucide lucide-regex"><path d="M17 3v10"/><path d="m12.67 5.5 8.66 5"/><path d="m12.67 10.5 8.66-5"/><path d="M9 17a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2z"/></svg></span></label></div><button class="remove-row"onclick="removeRow(this)">➖</button><button class="add-row"onclick="addRow(this)">➕</button></div></div><!--<div class="date-group"><div class="creation-date"><label>创建时间:</label><input type="date"name="date_from"><span>~</span><input type="date"name="date_to"><button class="clear-date"onclick="clearDate(this)">清空</button></div><div class="modification-date"><label>修改时间:</label><input type="date"name="date_from"><span>~</span><input type="date"name="date_to"><button class="clear-date"onclick="clearDate(this)">清空</button></div></div>--><div class="navigation-buttons"><button class="import-button"onclick="importFromSearchBox()">导入</button><button class="copy-button"onclick="copySearchQuery()">复制</button><button class="graph-button"onclick="openGraphView()">图谱</button><button class="search-button"onclick="executeSearch()">搜索</button><button class="reset-button"onclick="clearSearchForm()">重置</button></div>
`;
    // 添加容器
    document.body.appendChild(queryControlsContainer);

    // 找到搜索容器并在其第一个子元素前插入
    const searchContainer = document.querySelector('.workspace-leaf-content[data-type="search"]');
    if (searchContainer) {
      searchContainer.insertBefore(queryControlsContainer, searchContainer.children[0]);
    }
  })();


  function generateIcons(options, values) {
    return options.reduce((icons, option) => {
      icons[option] = values[option] || '';
      return icons;
    }, {});
  }

  const options = ["all", "file", "tag", "path", "content", "line", "block", "section", "task", "task-todo", "tasks-done"];
  const iconsWithValues = {
    'file': '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sticky-note"><path d="M16 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8Z"/><path d="M15 3v4a2 2 0 0 0 2 2h4"/></svg>',
    'tag': '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-tags"><path d="m15 5 6.3 6.3a2.4 2.4 0 0 1 0 3.4L17 19"/><path d="M9.586 5.586A2 2 0 0 0 8.172 5H3a1 1 0 0 0-1 1v5.172a2 2 0 0 0 .586 1.414L8.29 18.29a2.426 2.426 0 0 0 3.42 0l3.58-3.58a2.426 2.426 0 0 0 0-3.42z"/><circle cx="6.5" cy="9.5" r=".5" fill="currentColor"/></svg>',
    'path': '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-folder-closed"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/><path d="M2 10h20"/></svg>'
  };
  const icons = generateIcons(options, iconsWithValues);

  // !点击图标触发事件
  async function handleTypeIconClick(button) {
    const row = button.closest('.form-row');
    const type = row.querySelector('.type').value;
    const options = getOptionsByType(type);
    const quickAddApi = app.plugins.plugins.quickadd.api;
    const choice = await quickAddApi.suggester(options, options);
    if (choice) {
      const type = row.querySelector('.type').value;
      if (type === 'tag') {
        row.querySelector('input[type="text"]').value += ` ${choice.replace(/^#/, '')}`;
      } else {
        row.querySelector('input[type="text"]').value += ` "${choice}"`;
      }
    }
  }

  // 图标类型
  function getOptionsByType(type) {
    let options;
    switch (type) {
      case 'file':
        options = app.vault.getFiles()
          .filter(f => f.path.endsWith('.md'))
          .map(f => f.basename);
        options.sort();
        break;
      case 'tag':
        options = Object.keys(app.metadataCache.getTags());
        options.sort();
        break;
      case 'path':
        options = app.vault.getAllFolders().map(f => f.path);
        break;
      default:
        return [];
    }
    return options;
  }

  // !添加和删除按钮
  function addRow(button) {
    const currentRow = button.closest('.form-row');
    const currentType = currentRow.querySelector('.type').value;
    const currentOperator = currentRow.querySelector('.operator').value;
    const newRow = currentRow.cloneNode(true);

    // 只重置文本输入
    newRow.querySelectorAll('input[type="text"]').forEach(input => {
      input.value = '';
    });

    // 为新行的 radio 设置新的 name
    const newName = `search-mode-${Math.random().toString(36).substr(2, 9)}`;
    newRow.querySelectorAll('input[type="radio"]').forEach(radio => {
      radio.name = newName;
    });

    // 设置 operator 的值
    newRow.querySelector('.operator').value = currentOperator;

    currentRow.parentNode.insertBefore(newRow, currentRow.nextSibling);
    initializeRow(newRow, currentType);
  }
  function removeRow(button) {
    const row = button.closest('.form-row');
    const container = row.parentNode;
    if (container.querySelectorAll('.form-row').length > 1) {
      row.remove();
    }
  }

  function initializeRow(row, option, clearInputs = false) {
    if (clearInputs) {
      row.querySelectorAll('input[type="text"]').forEach(input => {
        input.value = '';
      });
    }
    const typeSelect = row.querySelector('.type');
    const button = row.querySelector('.icon-button');

    // 使用选中的属性初始化选项
    typeSelect.innerHTML = options.map(opt =>
      `<option>${opt}</option>`
    ).join('');

    // 设置默认选项
    typeSelect.value = option; // 确保选择项被设置

    // 添加 change 事件监听器
    typeSelect.addEventListener('change', function () {
      const selectedOption = typeSelect.value;
      button.setAttribute('data-select-option', icons[selectedOption]);
      button.innerHTML = icons[selectedOption];
    });

    // 触发 change 事件以设置初始状态
    typeSelect.dispatchEvent(new Event('change'));

    // 添加可取消选择的单选框逻辑
    const radios = row.querySelectorAll('input[type="radio"]');
    const rowName = `search-mode-${Math.random().toString(36).substr(2, 9)}`; // 每行共用一个name
    radios.forEach(radio => {
      radio.name = rowName; // 同一行的radio使用相同name实现互斥
      let lastState = false;
      radio.addEventListener('click', function () {
        if (this.checked && lastState) {
          this.checked = false;
        }
        lastState = this.checked;
      });
    });
  }

  function clearDate(button) {
    const container = button.parentElement;
    container.querySelectorAll('input[type="date"]').forEach(input => {
      input.value = '';
    });
  }

  // 转换查询条件为 Obsidian 搜索语法
  function convertToObsidianQuery(formRows, lineBreak = false) {
    let query = [];
    formRows.forEach(row => {
      const operator = row.querySelector('.operator').value;
      let type = row.querySelector('.type').value;
      type = type === 'all' ? "" : `${type}:`;
      const input = row.querySelector('input[type="text"]').value;
      const isCaseSensitive = row.querySelector('.case-sensitive').checked;
      const isRegex = row.querySelector('.regex').checked;

      if (input.trim()) {
        let searchTerm = input;
        if (isRegex) {
          searchTerm = `/${searchTerm}/`;
        } else if (type == 'tag:') {
          searchTerm = searchTerm.trim().split(" ").map(t => t.startsWith("#") ? t : `#${t}`).join(" ");
        } else {
          searchTerm = `(${searchTerm})`;
        }

        if (isCaseSensitive) {
          searchTerm = `match-case:${searchTerm}`;
        }

        let queryPart = '';
        switch (operator) {
          case 'AND': queryPart = `(${type}${searchTerm})`; break;
          case 'OR': queryPart = `${operator} (${type}${searchTerm})`; break;
          case 'NOT': queryPart = `-(${type}${searchTerm})`; break;
        }
        query.push(queryPart);
      }
    });

    return lineBreak ? query.join("\n") : query.join(" ");
  }

  // 搜索按钮点击处理函数
  function executeSearch() {
    const formRows = document.querySelectorAll('.form-row');
    const queryValue = convertToObsidianQuery(formRows);
    const searchInputs = document.querySelectorAll('.search-input-container > input');
    searchInputs.forEach(searchInput => {
      if (searchInput && searchInput.value !== queryValue) {
        searchInput.value = queryValue;
        searchInput.dispatchEvent(new Event('input', { bubbles: true }));
        searchInput.dispatchEvent(new KeyboardEvent('keydown', {
          key: 'Escape',
          code: 'Escape',
          keyCode: 27,
          bubbles: true
        }));
      }
    });
  }

  function openGraphView() {
    app.commands.executeCommandById("graph:open");

    setTimeout(() => {
      const formRows = document.querySelectorAll('.form-row');
      const queryValue = convertToObsidianQuery(formRows);

      // 更新图谱视图的搜索框
      const graphSearch = document.querySelector('.graph-control-section .search-input-container input');
      if (graphSearch) {
        graphSearch.value = queryValue;
        graphSearch.dispatchEvent(new Event('input', { bubbles: true }));
        graphSearch.dispatchEvent(new KeyboardEvent('keydown', {
          key: 'Escape',
          code: 'Escape',
          keyCode: 27,
          bubbles: true
        }));
      }
    }, 100);
  }

  // 添加重置函数
  function clearSearchForm(n = 2) {
    const container = document.querySelector('.search-section');
    if (!container) return;
    const templateRow = container.querySelector('.form-row');
    if (!templateRow) return;
    // 清空现有内容
    container.innerHTML = '';
    // 添加初始行
    for (let i = 0; i < n; i++) {
      const newRow = templateRow.cloneNode(true);
      container.appendChild(newRow);
      initializeRow(newRow, options[0], true);
    }
    // 触发 change 事件以确保 UI 更新
    container.dispatchEvent(new Event('change', { bubbles: true }));
  }

  // 添加导入功能
  function importFromSearchBox() {
    const searchInput = document.querySelector('.workspace-leaf-content[data-type="search"] .search-row input');
    if (!searchInput || !searchInput.value.trim()) {
      new Notice('No query to import');
      return;
    }

    // 解析查询字符串
    const query = searchInput.value.trim();
    const parts = query.split(/(?<=\)) (?=[-(]|\w+:|\()/g).filter(p => p.trim());

    // 清空现有行并生成对应行数
    clearSearchForm(parts.length);
    const container = document.querySelector('.search-section');
    const templateRow = container.querySelector('.form-row');

    parts.forEach((part, index) => {
      let row = index === 0 ? templateRow : templateRow.cloneNode(true);
      if (index > 0) {
        container.appendChild(row);
        initializeRow(row, 'all');
      }

      // 解析操作符
      if (part.startsWith('-')) {
        row.querySelector('.operator').value = 'NOT';
        part = part.slice(1);
      } else if (part.startsWith('OR ')) {
        row.querySelector('.operator').value = 'OR';
        part = part.slice(3);
      } else {
        row.querySelector('.operator').value = 'AND';
      }

      // 解析类型和值
      let type = 'all';
      let value = part.replace(/^\(|\)$/g, '');

      const typeMatch = value.match(/^(file|tag|path|content|line|block|section|task|task-todo|tasks-done):/);
      if (typeMatch) {
        type = typeMatch[1];
        value = value.slice(typeMatch[0].length);
      }

      // 设置类型
      row.querySelector('.type').value = type;

      // 处理大小写和正则
      const caseSensitive = value.startsWith('match-case:');
      if (caseSensitive) {
        row.querySelector('.case-sensitive').checked = true;
        value = value.slice(11);
      }

      const isRegex = value.startsWith('/') && value.endsWith('/');
      if (isRegex) {
        row.querySelector('.regex').checked = true;
        value = value.slice(1, -1);
      }

      // 处理标签特殊格式
      if (type === 'tag') {
        value = value.replace(/#/g, '');
      }
      // 设置值
      row.querySelector('input[type="text"]').value = value.replace(/^\(|\)$/g, '');
    });

    // 清空空行
    const rows = container.querySelectorAll('.form-row');
    rows.forEach(row => {
      const input = row.querySelector('input[type="text"]');
      if (!input.value.trim()) {
        row.remove();
      }
    });
  }

  function copyToClipboard(extrTexts) {
    const txtArea = document.createElement('textarea');
    txtArea.value = extrTexts;
    document.body.appendChild(txtArea);
    txtArea.select();
    if (document.execCommand('copy')) {
      new Notice('Copied to clipboard');
    } else {
      new Notice('Failed to copy');
    }
    document.body.removeChild(txtArea);
  }

  function copySearchQuery() {
    const formRows = document.querySelectorAll('.form-row');
    const queryValue = convertToObsidianQuery(formRows, true);
    const formattedQuery = `\`\`\`query\n${queryValue}\n\`\`\``;
    copyToClipboard(formattedQuery);
  }

  // 把所有函数暴露到全局作用域
  window.addRow = addRow;
  window.removeRow = removeRow;
  window.handleTypeIconClick = handleTypeIconClick;
  window.openGraphView = openGraphView;
  window.executeSearch = executeSearch;
  window.clearSearchForm = clearSearchForm;
  window.clearDate = clearDate;
  // 绑定导入按钮事件
  window.importFromSearchBox = importFromSearchBox;
  window.copySearchQuery = copySearchQuery;

  // 修改初始化逻辑
  (function initialize() {
    clearSearchForm();
  })();
};

脚本配置

该脚本主要是通过 QuickAdd Macro 运行的,配置流程如下:

  1. 将脚本代码保存为 js 文件
  2. 将该 js 文件放置到 QuickAdd 设置的模版路径中
  3. 添加一个 QuickAdd Macro 动作
    1. image
  4. 设置 Macro 动作,将刚刚的定义的脚本选择进来
    1. image
  5. 配置一个快捷键或设置一个按钮 (通过 Commanders 或 Note toolbar 插件) 来启动该命令。

存在问题

  1. 不能实现比较复杂的分组组合语法
  2. 如果全局搜索界面关闭后控件会消失,需要重新运行该脚本。

Reference

功能小结

image


  1. ↩︎

5 个赞

自带的搜索也还行,搞不懂的是为什么没有替换功能…

非常有用哈,把系统 的这个搜索,可视化了的, 非常有利于小白啦,非常感谢 ; 不能分组也不是你的问题;我看了下help ,官方的语法就不支持分组,要分组,还不得用 js写了的哈,这个已经很好了;

1 个赞

只能说能勉强符合需求吧,要是有复杂的搜索、正则替换需求,我就换 vscode 打开了 :smiling_face_with_tear:

替换我也是用vscode,vsc的搜索替换确实很好用,希望来个大佬做个类似的ob插件(做梦ing

拓展:配合 Quick Tagger 批量修改标签

发现插件Quick Tagger插件可以配合这个图谱和检索结果批量添加和删除标签:

特别基于文件夹和特殊文件名的检索后的修改。

批量添加标签:
PixPin_2025-01-16_17-38-01

批量删除标签:
PixPin_2025-01-17_14-57-05

感觉特别解压!哈哈哈

除了标签都可以支持正则语法,点击控件后面的.*按钮既可:
image

2025-01-16

  1. 优化<图谱>按钮的事件,只刷新图谱而不刷新搜索视图,<搜索>按钮还是更新 2 个视图。
    • 可以简单当做图谱的筛选控件。
  2. 新增<导入>按钮,可以将控件生成在:mag:输入框中 query 语法的还原为多行组合格式。
    • 可用于将收藏的 query 检索书签还原多行组合格式进一步调整。
    • 但应该只能解析基本的语法,存在BUG且不会优化,还请注意。

1 个赞

2025-01-18

  • 添加<复制>按钮,可以将当前的检索转换为Query语法并复制到剪切中。
    PixPin_2025-01-18_23-47-20
1 个赞

看着有一点点可爱

大佬,越做越好了,但我选择等待下一个版本哈 :grin: ,后面的肯定更好

1 个赞

大佬,看到这里有一个想法,既然搜索语法可视化了,点击正则按钮是否也可以弹出正则语法可视化编辑框,是否可以先做出一些常用正则的可视化编辑框。

还有搜索框是否考虑根据窗口宽度进行自动调整,比如在窗口变窄的时候能够自动缩小文字等,使得全部内容能显示出在调窄的宽度窗口。有些人可能习惯使用双栏左右分屏,左边任务栏的宽度比较窄,变窄后一些内容就被遮住了,比如我这样的


有时间是否考虑加一个设置帮助之类按钮,比如点击帮助文档按钮,可以跳转到网页文档使用手册,上面有介绍一些常用搜索情景,并给出这些情景对应的方法示例等,大家也可以共享编辑,分享一些自己组合语法的技巧、心得、案例等,一起完善,不用每个人独自慢慢摸索。

可能我一般ob界面的缩放比例比较小,所以没意识得到这个问题,我晚点看看,或者你可以Ctrl+-+号可以缩放Obsidian的缩放比例,Ob字体大小也可以单独设置。

1 个赞

好的,谢谢分享,我刚才试了一下, Ctrl +-无法改变左侧框的大小,不知道系统搜索框本身是不支持还是怎么样。其他的插件比如file tree插件的框就能随着改变字体大小。不过功能很好,不行我就拉大一点。

也可能与系统的缩放比例有关

1 个赞

好的,感谢您分享解决方案 :smile: