From 9b07864eee698ad9dd25ca99331711d48f23ce1c Mon Sep 17 00:00:00 2001 From: d-mitrofanov-v <88384601+d-mitrofanov-v@users.noreply.github.com> Date: Sat, 7 Oct 2023 07:58:22 +0300 Subject: [PATCH] Add ability to use refactoring actions without saving the buffer (#992) * save-buf-before-code-action * save-buf-before-code-action * use-refactoring-without-saving-buffer * moved getTempFile func to utils --- src/features/refactor.ts | 44 ++++++++++++++----------------------- src/features/sortImports.ts | 18 ++------------- src/utils.ts | 19 +++++++++++++++- 3 files changed, 36 insertions(+), 45 deletions(-) diff --git a/src/features/refactor.ts b/src/features/refactor.ts index 6b7e7ca6..359d65ae 100644 --- a/src/features/refactor.ts +++ b/src/features/refactor.ts @@ -1,11 +1,12 @@ import { ChildProcess } from 'child_process'; import { Disposable, Document, OutputChannel, Position, Range, TextDocument, Uri, window, workspace } from 'coc.nvim'; import * as path from 'path'; +import fs from 'fs'; import { createDeferred, Deferred } from '../async'; import { PythonSettings } from '../configSettings'; import { PythonExecutionService } from '../processService'; import { IPythonSettings } from '../types'; -import { getTextEditsFromPatch, getWindowsLineEndingCount, splitLines } from '../utils'; +import { getTextEditsFromPatch, getWindowsLineEndingCount, splitLines, getTempFileWithDocumentContents } from '../utils'; class RefactorProxy implements Disposable { protected readonly isWindows = process.platform === 'win32'; @@ -192,18 +193,6 @@ interface RenameResponse { results: [{ diff: string }]; } -async function checkDocument(doc: Document): Promise { - if (!doc) return false; - - const modified = await doc.buffer.getOption('modified'); - if (modified != 0) { - window.showWarningMessage('Buffer not saved, please save the buffer first!'); - return false; - } - - return true; -} - function validateDocumentForRefactor(doc: Document): Promise { if (!doc.dirty) { return Promise.resolve(); @@ -218,8 +207,7 @@ function validateDocumentForRefactor(doc: Document): Promise { export async function extractVariable(root: string, document: TextDocument, range: Range, outputChannel: OutputChannel): Promise { const doc = workspace.getDocument(document.uri); - const valid = await checkDocument(doc); - if (!valid) return; + const tempFile = await getTempFileWithDocumentContents(document); const pythonToolsExecutionService = new PythonExecutionService(); const rope = await pythonToolsExecutionService.isModuleInstalled('rope'); @@ -235,18 +223,17 @@ export async function extractVariable(root: string, document: TextDocument, rang return validateDocumentForRefactor(doc).then(() => { const newName = `newvariable${new Date().getMilliseconds().toString()}`; const proxy = new RefactorProxy(root, pythonSettings, workspaceRoot); - const rename = proxy.extractVariable(doc.textDocument, newName, Uri.parse(doc.uri).fsPath, range).then((response) => { + const rename = proxy.extractVariable(doc.textDocument, newName, tempFile, range).then((response) => { return response.results[0].diff; }); - return extractName(doc, newName, rename, outputChannel); + return extractName(doc, newName, rename, outputChannel, tempFile); }); } export async function extractMethod(root: string, document: TextDocument, range: Range, outputChannel: OutputChannel): Promise { const doc = workspace.getDocument(document.uri); - const valid = await checkDocument(doc); - if (!valid) return; + const tempFile = await getTempFileWithDocumentContents(document); const pythonToolsExecutionService = new PythonExecutionService(); const rope = await pythonToolsExecutionService.isModuleInstalled('rope'); @@ -262,18 +249,17 @@ export async function extractMethod(root: string, document: TextDocument, range: return validateDocumentForRefactor(doc).then(() => { const newName = `newmethod${new Date().getMilliseconds().toString()}`; const proxy = new RefactorProxy(root, pythonSettings, workspaceRoot); - const rename = proxy.extractMethod(doc.textDocument, newName, Uri.parse(doc.uri).fsPath, range).then((response) => { + const rename = proxy.extractMethod(doc.textDocument, newName, tempFile, range).then((response) => { return response.results[0].diff; }); - return extractName(doc, newName, rename, outputChannel); + return extractName(doc, newName, rename, outputChannel, tempFile); }); } export async function addImport(root: string, document: TextDocument, name: string, parent: boolean, outputChannel: OutputChannel): Promise { const doc = workspace.getDocument(document.uri); - const valid = await checkDocument(doc); - if (!valid) return; + const tempFile = await getTempFileWithDocumentContents(document); const pythonToolsExecutionService = new PythonExecutionService(); const rope = await pythonToolsExecutionService.isModuleInstalled('rope'); @@ -290,21 +276,21 @@ export async function addImport(root: string, document: TextDocument, name: stri const pythonSettings = PythonSettings.getInstance(); return validateDocumentForRefactor(doc).then(() => { const proxy = new RefactorProxy(root, pythonSettings, workspaceRoot); - const resp = proxy.addImport(doc.textDocument, Uri.parse(doc.uri).fsPath, name, parentModule).then((response) => { + const resp = proxy.addImport(doc.textDocument, tempFile, name, parentModule).then((response) => { return response.results[0].diff; }); - - return applyImports(doc, resp, outputChannel); + return applyImports(doc, resp, outputChannel, tempFile); }); } -async function applyImports(doc: Document, resp: Promise, outputChannel: OutputChannel): Promise { +async function applyImports(doc: Document, resp: Promise, outputChannel: OutputChannel, tempFile: string): Promise { try { const diff = await resp; if (diff.length === 0) return; const edits = getTextEditsFromPatch(doc.getDocumentContent(), diff); await doc.applyEdits(edits); + await fs.promises.unlink(tempFile); } catch (error) { let errorMessage = `${error}`; if (typeof error === 'string') { @@ -321,7 +307,7 @@ async function applyImports(doc: Document, resp: Promise, outputChannel: } } -async function extractName(textEditor: Document, newName: string, renameResponse: Promise, outputChannel: OutputChannel): Promise { +async function extractName(textEditor: Document, newName: string, renameResponse: Promise, outputChannel: OutputChannel, tempFile: string): Promise { let changeStartsAtLine = -1; try { const diff = await renameResponse; @@ -335,6 +321,8 @@ async function extractName(textEditor: Document, newName: string, renameResponse } }); await textEditor.applyEdits(edits); + await fs.promises.unlink(tempFile); + if (changeStartsAtLine >= 0) { let newWordPosition: Position | undefined; for (let lineNumber = changeStartsAtLine; lineNumber < textEditor.lineCount; lineNumber += 1) { diff --git a/src/features/sortImports.ts b/src/features/sortImports.ts index 10492eb7..92ebb6b4 100644 --- a/src/features/sortImports.ts +++ b/src/features/sortImports.ts @@ -1,25 +1,11 @@ -import { OutputChannel, TextDocument, Uri, window, workspace } from 'coc.nvim'; +import { OutputChannel, TextDocument, window, workspace } from 'coc.nvim'; import fs from 'fs'; -import md5 from 'md5'; import * as path from 'path'; import which from 'which'; import { PythonSettings } from '../configSettings'; import { PythonExecutionService } from '../processService'; import { ExecutionInfo } from '../types'; -import { getTextEditsFromPatch } from '../utils'; - -function getTempFileWithDocumentContents(document: TextDocument): Promise { - return new Promise((resolve, reject) => { - const fsPath = Uri.parse(document.uri).fsPath; - const fileName = `${fsPath}.${md5(document.uri)}${path.extname(fsPath)}`; - fs.writeFile(fileName, document.getText(), (ex) => { - if (ex) { - reject(new Error(`Failed to create a temporary file, ${ex.message}`)); - } - resolve(fileName); - }); - }); -} +import { getTextEditsFromPatch, getTempFileWithDocumentContents } from '../utils'; function getSortProviderInfo(provider: 'pyright' | 'isort' | 'ruff', extensionRoot: string): ExecutionInfo | null { const pythonSettings = PythonSettings.getInstance(); diff --git a/src/utils.ts b/src/utils.ts index 67d38d2f..762c6001 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,10 @@ /* eslint-disable @typescript-eslint/no-var-requires */ -import { Position, Range, TextDocument, TextEdit } from 'coc.nvim'; +import { Position, Range, TextDocument, TextEdit, Uri } from 'coc.nvim'; import { Diff, diff_match_patch } from 'diff-match-patch'; +import fs from 'fs'; +import md5 from 'md5'; import { EOL } from 'os'; +import * as path from 'path'; const NEW_LINE_LENGTH = EOL.length; enum EditAction { @@ -263,3 +266,17 @@ export function getWindowsLineEndingCount(document: TextDocument, offset: number } return count; } + +export function getTempFileWithDocumentContents(document: TextDocument): Promise { + return new Promise((resolve, reject) => { + const fsPath = Uri.parse(document.uri).fsPath; + const fileName = `${fsPath.slice(0, -3)}${md5(document.uri)}${path.extname(fsPath)}`; + fs.writeFile(fileName, document.getText(), (ex) => { + if (ex) { + reject(new Error(`Failed to create a temporary file, ${ex.message}`)); + } + resolve(fileName); + }); + }); +} +