opencode-fastedit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +130 -0
- package/package.json +31 -0
- package/tsconfig.json +13 -0
package/index.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { type Plugin, tool } from "@opencode-ai/plugin";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { createTwoFilesPatch } from "diff";
|
|
5
|
+
|
|
6
|
+
function normalizeLineEndings(text: string): string {
|
|
7
|
+
return text.replaceAll("\r\n", "\n");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const FastEditPlugin: Plugin = async ({ directory, client }) => {
|
|
11
|
+
return {
|
|
12
|
+
tool: {
|
|
13
|
+
fastedit: tool({
|
|
14
|
+
description: `Replace a range of lines in a file with new code without needing to specify the old content.
|
|
15
|
+
|
|
16
|
+
This tool efficiently replaces or deletes lines by line number instead of requiring you to type out the exact text being replaced. This is useful when:
|
|
17
|
+
- You want to replace multiple lines at once
|
|
18
|
+
- The old code is long and tedious to type
|
|
19
|
+
- You know the line numbers you want to modify
|
|
20
|
+
|
|
21
|
+
BEHAVIOR:
|
|
22
|
+
- Deletes all lines from start_line to end_line (inclusive)
|
|
23
|
+
- Inserts new_code at the start_line position
|
|
24
|
+
- If new_code is an empty string, the lines are simply deleted (pure deletion)
|
|
25
|
+
- If new_code contains multiple lines, they are all inserted starting at start_line
|
|
26
|
+
|
|
27
|
+
LINE NUMBERING:
|
|
28
|
+
- Uses 1-indexed line numbers (line 1 is the first line of the file)
|
|
29
|
+
- This matches the line numbers shown by the "view" tool
|
|
30
|
+
|
|
31
|
+
EXAMPLES:
|
|
32
|
+
- To replace lines 5-10 with new code: start_line=5, end_line=10, new_code="..."
|
|
33
|
+
- To delete lines 5-10: start_line=5, end_line=10, new_code=""
|
|
34
|
+
- To insert new code at line 5 (pushing existing line 5 and beyond down): use end_line=4 to "replace" a zero-length range
|
|
35
|
+
|
|
36
|
+
RETURN VALUE:
|
|
37
|
+
- Returns a success message with the file path and line range that was modified`,
|
|
38
|
+
args: {
|
|
39
|
+
file_path: tool.schema.string().describe("The absolute path to the file to modify"),
|
|
40
|
+
start_line: tool.schema
|
|
41
|
+
.number()
|
|
42
|
+
.min(1)
|
|
43
|
+
.describe("The starting line number (1-indexed) of the range to replace. Line 1 is the first line of the file."),
|
|
44
|
+
end_line: tool.schema
|
|
45
|
+
.number()
|
|
46
|
+
.min(1)
|
|
47
|
+
.describe("The ending line number (inclusive) of the range to replace. Must be >= start_line."),
|
|
48
|
+
new_code: tool.schema
|
|
49
|
+
.string()
|
|
50
|
+
.describe("The new code to insert at the start_line position. Can be empty string to simply delete the line range."),
|
|
51
|
+
},
|
|
52
|
+
async execute(args, context) {
|
|
53
|
+
const { file_path, start_line, end_line, new_code } = args;
|
|
54
|
+
|
|
55
|
+
const resolvedPath = path.isAbsolute(file_path)
|
|
56
|
+
? file_path
|
|
57
|
+
: path.join(context.worktree, file_path)
|
|
58
|
+
|
|
59
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
60
|
+
throw new Error(`File does not exist: ${resolvedPath}`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const stat = fs.statSync(resolvedPath)
|
|
64
|
+
if (stat.isDirectory()) {
|
|
65
|
+
throw new Error(`Path is a directory, not a file: ${resolvedPath}`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (start_line > end_line) {
|
|
69
|
+
throw new Error(`start_line (${start_line}) must be <= end_line (${end_line})`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const contentOld = normalizeLineEndings(fs.readFileSync(resolvedPath, "utf-8"))
|
|
73
|
+
const lines = contentOld.split("\n")
|
|
74
|
+
|
|
75
|
+
if (start_line > lines.length) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`start_line (${start_line}) exceeds file length (${lines.length} lines). Maximum valid line number is ${lines.length}.`
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (end_line > lines.length) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`end_line (${end_line}) exceeds file length (${lines.length} lines). Maximum valid line number is ${lines.length}.`
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const beforeLines = lines.slice(0, start_line - 1)
|
|
88
|
+
const afterLines = lines.slice(end_line)
|
|
89
|
+
|
|
90
|
+
const contentNew = normalizeLineEndings([...beforeLines, new_code, ...afterLines].join("\n"))
|
|
91
|
+
|
|
92
|
+
const diff = createTwoFilesPatch(resolvedPath, resolvedPath, contentOld, contentNew)
|
|
93
|
+
|
|
94
|
+
fs.writeFileSync(resolvedPath, contentNew, "utf-8")
|
|
95
|
+
|
|
96
|
+
const deletedCount = end_line - start_line + 1
|
|
97
|
+
const insertedLines = new_code ? new_code.split("\n").length : 0
|
|
98
|
+
const added = insertedLines
|
|
99
|
+
const removed = deletedCount
|
|
100
|
+
|
|
101
|
+
return `FastEdit applied to ${resolvedPath}
|
|
102
|
+
|
|
103
|
+
+${added} -${removed} lines
|
|
104
|
+
|
|
105
|
+
\`\`\`diff
|
|
106
|
+
${diff}
|
|
107
|
+
\`\`\``
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
"tool.execute.after": async (input, output) => {
|
|
113
|
+
if (input.tool === "fastedit") {
|
|
114
|
+
const fileMatch = output.output.match(/FastEdit applied to (.+?)\n/);
|
|
115
|
+
const statsMatch = output.output.match(/\+(\d+) -(\d+) lines/);
|
|
116
|
+
|
|
117
|
+
if (fileMatch && statsMatch) {
|
|
118
|
+
output.title = `FastEdit: ${path.basename(fileMatch[1])} +${statsMatch[1]}/-${statsMatch[2]}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
output.metadata = {
|
|
122
|
+
...output.metadata,
|
|
123
|
+
provider: "fastedit",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export default FastEditPlugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-fastedit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenCode plugin for fast line-based editing by line numbers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "bun test",
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@opencode-ai/plugin": "latest",
|
|
13
|
+
"diff": "^8.0.2"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/diff": "^7.0.2",
|
|
17
|
+
"bun-types": "latest",
|
|
18
|
+
"typescript": "^5.0.0"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"opencode",
|
|
22
|
+
"plugin",
|
|
23
|
+
"fastedit"
|
|
24
|
+
],
|
|
25
|
+
"author": "mudaaaa",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/mudaaaa/opencode-fastedit.git"
|
|
29
|
+
},
|
|
30
|
+
"license": "MIT"
|
|
31
|
+
}
|
package/tsconfig.json
ADDED