optimize start up for devchat-view

This commit is contained in:
bobo.yang 2023-11-30 13:25:18 +08:00
parent d81c962960
commit b1af136111
32 changed files with 36 additions and 672 deletions

3
.gitignore vendored
View File

@ -11,6 +11,3 @@ node_modules
.chat/prompts.db
.vscode/settings.json
workflows/

View File

@ -1,22 +0,0 @@
import * as path from 'path';
import { createTempSubdirectory, runCommandStringAndWriteOutput } from '../util/commonUtil';
import { logger } from '../util/logger';
export async function handleRefCommand(ref_command: string) {
if (ref_command) {
const tempDir = await createTempSubdirectory('devchat/context');
const diff_file = path.join(tempDir, 'custom.txt');
logger.channel()?.info(`custom command: ${ref_command}`);
const result = await runCommandStringAndWriteOutput(ref_command, diff_file);
logger.channel()?.info(` exit code:`, result.exitCode);
logger.channel()?.debug(` stdout:`, result.stdout);
logger.channel()?.debug(` stderr:`, result.stderr);
return `[context|${diff_file}]`;
}
return '';
}

View File

@ -73,14 +73,14 @@ export function checkDevChatDependency(showError: boolean = true): boolean {
return false;
}
logger.channel()?.info("devchat has installed.")
logger.channel()?.info("devchat has installed.");
return true;
} catch(error) {
const error_status = `Failed to check DevChat dependency due to error: ${error}`;
if (devchatStatus !== error_status && showError) {
logger.channel()?.warn(error_status);
const errorStatus = `Failed to check DevChat dependency due to error: ${error}`;
if (devchatStatus !== errorStatus && showError) {
logger.channel()?.warn(errorStatus);
logger.channel()?.show();
devchatStatus = error_status;
devchatStatus = errorStatus;
}
return false;

View File

@ -60,13 +60,13 @@ async function isProviderHasSetted() {
}
async function configUpdateTo_1115() {
const support_models = [
async function configUpdateTo1115() {
const supportModels = [
"Model.gpt-3-5-1106",
"Model.gpt-4-turbo",
];
for (const model of support_models) {
for (const model of supportModels) {
const modelConfig1: any = UiUtilWrapper.getConfiguration("devchat", model);
if (Object.keys(modelConfig1).length === 0) {
let modelConfigNew = {};
@ -80,7 +80,7 @@ async function configUpdateTo_1115() {
}
}
async function configUpdateTo_0924() {
async function configUpdateTo0924() {
if (await isProviderHasSetted()) {
return ;
}
@ -122,7 +122,7 @@ async function configUpdateTo_0924() {
await vscode.workspace.getConfiguration("devchat").update("Provider.devchat", providerConfigNew, vscode.ConfigurationTarget.Global);
}
const support_models = [
const supportModels = [
"Model.gpt-3-5",
"Model.gpt-3-5-1106",
"Model.gpt-3-5-16k",
@ -136,7 +136,7 @@ async function configUpdateTo_0924() {
"Model.llama-2-70b-chat"
];
for (const model of support_models) {
for (const model of supportModels) {
const modelConfig1: any = UiUtilWrapper.getConfiguration("devchat", model);
if (Object.keys(modelConfig1).length === 0) {
modelConfigNew = {"provider": "devchat"};
@ -154,19 +154,19 @@ async function configUpdateTo_0924() {
}
async function configUpdate0912To_0924() {
async function configUpdate0912To0924() {
if (await isProviderHasSetted()) {
return ;
}
const old_models = [
const oldModels = [
"Model.gpt-3-5",
"Model.gpt-3-5-16k",
"Model.gpt-4",
"Model.claude-2"
];
for (const model of old_models) {
for (const model of oldModels) {
const modelConfig: any = UiUtilWrapper.getConfiguration("devchat", model);
if (Object.keys(modelConfig).length !== 0) {
let modelProperties: any = {};
@ -176,7 +176,7 @@ async function configUpdate0912To_0924() {
}
if (modelConfig["api_key"]) {
let providerConfigNew = {}
let providerConfigNew = {};
providerConfigNew["access_key"] = modelConfig["api_key"];
if (modelConfig["api_base"]) {
providerConfigNew["api_base"] = modelConfig["api_base"];
@ -211,9 +211,9 @@ async function activate(context: vscode.ExtensionContext) {
logger.init(LoggerChannelVscode.getInstance());
UiUtilWrapper.init(new UiUtilVscode());
await configUpdateTo_0924();
await configUpdate0912To_0924();
await configUpdateTo_1115();
await configUpdateTo0924();
await configUpdate0912To0924();
await configUpdateTo1115();
regLanguageContext();

View File

@ -16,6 +16,7 @@ export async function featureToggle(message: any, panel: vscode.WebviewPanel|vsc
}
regInMessage({command: 'featureToggles'});
// eslint-disable-next-line @typescript-eslint/naming-convention
regOutMessage({command: 'featureToggles', features: {'feature name': true}});
export async function getFeatureToggles(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
const featureTaggles = FTs();

View File

@ -1,9 +1,7 @@
import * as vscode from 'vscode';
import { ChatContextManager } from '../context/contextManager';
import { MessageHandler } from './messageHandler';
import { regInMessage, regOutMessage } from '../util/reg_messages';
import { ApiKeyManager } from '../util/apiKey';
import { UiUtilWrapper } from '../util/uiUtil';
regInMessage({command: 'regModelList'});

View File

@ -6,31 +6,7 @@ import { UiUtilWrapper } from '../util/uiUtil';
const functionRegistry: any = {
"/hellox": {
"keys": [],
"handler": async () => {
return "111222";
}
},
"/hellox2": {
"keys": ["a", "b"],
"handler": async (a: string, b: string) => {
return a+b;
}
},
"/hellox3": {
"keys": [],
"handler": async () => {
return {
"name": "v1",
"age": 20,
"others": {
"address": "sh",
"phone": "123456789"
}
};
}
},
// eslint-disable-next-line @typescript-eslint/naming-convention
"/get_lsp_brige_port": {
"keys": [],
"handler": async () => {
@ -106,6 +82,7 @@ export async function startRpcServer() {
responseResult['error'] = "Function not found";
}
// eslint-disable-next-line @typescript-eslint/naming-convention
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(responseResult));
}

View File

@ -41,16 +41,19 @@ export function createChatDirectoryAndCopyInstructionsSync(extensionUri: vscode.
const chatWorkflowsDirPath = path.join(workspaceRoot, '.chat', 'workflows');
const instructionsSrcPath = path.join(extensionUri.fsPath, 'workflows');
// if workflows directory exists, return
if (fs.existsSync(chatWorkflowsDirPath)) {
return ;
}
try {
// 检查 .chat 目录是否存在,如果不存在,则创建它
if (!fs.existsSync(chatWorkflowsDirPath)) {
fs.mkdirSync(chatWorkflowsDirPath, {recursive: true});
} else {
// return;
}
// 将 workflows 目录复制到 .chat 目录中
copyDirSync(instructionsSrcPath, chatWorkflowsDirPath);
} catch (error) {
logger.channel()?.error('Error occurred while creating the .chat directory and copying workflows:', error);

View File

@ -1,87 +0,0 @@
// chatPanel.ts
import * as vscode from 'vscode';
import * as path from 'path';
import '../handler/handlerRegister';
import handleMessage from '../handler/messageHandler';
import WebviewManager from './webviewManager';
import { createChatDirectoryAndCopyInstructionsSync } from '../init/chatConfig';
import { UiUtilWrapper } from '../util/uiUtil';
export default class ChatPanel {
private static _instance: ChatPanel | undefined;
private readonly _panel: vscode.WebviewPanel;
private _webviewManager: WebviewManager;
private _disposables: vscode.Disposable[] = [];
public static createOrShow(extensionUri: vscode.Uri) {
// 创建 .chat 目录并复制 workflows
createChatDirectoryAndCopyInstructionsSync(extensionUri);
const workspaceDir = UiUtilWrapper.workspaceFoldersFirstPath();
if (workspaceDir) {
const workflowsDir = path.join(workspaceDir!, '.chat', 'workflows');
}
if (ChatPanel._instance) {
ChatPanel._instance._panel.reveal();
} else {
const panel = ChatPanel.createWebviewPanel(extensionUri);
ChatPanel._instance = new ChatPanel(panel, extensionUri);
}
}
public static currentPanel(): ChatPanel | undefined {
return ChatPanel._instance;
}
// Create a new webview panel
private static createWebviewPanel(extensionUri: vscode.Uri): vscode.WebviewPanel {
return vscode.window.createWebviewPanel(
'chatPanel',
'Chat',
vscode.ViewColumn.Beside,
{
enableScripts: true,
localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'dist')],
retainContextWhenHidden: true
}
);
}
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
this._panel = panel;
this._webviewManager = new WebviewManager(panel.webview, extensionUri);
this.registerEventListeners();
}
public panel(): vscode.WebviewPanel {
return this._panel;
}
// Register event listeners for the panel and webview
private registerEventListeners() {
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
this._panel.webview.onDidReceiveMessage(
async (message) => {
handleMessage(message, this._panel);
},
null,
this._disposables
);
}
// Dispose the panel and clean up resources
public dispose() {
ChatPanel._instance = undefined;
this._panel.dispose();
while (this._disposables.length) {
const disposable = this._disposables.pop();
if (disposable) {
disposable.dispose();
}
}
}
}

View File

@ -81,16 +81,4 @@ export class DevChatViewProvider implements vscode.WebviewViewProvider {
// this.updateWebviewContent();
}
}
// public dispose() {
// ChatPanel._instance = undefined;
// this._panel.dispose();
// while (this._disposables.length) {
// const disposable = this._disposables.pop();
// if (disposable) {
// disposable.dispose();
// }
// }
// }
}

View File

@ -53,9 +53,8 @@ export function createStatusBarItem(context: vscode.ExtensionContext): vscode.St
progressBar.update(`Checking dependencies: Success`, 0);
progressBar.end();
// execute command: DevChat.InstallCommands
// download workflows from github or gitlab
await vscode.commands.executeCommand('DevChat.InstallCommands');
ExtensionContextHolder.provider?.reloadWebview();
vscode.commands.executeCommand('DevChat.InstallCommandPython');
clearInterval(timer);
} catch (error) {

View File

@ -4,9 +4,13 @@ import { logger } from "./logger";
type Action = {
action: "delete" | "insert" | "modify";
content?: string;
// eslint-disable-next-line @typescript-eslint/naming-convention
insert_after?: string;
// eslint-disable-next-line @typescript-eslint/naming-convention
insert_before?: string;
// eslint-disable-next-line @typescript-eslint/naming-convention
original_content?: string;
// eslint-disable-next-line @typescript-eslint/naming-convention
new_content?: string;
};

View File

@ -11,6 +11,7 @@ const featureTogglesJson = `
const featureToggles = JSON.parse(featureTogglesJson);
// eslint-disable-next-line @typescript-eslint/naming-convention
export function FT(feature: string): boolean {
const betaInvitationCode = vscode.workspace.getConfiguration('DevChat').get<string>('betaInvitationCode');
const expectedInvitationCode = 'WELCOMEADDTODEVCHAT';
@ -18,6 +19,7 @@ export function FT(feature: string): boolean {
return betaInvitationCode === expectedInvitationCode || featureToggles[feature] === true;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
export function FTs(): any {
// visited features
let newFeatureToggles = {};

View File

@ -7,6 +7,7 @@ export interface LogChannel {
show(): void;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
export class logger {
private static _channel: LogChannel | undefined;
public static init(channel: LogChannel): void {

View File

@ -40,7 +40,7 @@ export async function createEnvByMamba(pkgName: string, pkgVersion: string, pyth
export async function createEnvByConda(pkgName: string, pkgVersion: string, pythonVersion: string) : Promise<string> {
// install conda
logger.channel()?.info('Install conda ...')
logger.channel()?.info('Install conda ...');
const condaCommand = await installConda();
if (!condaCommand) {
logger.channel()?.error('Install conda failed');
@ -86,7 +86,7 @@ export async function appInstall(pkgName: string, pkgVersion: string, pythonVers
logger.channel()?.info(`Create env success: ${pythonCommand}`);
// install devchat in the env
logger.channel()?.info('Install python packages ...')
logger.channel()?.info('Install python packages ...');
let isInstalled = false;
// try 3 times
for (let i = 0; i < 4; i++) {

View File

@ -1,13 +0,0 @@
{
"name": "ask_codebase",
"description": "Ask question about codebase.",
"type": ["question"],
"args": [{
"name": "question",
"description": "selected question",
"type": "string",
"from": "content.content.question"
}],
"action": "ask_codebase",
"handler": ["${PythonVirtualEnv}", "${CurDir}/handler.py", "${question}"]
}

View File

@ -1,76 +0,0 @@
import os
import re
import sys
import json
import tempfile
import uuid
from chat.ask_codebase.store.qdrant import QdrantWrapper as Q, get_client
from chat.ask_codebase.indexing.embedding import EmbeddingWrapper as E
from langchain.embeddings import HuggingFaceEmbeddings
from chat.ask_codebase.indexing.loader.file import (
FileLoader,
FileSource,
gen_local_reference_maker,
)
from chat.util.misc import is_source_code
from chat.ask_codebase.chains.simple_qa import SimpleQA
from chat.ask_codebase.chains.stuff_dc_qa import StuffDocumentCodeQa
def get_app_data_dir(app_name):
home = os.path.expanduser("~")
if os.name == "nt": # For Windows
appPath = os.path.join(home, "AppData", "Roaming", app_name)
else: # For Unix and Linux
appPath = os.path.join(home, ".local", "share", app_name)
if not os.path.exists(appPath):
os.makedirs(appPath)
return appPath
supportedFileTypes = []
STORAGE_FILE = os.path.join(get_app_data_dir("devchat"), "qdrant_storage2")
SOURCE_NAME = ""
def query(question: str):
try:
client = get_client(mode=STORAGE_FILE)
q = Q.reuse(
source_name=SOURCE_NAME,
embedding_cls=HuggingFaceEmbeddings,
client=client,
)
chain = StuffDocumentCodeQa(q)
_, docs = chain.run(question)
for d in docs:
print(d.metadata.get('filepath'))
print(d.page_content)
sys.exit(0)
except Exception as e:
print(e)
sys.exit(1)
if __name__ == "__main__":
try:
if os.path.exists(".chat/askcode.json"):
with open(".chat/askcode.json", "r") as f:
askcode_data = json.load(f)
SOURCE_NAME = askcode_data.get("SOURCE_NAME", str(uuid.uuid4()))
else:
SOURCE_NAME = str(uuid.uuid4())
with open(".chat/askcode.json", "w+") as f:
json.dump({"SOURCE_NAME": SOURCE_NAME}, f)
query(sys.argv[1])
sys.exit(0)
except Exception as e:
print(e)
sys.exit(1)

View File

@ -1,14 +0,0 @@
{
"name": "finish_task",
"description": "Flag task as finished",
"type": [
"task"
],
"args": [
],
"action": "finish_task",
"handler": [
"echo",
"Task finished"
]
}

View File

@ -1,9 +0,0 @@
{
"name": "get_project_tree",
"description": "Get project file list.",
"type": ["project"],
"action": "get_project_tree",
"args": [
],
"handler": ["python", "${CurDir}/handler.py"]
}

View File

@ -1,25 +0,0 @@
"""
get project files by "git ls-files" command
如果项目文件数超出100那么结束执行退出码为1并且输出英文文件数太多需要通过ls命令逐层查看各个目录中的文件目录结构信息
"""
import subprocess
import sys
def get_project_tree():
"""
Get project files by "git ls-files" command
"""
try:
output = subprocess.check_output(["git", "ls-files"]).decode("utf-8").split("\n")
if len(output) > 100:
sys.stderr.write("Error: Too many files, you need to view the files and directory structure in each directory through the 'ls' command.\n")
sys.exit(1)
return output
except Exception as e:
sys.stderr.write(f"Error: {str(e)}\n")
sys.exit(1)
if __name__ == "__main__":
print(get_project_tree())
sys.exit(0)

View File

@ -1,27 +0,0 @@
{
"name": "load_file",
"description": "Load text file content.",
"type": ["file"],
"action": "load_file",
"args": [
{
"name": "fileName",
"description": "Specify the file, which content will be loaded",
"type": "string",
"from": "content.content.fileName"
},
{
"name": "startLine",
"description": "Specify the start line of content. It is not required if load the whole file.",
"type": "string",
"from": "content.content.startLine"
},
{
"name": "endLine",
"description": "Specify the end line of content. It is not required if load the whole file.",
"type": "string",
"from": "content.content.endLine"
}
],
"handler": ["python", "${CurDir}/handler.py", "${fileName}", "${startLine}", "${endLine}"]
}

View File

@ -1,33 +0,0 @@
"""
Load content in file.
"""
import sys
def load_file_content(file_name, start_line=0, end_line=1000):
"""
Load content in file from start_line to end_line.
If start_line and end_line are not provided, load the whole file.
"""
try:
with open(file_name, 'r') as file:
lines = file.readlines()
if start_line < 0 or end_line <= start_line:
sys.stderr.write("Error: start line must be greater than or equal to 0 and end line must be greater than start line.\n")
sys.exit(1)
content = "".join(lines[int(start_line):int(end_line)])
if len(content.split('\n')) > 500:
sys.stderr.write("Error: The content is too large, please set a reasonable reading range.\n")
sys.exit(1)
return content
except Exception as e:
sys.stderr.write(f"Error: {str(e)}\n")
sys.exit(1)
if __name__ == "__main__":
file_name = sys.argv[1]
start_line = int(sys.argv[2]) if sys.argv[2] != '${startLine}' else 0
end_line = int(sys.argv[3]) if sys.argv[3] != '${endLine}' else 1000
print(load_file_content(file_name, start_line, end_line))
sys.exit(0)

View File

@ -1,24 +0,0 @@
{
"name": "new_file",
"description": "create new file or replace file with new content",
"type": [
"*"
],
"args": [
{
"name": "fileName",
"description": "target file name to create",
"type": "string",
"from": "content.fileName"
},
{
"name": "content",
"description": "content to write to file",
"type": "string",
"as": "file",
"from": "content.content"
}
],
"action": "new_file",
"handler": ["python", "${CurDir}/handler.py", "${content}", "${fileName}"]
}

View File

@ -1,32 +0,0 @@
"""
Create new file.
arg:
content: current file name with content
fileName: new file name.
Rename content to fileName.
"""
import os
import sys
import shutil
def create_new_file(content_file, new_file):
"""
Create new file or replace file with new content.
"""
try:
if os.path.exists(new_file):
os.remove(new_file)
# Create the parent directories for the new file if they don't exist.
os.makedirs(os.path.dirname(new_file), exist_ok=True)
shutil.move(content_file, new_file)
except Exception as e:
sys.stderr.write(f"Error: {str(e)}\n")
sys.exit(1)
if __name__ == "__main__":
content_file = sys.argv[1]
new_file = sys.argv[2]
create_new_file(content_file, new_file)
sys.exit(0)

View File

@ -1,19 +0,0 @@
{
"name": "run_shell",
"description": "run shell script",
"type": [
"shell-singleline"
],
"args": [
{
"name": "script",
"description": "shell command to run, for example: [\"ls\", \"/tmp\"]",
"type": "string",
"from": "content.content"
}
],
"action": "run_shell",
"handler": [
"${script}"
]
}

View File

@ -1,21 +0,0 @@
{
"name": "run_shell_file",
"description": "run shell script file",
"type": [
"shell"
],
"args": [
{
"name": "script_file",
"description": "shell script file to run",
"type": "string",
"as": "file",
"from": "content.content"
}
],
"action": "run_shell",
"handler": [
"bash",
"${script_file}"
]
}

View File

@ -1,23 +0,0 @@
{
"name": "search_text",
"description": "Search text in all files within project. Search text with match whole word. Text search is an alternative to finding symbol references, mainly for scenarios where symbol references cannot be used, such as searching for references to a specific string.",
"type": ["none"],
"args": [{
"name": "text",
"description": "text to search",
"type": "string",
"from": "content.content.text"
}, {
"name": "include_files",
"description": "search text within setting include files, support multi pattern. The parameter format is based on the corresponding configuration in vscode search.",
"type": "string",
"from": "content.content.include_files"
}, {
"name": "exclude_files",
"description": "search text not within setting exclude files, support multi pattern. The parameter format is based on the corresponding configuration in vscode search.",
"type": "string",
"from": "content.content.exclude_files"
}],
"action": "search_text",
"handler": ["python", "${CurDir}/handler.py", "${text}", "${include_files}", "${exclude_files}"]
}

View File

@ -1,66 +0,0 @@
"""
Search text in all files within project. Search text with match whole word mode.
"""
import sys
import os
import fnmatch
import re
def search_text(text, include_files=None, exclude_files=None):
"""
Search text in all files within project. Search text with match whole word mode.
"""
# Get the current working directory
cwd = os.getcwd()
# Prepare the include and exclude patterns
include_patterns = include_files.split(',') if include_files else ['*']
exclude_patterns = exclude_files.split(',') if exclude_files else []
# Initialize a flag to check if the text is found
found = 0
# Walk through the directory
search_result = []
for dirpath, dirnames, filenames in os.walk(cwd):
for filename in filenames:
filename = os.path.join(dirpath, filename)
relFileName = os.path.relpath(filename, cwd)
# Check if the file matches any of the include patterns
if any(fnmatch.fnmatch(relFileName, pattern) for pattern in include_patterns):
# Check if the file matches any of the exclude patterns
if not any(fnmatch.fnmatch(relFileName, pattern) for pattern in exclude_patterns):
# Open the file and search for the text
try:
with open(filename, 'r') as file:
for line_no, line in enumerate(file, 1):
if re.search(r'\b' + re.escape(text) + r'\b', line):
search_result.append(f'Found "{text}" in file {relFileName} on line {line_no-1}: {line.strip()}')
found += len(line.strip())
if (len(search_result) > 100 or found > 5000):
sys.stderr.write("The search text is too long, try to shorten it.\n")
sys.exit(1)
except Exception:
pass
# Check if the text was found
if found == 0:
sys.stderr.write("Optimize the search text content, make sure the search text exists in the file.\n")
sys.exit(1)
# Print the result
print('\n'.join(search_result))
def main():
# Get the arguments from the command line
text = sys.argv[1]
include_files = sys.argv[2] if sys.argv[2] != '${include_files}' else None
exclude_files = sys.argv[3] if sys.argv[3] != '${exclude_files}' else None
# Call the search_text function
search_text(text, include_files, exclude_files)
sys.exit(0)
if __name__ == "__main__":
main()

View File

@ -1,15 +0,0 @@
{
"name": "summary",
"description": "Get summary of specified file or direcotry in workspace",
"type": ["project"],
"action": "summary",
"args": [
{
"name": "path",
"description": "The relative path of the specified file or folder, for example, /hello.py, represents the hello.py file in the root directory. This arg is required.",
"type": "string",
"from": "content.content.path"
}
],
"handler": ["${PythonVirtualEnv}", "${CurDir}/handler.py", "${path}"]
}

View File

@ -1,37 +0,0 @@
"""
"""
import os
import sys
from chat.ask_codebase.indexing.loader.file import FileMetadata, FileSource, simple_file_filter
from chat.ask_codebase.indexing.module_summary import SummaryWrapper
def desc(repo_dir: str, repo_cache_path: str, target_path: str):
"""
"""
target_path = target_path.replace(repo_dir, '')
sw = SummaryWrapper(repo_cache_path, FileSource(
path=repo_dir,
rel_root=repo_dir,
file_filter=simple_file_filter,
))
return sw.get_desc(target_path)
def summary():
"""
Get file or directory 's summary
"""
try:
repo_dir = os.getcwd()
repo_cache_path = os.path.join(repo_dir, '.chat', '.summary.json')
target_path = sys.argv[1]
return desc(repo_dir, repo_cache_path, target_path)
except Exception as e:
sys.stderr.write(f"Error: {str(e)}\n")
sys.exit(1)
if __name__ == "__main__":
print(summary())
sys.exit(0)

View File

@ -1,23 +0,0 @@
{
"name": "update_file",
"description": "replace selections of file with specified content",
"type": ["update"],
"args": [{
"name": "fileName",
"description": "target file name to update",
"type": "string",
"from": "content.fileName"
}, {
"name": "old_content",
"description": "old content to replaced",
"type": "string",
"from": "content.content.old"
}, {
"name": "content",
"description": "content to write to file",
"type": "string",
"from": "content.content.new"
}],
"action": "update_file",
"handler": ["python", "${CurDir}/handler.py", "${fileName}", "${old_content}", "${content}"]
}

View File

@ -1,40 +0,0 @@
import sys
def replace_text_in_file(fileName: str, old_content: str, newContent: str) -> None:
"""
Replace text in a file within the specified range with new content.
:param fileName: The name of the file to modify.
:param startPos: The starting position of the range to replace.
:param endPos: The ending position of the range to replace.
:param newContent: The new content to replace the specified range with.
"""
with open(fileName, 'r') as file:
content = file.read()
# how many times old_content occurs in content
count = content.count(old_content)
# if count is not 1, then we can't replace the text
if count != 1:
# output error message to stderr and exit
print(f"Error: {old_content} occurs {count} times in {fileName}.", file=sys.stderr)
exit(-1)
# replace old_content with new_content
modified_content = content.replace(old_content, new_content)
with open(fileName, 'w') as file:
file.write(modified_content)
if __name__ == "__main__":
try:
file_name = sys.argv[1]
old_content = sys.argv[2]
new_content = sys.argv[3]
replace_text_in_file(file_name, old_content, new_content)
except Exception as e:
print(e, file=sys.stderr)
exit(-1)
exit(0)