Skip to content

Commit a5bb916

Browse files
committed
feat: add read image from clipboard for smart-paste
1 parent 4f18ded commit a5bb916

16 files changed

Lines changed: 365 additions & 46 deletions

File tree

.vscode/launch.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
"request": "launch",
1212
"runtimeExecutable": "${execPath}",
1313
"args": [
14-
"--extensionDevelopmentPath=${workspaceFolder}",
15-
"--disable-extensions"
14+
"--extensionDevelopmentPath=${workspaceFolder}"
15+
// "--disable-extensions"
1616
],
1717
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
1818
"preLaunchTask": "npm: watch",

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"undici",
7373
"vitepress",
7474
"vsix",
75+
"Xclip",
7576
"Zhipu"
7677
]
7778
}

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
},
154154
"aide.codeViewerHelperPrompt": {
155155
"type": "string",
156+
"editPresentation": "multilineText",
156157
"default": "You are a programming language commentator.\nYou need to help me add comments to #{sourceLanguage} code as much as possible to make it readable for beginners.\nDo not change the original code, just add as detailed comments as possible,\nbecause my purpose is only to understand and read. Please use my native language #{locale} as the commenting language.\nPlease do not reply with any text other than the code, and do not use markdown syntax.\nHere is the code you need to comment on:\n\n#{content}",
157158
"scope": "resource",
158159
"markdownDescription": "%config.codeViewerHelperPrompt.description%"
@@ -168,8 +169,14 @@
168169
"default": true,
169170
"markdownDescription": "%config.autoRememberConvertLanguagePairs.description%"
170171
},
172+
"aide.readClipboardImage": {
173+
"type": "boolean",
174+
"default": false,
175+
"markdownDescription": "%config.readClipboardImage.description%"
176+
},
171177
"aide.aiPrompt": {
172178
"type": "string",
179+
"editPresentation": "multilineText",
173180
"default": "#{content}",
174181
"scope": "resource",
175182
"markdownDescription": "%config.aiPrompt.description%"

package.nls.en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"config.codeViewerHelperPrompt.description": "Code viewer helper prompt template, [click to view online documentation](https://aide.nicepkg.cn/guide/configuration/code-viewer-helper-prompt)",
1313
"config.convertLanguagePairs.description": "Default convert language pairs, [click to view online documentation](https://aide.nicepkg.cn/guide/configuration/convert-language-pairs)",
1414
"config.autoRememberConvertLanguagePairs.description": "Automatically remember convert language pairs, [click to view online documentation](https://aide.nicepkg.cn/guide/configuration/auto-remember-convert-language-pairs)",
15+
"config.readClipboardImage.description": "Allow reading clipboard images as AI context in certain scenarios, [click to view online documentation](https://aide.nicepkg.cn/guide/configuration/read-clipboard-image)",
1516
"config.aiPrompt.description": "Template for copied content, use #{content} as a placeholder for file content, [click to view online documentation](https://aide.nicepkg.cn/guide/configuration/ai-prompt)",
1617
"config.ignorePatterns.description": "Ignored file name patterns, supports glob syntax, [click to view online documentation](https://aide.nicepkg.cn/guide/configuration/ignore-patterns)",
1718
"config.respectGitIgnore.description": "Respect .gitignore file, [click to view online documentation](https://aide.nicepkg.cn/guide/configuration/respect-git-ignore)",
@@ -33,6 +34,7 @@
3334
"error.noTargetLanguage": "No target language selected",
3435
"error.noContext": "Context not initialized",
3536
"error.emptyClipboard": "Clipboard is empty",
37+
"error.xclipNotFound": "xclip is not installed. Please install it using your package manager (e.g., sudo apt-get install xclip)",
3638
"info.copied": "File contents have been copied to clipboard",
3739
"info.customLanguage": "Custom language",
3840
"info.noAiSuggestionsVariableName": "AI thinks your variable name is already good",

package.nls.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"config.codeViewerHelperPrompt.description": "Code viewer helper prompt template, [click to view online documentation](https://aide.nicepkg.cn/guide/configuration/code-viewer-helper-prompt)",
1313
"config.convertLanguagePairs.description": "Default convert language pairs, [click to view online documentation](https://aide.nicepkg.cn/guide/configuration/convert-language-pairs)",
1414
"config.autoRememberConvertLanguagePairs.description": "Automatically remember convert language pairs, [click to view online documentation](https://aide.nicepkg.cn/guide/configuration/auto-remember-convert-language-pairs)",
15+
"config.readClipboardImage.description": "Allow reading clipboard images as AI context in certain scenarios, [click to view online documentation](https://aide.nicepkg.cn/guide/configuration/read-clipboard-image)",
1516
"config.aiPrompt.description": "Template for copied content, use #{content} as a placeholder for file content, [click to view online documentation](https://aide.nicepkg.cn/guide/configuration/ai-prompt)",
1617
"config.ignorePatterns.description": "Ignored file name patterns, supports glob syntax, [click to view online documentation](https://aide.nicepkg.cn/guide/configuration/ignore-patterns)",
1718
"config.respectGitIgnore.description": "Respect .gitignore file, [click to view online documentation](https://aide.nicepkg.cn/guide/configuration/respect-git-ignore)",
@@ -33,6 +34,7 @@
3334
"error.noTargetLanguage": "No target language selected",
3435
"error.noContext": "Context not initialized",
3536
"error.emptyClipboard": "Clipboard is empty",
37+
"error.xclipNotFound": "xclip is not installed. Please install it using your package manager (e.g., sudo apt-get install xclip)",
3638
"info.copied": "File contents have been copied to clipboard",
3739
"info.customLanguage": "Custom language",
3840
"info.noAiSuggestionsVariableName": "AI thinks your variable name is already good",

package.nls.zh-cn.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"config.codeViewerHelperPrompt.description": "代码查看器助手 prompt 模板, [点击查看在线文档](https://aide.nicepkg.cn/zh/guide/configuration/code-viewer-helper-prompt)",
1313
"config.convertLanguagePairs.description": "默认转换语言对照表, [点击查看在线文档](https://aide.nicepkg.cn/zh/guide/configuration/convert-language-pairs)",
1414
"config.autoRememberConvertLanguagePairs.description": "是否自动记住转换语言对照表, [点击查看在线文档](https://aide.nicepkg.cn/zh/guide/configuration/auto-remember-convert-language-pairs)",
15+
"config.readClipboardImage.description": "是否允许某些场景读取剪贴板图片作为 AI 上下文, [点击查看在线文档](https://aide.nicepkg.cn/zh/guide/configuration/read-clipboard-image)",
1516
"config.aiPrompt.description": "复制内容的模板,使用#{content}作为文件内容的占位符, [点击查看在线文档](https://aide.nicepkg.cn/zh/guide/configuration/ai-prompt)",
1617
"config.ignorePatterns.description": "忽略的文件名模式, 支持 glob 语法, [点击查看在线文档](https://aide.nicepkg.cn/zh/guide/configuration/ignore-patterns)",
1718
"config.respectGitIgnore.description": "是否尊重 .gitignore 文件, [点击查看在线文档](https://aide.nicepkg.cn/zh/guide/configuration/respect-git-ignore)",
@@ -33,6 +34,7 @@
3334
"error.noTargetLanguage": "未选择目标语言",
3435
"error.noContext": "上下文未初始化",
3536
"error.emptyClipboard": "剪贴板为空",
37+
"error.xclipNotFound": "xclip 未安装。请使用你的包管理器安装它 (例如,sudo apt-get install xclip)",
3638
"info.copied": "文件内容已复制到剪贴板",
3739
"info.customLanguage": "自定义语言",
3840
"info.noAiSuggestionsVariableName": " AI 觉得你这个变量名字已经很好了",

src/clipboard.ts

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { spawn } from 'child_process'
2+
import crypto from 'crypto'
3+
import { promises as fs } from 'fs'
4+
import { tmpdir } from 'os'
5+
import * as path from 'path'
6+
import * as vscode from 'vscode'
7+
8+
import { t } from './i18n'
9+
import { logger } from './logger'
10+
11+
const getClipboardImageAsBase64Url = async (): Promise<string | null> => {
12+
const osPlatform = process.platform
13+
const tempDir = tmpdir()
14+
const randomString = crypto.randomBytes(8).toString('hex')
15+
let command: string
16+
let args: string[]
17+
let fileExtension: string
18+
let mimeType: string
19+
let filePath: string
20+
21+
switch (osPlatform) {
22+
case 'win32':
23+
fileExtension = '.bmp'
24+
mimeType = 'image/bmp'
25+
filePath = path.win32.join(
26+
tempDir,
27+
`clipboard_image_${randomString}${fileExtension}`
28+
)
29+
command = 'powershell'
30+
args = [
31+
'-command',
32+
`Add-Type -AssemblyName System.Windows.Forms;
33+
Add-Type -AssemblyName System.Drawing;
34+
$img = [System.Windows.Forms.Clipboard]::GetImage();
35+
if ($img -ne $null) {
36+
$img.Save('${filePath}', [System.Drawing.Imaging.ImageFormat]::Bmp);
37+
$img.Dispose();
38+
} else {
39+
Write-Error "No image found in clipboard"
40+
}`
41+
]
42+
break
43+
case 'darwin':
44+
fileExtension = '.png'
45+
mimeType = 'image/png'
46+
filePath = path.join(
47+
tempDir,
48+
`clipboard_image_${randomString}${fileExtension}`
49+
)
50+
command = 'osascript'
51+
args = [
52+
'-e',
53+
`set imgFile to (POSIX file "${filePath}")
54+
try
55+
set imgData to the clipboard as «class PNGf»
56+
set fileRef to open for access imgFile with write permission
57+
write imgData to fileRef
58+
close access fileRef
59+
on error
60+
try
61+
close access imgFile
62+
end try
63+
error "No image found in clipboard"
64+
end try`
65+
]
66+
break
67+
case 'linux':
68+
fileExtension = '.png'
69+
mimeType = 'image/png'
70+
filePath = path.join(
71+
tempDir,
72+
`clipboard_image_${randomString}${fileExtension}`
73+
)
74+
await checkXclipInstalled()
75+
command = 'sh'
76+
args = [
77+
'-c',
78+
`xclip -selection clipboard -t image/png -o > "${filePath}"`
79+
]
80+
break
81+
default:
82+
logger.warn(`Unsupported platform: ${osPlatform}`)
83+
return null
84+
}
85+
86+
try {
87+
await executeCommand(command, args)
88+
const base64 = await readFileAsBase64(filePath)
89+
return `data:${mimeType};base64,${base64}`
90+
} catch (error) {
91+
logger.warn(
92+
'getClipboardImageAsBase64Url Failed to get clipboard image',
93+
error
94+
)
95+
return null
96+
} finally {
97+
await safeDeleteFile(filePath)
98+
}
99+
}
100+
101+
const checkXclipInstalled = async (): Promise<void> => {
102+
try {
103+
await executeCommand('which', ['xclip'])
104+
} catch (error) {
105+
throw new Error(t('error.xclipNotFound'))
106+
}
107+
}
108+
109+
const executeCommand = (command: string, args: string[]): Promise<void> =>
110+
new Promise((resolve, reject) => {
111+
const childProcess = spawn(command, args)
112+
let stderr = ''
113+
114+
childProcess.stderr.on('data', data => {
115+
stderr += data.toString()
116+
})
117+
118+
childProcess.on('close', code => {
119+
if (code === 0) {
120+
resolve()
121+
} else {
122+
reject(new Error(`Command failed with code ${code}. Error: ${stderr}`))
123+
}
124+
})
125+
})
126+
127+
const readFileAsBase64 = async (filePath: string): Promise<string | null> => {
128+
try {
129+
const data = await fs.readFile(filePath)
130+
return data.toString('base64')
131+
} catch (error) {
132+
logger.warn('readFileAsBase64 Failed to read file as base64', error)
133+
return null
134+
}
135+
}
136+
137+
const safeDeleteFile = async (filePath: string): Promise<void> => {
138+
try {
139+
await fs.unlink(filePath)
140+
} catch (error) {
141+
// File doesn't exist or can't be accessed, no need to do anything
142+
}
143+
}
144+
145+
type SafeReadClipboardOptions = {
146+
readImg?: boolean
147+
}
148+
149+
type ClipboardResult = {
150+
text: string
151+
img?: string
152+
}
153+
154+
export const safeReadClipboard = async (
155+
options: SafeReadClipboardOptions = {}
156+
): Promise<ClipboardResult> => {
157+
const clipboardResult: ClipboardResult = { text: '' }
158+
159+
if (options.readImg) {
160+
const img = await getClipboardImageAsBase64Url()
161+
if (img) {
162+
clipboardResult.img = img
163+
}
164+
}
165+
166+
try {
167+
const text = await vscode.env.clipboard.readText()
168+
if (text) {
169+
clipboardResult.text = text
170+
}
171+
} catch (error) {
172+
logger.warn('safeReadClipboard Failed to read clipboard text', error)
173+
}
174+
175+
return clipboardResult
176+
}

0 commit comments

Comments
 (0)