diff --git a/doc/image-1.png b/doc/image-1.png
new file mode 100644
index 0000000..c3f1888
Binary files /dev/null and b/doc/image-1.png differ
diff --git a/doc/image.png b/doc/image.png
new file mode 100644
index 0000000..cf665ee
Binary files /dev/null and b/doc/image.png differ
diff --git a/doc/文档tab页.md b/doc/文档tab页.md
new file mode 100644
index 0000000..be4d015
--- /dev/null
+++ b/doc/文档tab页.md
@@ -0,0 +1,132 @@
+# 文档页面开发记录
+
+## 文档页面实现功能
+1. 文档分页获取文件为markdown格式
+2. 布局:
+ 
+ - 左侧为标题目录
+ 1. 支持折叠展开
+ 1. 支持点击时 右侧文档滚动到对应位置
+ - 右侧为文档主题
+ 1. 支持展示图片
+ 1. 支持特殊标记实现点击时打开工作区内文件中的某个函数
+ 目前暂定格式与示例\
+ 示例:
+ [打开vscode中的src/test.js:testFunction](__workspace/src/test.js?functionName=resolveTripleslashReference)
+ 格式:
+ [显示文字](__workspace/[文件的相对路径]?functionName=[函数名])
+ 关键代码:
+ ``` js
+ ///webview\src\components\MarkdownViewer.vue
+
+ /// ...
+ const renderLink = renderer.link
+ const handleDelegateClick = (event) => {
+ // 通过事件冒泡捕获目标元素
+ const target = event.target.closest('.openFileAndGoToFunction')
+ if (target) {
+ const { filepath, functionname } = target.dataset
+ console.log(filepath, functionname, '_**=== filepath, functionname');
+ console.log(target.dataset, '_**=== target.dataset');
+ if (window.acquireVsCodeApi && window.vscodeApi) {
+ window.vscodeApi.postMessage({
+ type: 'openFileAndGoToFunction',
+ filePath: filepath,
+ functionName: functionname
+ });
+ }
+ }
+ }
+
+ renderer.link = function (src) {
+ // 工作区跳转
+ if (src.href.startsWith("__workspace/")) {
+ const { href, title, tokens } = src
+ const text = renderer.parser.parseInline(tokens);
+ // const cleanHref = cleanUrl(href);
+ const titleTip = href.slice("__workspace/".length);
+ const [filePath, functionName] = titleTip.split('?functionName=')
+ console.log(filePath, functionName, '_**=== filePath, functionName');
+ return `${text}`
+ }
+ return renderLink.call(renderer, src);
+ }
+ ```
+
+ ```typescript
+ // src\extension\ChatViewProvider.ts
+ async openFileAndGoToFunction(message: ListenerParam): Promise {
+ if (message.type !== 'openFileAndGoToFunction') { return false };
+ const rootUri = vscode.workspace.workspaceFolders?.[0]?.uri;
+ const filePath = message.filePath; // 确保路径正确,相对于工作区
+ this._logger(rootUri + '#####' + filePath)
+ const targetFunc = message.functionName
+ if (!filePath || !targetFunc) {
+ vscode.window.showWarningMessage(`未提供文件: ${filePath} 或函数: ${targetFunc}`)
+ return
+ }
+ try {
+ // 打开文件
+ const docInfo = await this.openWorkspaceFile(filePath);
+ if (!docInfo) {
+ vscode.window.showWarningMessage(`无法打开或创建文件: ${filePath}`)
+ return
+ }
+ const { editor, document } = docInfo
+ // 获取文档符号
+ const symbols = await vscode.commands.executeCommand(
+ 'vscode.executeDocumentSymbolProvider',
+ document.uri
+ )
+
+ if (symbols) {
+ const funcSymbol = this.findFunction(symbols, targetFunc)
+ if (funcSymbol) {
+ // 跳转到目标位置
+ editor.revealRange(funcSymbol.range, vscode.TextEditorRevealType.InCenter)
+ editor.selection = new vscode.Selection(funcSymbol.range.start, funcSymbol.range.end)
+ } else {
+ vscode.window.showWarningMessage(`未找到函数 ${targetFunc}`)
+ }
+ }
+ } catch (error) {
+ vscode.window.showErrorMessage(`无法打开文件: ${error}`)
+ }
+ }
+ // 打开工作区的文件
+ async openWorkspaceFile(relativePath: string) {
+ // 1. 获取工作区根路径
+ const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri;
+ if (!workspaceRoot) {
+ vscode.window.showErrorMessage(" 未检测到工作区");
+ return;
+ }
+
+ // 2. 拼接完整路径(自动处理跨平台路径)
+ const targetUri = vscode.Uri.joinPath(workspaceRoot, relativePath);
+
+ // 3. 文件存在性检测
+ try {
+ await vscode.workspace.fs.stat(targetUri);
+ } catch {
+ const createNew = await vscode.window.showInformationMessage(
+ "文件不存在,是否创建?",
+ "创建", "取消"
+ );
+ if (createNew === "创建") {
+ await vscode.workspace.fs.writeFile(targetUri, Buffer.from(""));
+ } else {
+ return;
+ }
+ }
+ this._logger(targetUri + ':::targetUri')
+ // 4. 打开文件编辑器
+ const document = await vscode.workspace.openTextDocument(targetUri);
+ const editor = await vscode.window.showTextDocument(document);
+ return { document, editor }
+ }
+ ```
+若需要改为标题或列表, 修改 renderer 的对应方法以及触发跳转
+
+https://marked.nodejs.cn/using_pro#hooks
+
diff --git a/src/extension.ts b/src/extension.ts
index cd3360a..a89fc6a 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -9,7 +9,7 @@ function log(message: string) {
if (!outputChannel) {
outputChannel = vscode.window.createOutputChannel('AI Chat Plugin');
}
-
+
const timestamp = new Date().toISOString();
outputChannel.appendLine(`[${timestamp}] ${message}`);
}
@@ -18,7 +18,7 @@ export function activate(context: vscode.ExtensionContext) {
// 创建输出通道
outputChannel = vscode.window.createOutputChannel('AI Chat Plugin');
outputChannel.show(true);
-
+
log('VSCode AI Chat Plugin 已激活');
// 注册Webview视图提供程序
@@ -37,7 +37,7 @@ export function activate(context: vscode.ExtensionContext) {
vscode.commands.executeCommand('workbench.view.extension.aiChatView');
})
);
-
+
// 监听配置变更
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration(e => {
@@ -50,8 +50,8 @@ export function activate(context: vscode.ExtensionContext) {
export function deactivate() {
log('VSCode AI Chat Plugin 已停用');
-
+
if (outputChannel) {
outputChannel.dispose();
}
-}
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/extension/ChatViewProvider.ts b/src/extension/ChatViewProvider.ts
index e176d05..9eea7d4 100644
--- a/src/extension/ChatViewProvider.ts
+++ b/src/extension/ChatViewProvider.ts
@@ -486,34 +486,38 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
}
findFunction(symbols: vscode.DocumentSymbol[], name: string): vscode.DocumentSymbol | false {
for (const symbol of symbols) {
- if (symbol.kind === vscode.SymbolKind.Function && symbol.name === name) {
- return symbol;
- }
- if (symbol.children) {
- const found = this.findFunction(symbol.children, name);
- if (found) return found;
- }
+ if (symbol.kind === vscode.SymbolKind.Function && symbol.name === name) {
+ return symbol;
+ }
+ if (symbol.children) {
+ const found = this.findFunction(symbol.children, name);
+ if (found) return found;
+ }
}
return false;
}
async openFileAndGoToFunction(message: ListenerParam): Promise {
if (message.type !== 'openFileAndGoToFunction') { return false };
- const filePath = message.filePath; // 确保路径正确,可以是绝对路径或相对于工作区
+ const rootUri = vscode.workspace.workspaceFolders?.[0]?.uri;
+ const filePath = message.filePath; // 确保路径正确,相对于工作区
+ this._logger(rootUri + '#####' + filePath)
const targetFunc = message.functionName
- if (!filePath || !targetFunc){
+ if (!filePath || !targetFunc) {
vscode.window.showWarningMessage(`未提供文件: ${filePath} 或函数: ${targetFunc}`)
return
}
try {
// 打开文件
- const uri = vscode.Uri.file(filePath)
- const doc = await vscode.workspace.openTextDocument(uri)
- const editor = await vscode.window.showTextDocument(doc)
-
+ const docInfo = await this.openWorkspaceFile(filePath);
+ if (!docInfo) {
+ vscode.window.showWarningMessage(`无法打开或创建文件: ${filePath}`)
+ return
+ }
+ const { editor, document } = docInfo
// 获取文档符号
const symbols = await vscode.commands.executeCommand(
'vscode.executeDocumentSymbolProvider',
- doc.uri
+ document.uri
)
if (symbols) {
@@ -546,4 +550,36 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
}
webview.postMessage({ type: 'mdContent', data: '未正确传入md文件路径' })
}
-}
+ // 打开工作区的文件
+ async openWorkspaceFile(relativePath: string) {
+ // 1. 获取工作区根路径
+ const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri;
+ if (!workspaceRoot) {
+ vscode.window.showErrorMessage(" 未检测到工作区");
+ return;
+ }
+
+ // 2. 拼接完整路径(自动处理跨平台路径)
+ const targetUri = vscode.Uri.joinPath(workspaceRoot, relativePath);
+
+ // 3. 文件存在性检测
+ try {
+ await vscode.workspace.fs.stat(targetUri);
+ } catch {
+ const createNew = await vscode.window.showInformationMessage(
+ "文件不存在,是否创建?",
+ "创建", "取消"
+ );
+ if (createNew === "创建") {
+ await vscode.workspace.fs.writeFile(targetUri, Buffer.from(""));
+ } else {
+ return;
+ }
+ }
+ this._logger(targetUri + ':::targetUri')
+ // 4. 打开文件编辑器
+ const document = await vscode.workspace.openTextDocument(targetUri);
+ const editor = await vscode.window.showTextDocument(document);
+ return { document, editor }
+ }
+}
\ No newline at end of file
diff --git a/webview/public/ruanjian.md b/webview/public/ruanjian.md
index 21396de..f40da02 100644
--- a/webview/public/ruanjian.md
+++ b/webview/public/ruanjian.md
@@ -37,4 +37,9 @@
- **快速主题与滤镜**
- 全局主题与滤镜:支持全局主题设置,用户可以一键切换不同的主题风格,包括颜色、字体、背景等元素,快速调整数据可视化的整体视觉效果。同时,提供滤镜功能,用户可以对图表进行美化处理,如添加阴影、渐变色等效果,提升数据可视化的美观度。
-
\ No newline at end of file
+
+
+示例:
+[打开vscode中的src/test.js:testFunction](__workspace/src/test.js?functionName=resolveTripleslashReference)
+格式:
+[显示文字](__workspace/[文件的相对路径]?functionName=[函数名])
\ No newline at end of file
diff --git a/webview/src/components/MarkdownViewer.vue b/webview/src/components/MarkdownViewer.vue
index 059e9f7..fe70335 100644
--- a/webview/src/components/MarkdownViewer.vue
+++ b/webview/src/components/MarkdownViewer.vue
@@ -7,12 +7,12 @@
目录
-
+
-
+
加载中...
{{ error }}
@@ -74,9 +74,9 @@ renderer.heading = ({ text, depth }) => {
toc.value.push({
id: escapedText,
level: depth,
- title: text,
- anchor: escapedText
- })
+ title: text,
+ anchor: escapedText
+ })
const node = {
key: escapedText,
title: text
@@ -106,11 +106,61 @@ renderer.heading = ({ text, depth }) => {
${text}
`;
};
-let renderList = renderer.listitem
-
-renderer.listitem = (src)=>{
+// 渲染列表
+const renderList = renderer.listitem
+// 若需改为列表跳转, 可改此函数
+renderer.listitem = (src) => {
return renderList.call(renderer, src);
}
+const handleDelegateClick = (event) => {
+
+ // 通过事件冒泡捕获目标元素
+ const target = event.target.closest('.openFileAndGoToFunction')
+ if (target) {
+ const { filepath, functionname } = target.dataset
+ console.log(filepath, functionname, '_**=== filepath, functionname');
+ console.log(target.dataset, '_**=== target.dataset');
+ if (window.acquireVsCodeApi && window.vscodeApi) {
+ window.vscodeApi.postMessage({
+ type: 'openFileAndGoToFunction',
+ filePath: filepath,
+ functionName: functionname
+ });
+ }
+ }
+}
+
+const renderLink = renderer.link
+renderer.link = function (src) {
+ /*
+ {
+ "type": "link",
+ "raw": "[打开vscode中的文件1](__workspace/)",
+ "href": "__workspace/",
+ "title": null,
+ "text": "打开vscode中的文件1",
+ "tokens": [
+ {
+ "type": "text",
+ "raw": "打开vscode中的文件1",
+ "text": "打开vscode中的文件1",
+ "escaped": false
+ }
+ ]
+ }
+ */
+ // 工作区跳转
+ if (src.href.startsWith("__workspace/")) {
+ const { href, title, tokens } = src
+ const text = renderer.parser.parseInline(tokens);
+ // const cleanHref = cleanUrl(href);
+ const titleTip = href.slice("__workspace/".length);
+ const [filePath, functionName] = titleTip.split('?functionName=')
+ console.log(filePath, functionName, '_**=== filePath, functionName');
+ return `${text}`
+ }
+ return renderLink.call(renderer, src);
+}
marked.setOptions({
renderer,
highlight: (code, lang) => {
@@ -135,9 +185,8 @@ const loadMarkdownFile = async () => {
loading.value = true;
error.value = null;
let fetchMdPms
-
- if(filePath.startsWith('__localWebViewPath/')){
- if(!window.vscodeApi){
+ if (filePath.startsWith('__localWebViewPath/')) {
+ if (!window.vscodeApi) {
console.error('获取__localWebViewPath的md文档时, 无法获取VSCode API')
return
}
@@ -158,7 +207,7 @@ const loadMarkdownFile = async () => {
markdownText.value = event.data.data
}
});
- }else{
+ } else {
fetchMdPms = fetch(props.filePath);
const response = await fetchMdPms
if (!response.ok) throw new Error('无法加载Markdown文件');
@@ -178,8 +227,6 @@ const loadMarkdownFile = async () => {
// 渲染Markdown
const renderMarkdown = () => {
- console.log(markdownText.value);
-
toc.value = []; // 清空目录
renderedContent.value = marked(markdownText.value);
// 等待DOM更新后初始化高亮
@@ -187,6 +234,7 @@ const renderMarkdown = () => {
document.querySelectorAll('pre code').forEach(block => {
hljs.highlightElement(block);
});
+ // 懒加载, todo 兼容跳转
// setupHeadingObservers();
});
};
@@ -244,7 +292,7 @@ const handleResize = throttle(() => {
}, props.debounceTime);
// 生命周期钩子
-onBeforeMount(()=>{
+onBeforeMount(() => {
loadMarkdownFile();
})
onMounted(() => {
@@ -309,9 +357,11 @@ onUnmounted(() => {
margin: 0;
font-size: 1.2rem;
}
+
.toc-content {
height: calc(100vh - 200px);
}
+
.toc-content ul {
list-style: none;
padding: 0;