-
Notifications
You must be signed in to change notification settings - Fork 36
Always use a unique virtual document file path #688
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have streamlined this function quite a bit. I found that pretty much everything was shared code, and we don't actually need an "early exit" path for the The diffs are a little large but I hope it is a big improvement overall (PS, it is much easier to review this file in Split View in the github ui) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Really the main actual change is that the |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,45 +29,50 @@ import { | |
} from "vscode"; | ||
import { VirtualDoc, VirtualDocUri } from "./vdoc"; | ||
|
||
/** | ||
* Create an on disk temporary file containing the contents of the virtual document | ||
* | ||
* @param virtualDoc The document to use when populating the temporary file | ||
* @param docPath The path to the original document the virtual document is | ||
* based on. When `local` is `true`, this is used to determine the directory | ||
* to create the temporary file in. | ||
* @param local Whether or not the temporary file should be created "locally" in | ||
* the workspace next to `docPath` or in a temporary directory outside the | ||
* workspace. | ||
* @returns A `VirtualDocUri` | ||
*/ | ||
export async function virtualDocUriFromTempFile( | ||
virtualDoc: VirtualDoc, | ||
docPath: string, | ||
local: boolean | ||
): Promise<VirtualDocUri> { | ||
const newVirtualDocUri = (doc: TextDocument) => | ||
<VirtualDocUri>{ | ||
uri: doc.uri, | ||
cleanup: async () => await deleteDocument(doc), | ||
}; | ||
|
||
// if this is local then create it alongside the docPath and return a cleanup | ||
// function to remove it when the action is completed. | ||
if (local || virtualDoc.language.localTempFile) { | ||
const ext = virtualDoc.language.extension; | ||
const vdocPath = path.join(path.dirname(docPath), `.vdoc.${ext}`); | ||
fs.writeFileSync(vdocPath, virtualDoc.content); | ||
const vdocUri = Uri.file(vdocPath); | ||
const doc = await workspace.openTextDocument(vdocUri); | ||
return newVirtualDocUri(doc); | ||
} | ||
const useLocal = local || virtualDoc.language.localTempFile; | ||
|
||
// write the virtual doc as a temp file | ||
const vdocTempFile = createVirtualDocTempFile(virtualDoc); | ||
// If `useLocal`, then create the temporary document alongside the `docPath` | ||
// so tools like formatters have access to workspace configuration. Otherwise, | ||
// create it in a temp directory. | ||
const virtualDocFilepath = useLocal | ||
? createVirtualDocLocalFile(virtualDoc, path.dirname(docPath)) | ||
: createVirtualDocTempfile(virtualDoc); | ||
|
||
// open the document and save a reference to it | ||
const vdocUri = Uri.file(vdocTempFile); | ||
const doc = await workspace.openTextDocument(vdocUri); | ||
const virtualDocUri = Uri.file(virtualDocFilepath); | ||
const virtualDocTextDocument = await workspace.openTextDocument(virtualDocUri); | ||
|
||
// TODO: Reevaluate whether this is necessary. Old comment: | ||
// > if this is the first time getting a virtual doc for this | ||
// > language then execute a dummy request to cause it to load | ||
await commands.executeCommand<Hover[]>( | ||
"vscode.executeHoverProvider", | ||
vdocUri, | ||
new Position(0, 0) | ||
); | ||
if (!useLocal) { | ||
// TODO: Reevaluate whether this is necessary. Old comment: | ||
// > if this is the first time getting a virtual doc for this | ||
// > language then execute a dummy request to cause it to load | ||
await commands.executeCommand<Hover[]>( | ||
"vscode.executeHoverProvider", | ||
virtualDocUri, | ||
new Position(0, 0) | ||
); | ||
} | ||
|
||
return newVirtualDocUri(doc); | ||
return <VirtualDocUri>{ | ||
uri: virtualDocTextDocument.uri, | ||
cleanup: async () => await deleteDocument(virtualDocTextDocument), | ||
}; | ||
} | ||
|
||
// delete a document | ||
|
@@ -82,19 +87,57 @@ async function deleteDocument(doc: TextDocument) { | |
} | ||
} | ||
|
||
// create temp files for vdocs. use a base directory that has a subdirectory | ||
// for each extension used within the document. this is a no-op if the | ||
// file already exists | ||
tmp.setGracefulCleanup(); | ||
const vdocTempDir = tmp.dirSync().name; | ||
function createVirtualDocTempFile(virtualDoc: VirtualDoc) { | ||
const ext = virtualDoc.language.extension; | ||
const dir = path.join(vdocTempDir, ext); | ||
if (!fs.existsSync(dir)) { | ||
fs.mkdirSync(dir); | ||
const VIRTUAL_DOC_TEMP_DIRECTORY = tmp.dirSync().name; | ||
|
||
/** | ||
* Creates a virtual document in a temporary directory | ||
* | ||
* The temporary directory is automatically cleaned up on process exit. | ||
* | ||
* @param virtualDoc The document to use when populating the temporary file | ||
* @returns The path to the temporary file | ||
*/ | ||
function createVirtualDocTempfile(virtualDoc: VirtualDoc): string { | ||
const filepath = generateVirtualDocFilepath(VIRTUAL_DOC_TEMP_DIRECTORY, virtualDoc.language.extension); | ||
createVirtualDoc(filepath, virtualDoc.content); | ||
return filepath; | ||
} | ||
|
||
/** | ||
* Creates a virtual document in the provided directory | ||
* | ||
* @param virtualDoc The document to use when populating the temporary file | ||
* @param directory The directory to create the temporary file in | ||
* @returns The path to the temporary file | ||
*/ | ||
function createVirtualDocLocalFile(virtualDoc: VirtualDoc, directory: string): string { | ||
const filepath = generateVirtualDocFilepath(directory, virtualDoc.language.extension); | ||
createVirtualDoc(filepath, virtualDoc.content); | ||
return filepath; | ||
} | ||
|
||
/** | ||
* Creates a file filled with the provided content | ||
*/ | ||
function createVirtualDoc(filepath: string, content: string): void { | ||
const directory = path.dirname(filepath); | ||
|
||
if (!fs.existsSync(directory)) { | ||
fs.mkdirSync(directory); | ||
} | ||
const tmpPath = path.join(vdocTempDir, ext, ".intellisense." + uuid.v4() + "." + ext); | ||
fs.writeFileSync(tmpPath, virtualDoc.content); | ||
|
||
return tmpPath; | ||
fs.writeFileSync(filepath, content); | ||
} | ||
|
||
/** | ||
* Generates a unique virtual document file path | ||
* | ||
* It is important for virtual documents to have unique file paths. If a static | ||
* name like `.vdoc.{ext}` is used, it is possible for one language server | ||
* request to overwrite the contents of the virtual document while another | ||
* language server request is running (#683). | ||
*/ | ||
function generateVirtualDocFilepath(directory: string, extension: string): string { | ||
return path.join(directory, ".vdoc." + uuid.v4() + "." + extension); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Worth noting that for non- |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found it extremely useful to have an
outputChannel
to log to while debugging. I plumbed this through to points of interest so I could log.I have since removed the log calls, but I still think this is quite useful for future us so I've left the actual output channel in to make this easier later on.
It is shared with the LSP client, essentially we provide our own output channel now rather than having the LSP client generate one automatically for us (we do the same thing in positron)