Skip to content

Commit

Permalink
Add ability to use refactoring actions without saving the buffer (#992)
Browse files Browse the repository at this point in the history
* save-buf-before-code-action

* save-buf-before-code-action

* use-refactoring-without-saving-buffer

* moved getTempFile func to utils
  • Loading branch information
d-mitrofanov-v authored Oct 7, 2023
1 parent 1d8e38c commit 9b07864
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 45 deletions.
44 changes: 16 additions & 28 deletions src/features/refactor.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -192,18 +193,6 @@ interface RenameResponse {
results: [{ diff: string }];
}

async function checkDocument(doc: Document): Promise<boolean> {
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<void> {
if (!doc.dirty) {
return Promise.resolve();
Expand All @@ -218,8 +207,7 @@ function validateDocumentForRefactor(doc: Document): Promise<void> {

export async function extractVariable(root: string, document: TextDocument, range: Range, outputChannel: OutputChannel): Promise<any> {
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');
Expand All @@ -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<RenameResponse>(doc.textDocument, newName, Uri.parse(doc.uri).fsPath, range).then((response) => {
const rename = proxy.extractVariable<RenameResponse>(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<any> {
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');
Expand All @@ -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<RenameResponse>(doc.textDocument, newName, Uri.parse(doc.uri).fsPath, range).then((response) => {
const rename = proxy.extractMethod<RenameResponse>(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<void> {
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');
Expand All @@ -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<RenameResponse>(doc.textDocument, Uri.parse(doc.uri).fsPath, name, parentModule).then((response) => {
const resp = proxy.addImport<RenameResponse>(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<string>, outputChannel: OutputChannel): Promise<any> {
async function applyImports(doc: Document, resp: Promise<string>, outputChannel: OutputChannel, tempFile: string): Promise<any> {
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') {
Expand All @@ -321,7 +307,7 @@ async function applyImports(doc: Document, resp: Promise<string>, outputChannel:
}
}

async function extractName(textEditor: Document, newName: string, renameResponse: Promise<string>, outputChannel: OutputChannel): Promise<any> {
async function extractName(textEditor: Document, newName: string, renameResponse: Promise<string>, outputChannel: OutputChannel, tempFile: string): Promise<any> {
let changeStartsAtLine = -1;
try {
const diff = await renameResponse;
Expand All @@ -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) {
Expand Down
18 changes: 2 additions & 16 deletions src/features/sortImports.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
return new Promise<string>((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();
Expand Down
19 changes: 18 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -263,3 +266,17 @@ export function getWindowsLineEndingCount(document: TextDocument, offset: number
}
return count;
}

export function getTempFileWithDocumentContents(document: TextDocument): Promise<string> {
return new Promise<string>((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);
});
});
}

0 comments on commit 9b07864

Please sign in to comment.