vscode#WorkspaceEdit TypeScript Examples

The following examples show how to use vscode#WorkspaceEdit. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: documentActions.ts    From vscode-todo-md with MIT License 6 votes vote down vote up
/**
 * Recursively expand/collapse all nested tasks
 */
export async function toggleTaskCollapseRecursive(document: TextDocument, lineNumber: number) {
	const parentTask = getTaskAtLineExtension(lineNumber);
	if (!parentTask) {
		return undefined;
	}
	const edit = new WorkspaceEdit();

	if (parentTask.isCollapsed) {
		forEachTask(task => {
			if (task.isCollapsed && task.subtasks.length) {
				toggleTaskCollapseWorkspaceEdit(edit, document, task.lineNumber);
			}
		}, parentTask.subtasks);
	} else {
		forEachTask(task => {
			if (!task.isCollapsed && task.subtasks.length) {
				toggleTaskCollapseWorkspaceEdit(edit, document, task.lineNumber);
			}
		}, parentTask.subtasks);
	}
	toggleTaskCollapseWorkspaceEdit(edit, document, lineNumber);
	return await applyEdit(edit, document);
}
Example #2
Source File: spProviders.ts    From sourcepawn-vscode with MIT License 6 votes vote down vote up
public async provideRenameEdits(
    document: TextDocument,
    position: Position,
    newName: string,
    token: CancellationToken
  ): Promise<WorkspaceEdit> {
    return renameProvider(
      this.itemsRepository,
      position,
      document,
      newName,
      token
    );
  }
Example #3
Source File: documentActions.ts    From vscode-todo-md with MIT License 6 votes vote down vote up
/**
 * Increment count special tag. If already max `3/3` then set it to `0/3`
 */
export async function incrementCountForTask(document: TextDocument, lineNumber: number, task: TheTask) {
	const line = document.lineAt(lineNumber);
	const edit = new WorkspaceEdit();
	const count = task.count;
	if (!count) {
		return Promise.resolve(undefined);
	}
	let newValue = 0;
	// TODO: this function must call toggleDoneAtLine() !!!
	if (count.current !== count.needed) {
		newValue = count.current + 1;
		if (newValue === count.needed) {
			insertCompletionDateWorkspaceEdit(edit, document, line, task);
			removeOverdueWorkspaceEdit(edit, document.uri, task);
		}
		setCountCurrentValueWorkspaceEdit(edit, document.uri, count, String(newValue));
	} else {
		setCountCurrentValueWorkspaceEdit(edit, document.uri, count, '0');
		removeCompletionDateWorkspaceEdit(edit, document.uri, task);
	}
	return applyEdit(edit, document);
}
Example #4
Source File: command.ts    From typescript-explicit-types with GNU General Public License v3.0 6 votes vote down vote up
generateType = async (
  { typescriptHoverResult, typePosition, isFunction }: GenerateTypeInfo,
  editor: TextEditor,
  isAutoFormatOn?: boolean
) => {
  const indexes = findMatchIndexes(/:/gm, typescriptHoverResult);
  const dirtyType = typescriptHoverResult.slice(isFunction ? indexes.slice(-1)[0] : indexes[0]);
  const cleanType = dirtyType.replace(/(`)/gm, '').replace(/\n+$/, '');
  await editor.edit((editor) => editor.insert(typePosition, cleanType));

  if (!isAutoFormatOn) return;

  const document = editor.document;
  const text = document.getText();
  const typeIndex = text.indexOf(cleanType.replace(/\n/gm, '\r\n'), document.offsetAt(typePosition));
  if (typeIndex < 0) return;

  const typePositionStart = document.positionAt(typeIndex);
  const typePositionEnd = document.positionAt(typeIndex + cleanType.length + (cleanType.match(/\n/gm)?.length ?? 0));
  const typeRange = new Range(typePositionStart, typePositionEnd);
  if (!typeRange) return;

  if (isAutoFormatOn) {
    const edits = await executeFormatDocumentProvider(document.uri);
    if (!edits) return;
    const workspaceEdit = new WorkspaceEdit();
    workspaceEdit.set(document.uri, edits);
    await workspace.applyEdit(workspaceEdit);
  }
}
Example #5
Source File: lexMode.ts    From yash with MIT License 6 votes vote down vote up
export function getLEXMode(lexLanguageService: LEXLanguageService): LanguageMode {
    const cache = CreateDocumentCache<LexDocument>(10, 60, document => lexLanguageService.parseLexDocument(document));
    return {
        getId() {
            return 'lex';
        },
        doValidation(document: TextDocument): Diagnostic[] {
            const lex = cache.get(document);
            return lexLanguageService.doValidation(document, lex);
        },
        doComplete(document: TextDocument, position: Position): CompletionList | CompletionItem[] {
            const lex = cache.get(document);
            return lexLanguageService.doComplete(document, position, lex);
        },
        doHover(document: TextDocument, position: Position): Hover | null {
            const lex = cache.get(document);
            return lexLanguageService.doHover(document, position, lex);
        },
        findDefinition(document: TextDocument, position: Position): Definition | null {
            const lex = cache.get(document);
            return lexLanguageService.findDefinition(document, position, lex);
        },
        findReferences(document: TextDocument, position: Position): Location[] {
            const lex = cache.get(document);
            return lexLanguageService.findReferences(document, position, lex);
        },
        doRename(document: TextDocument, position: Position, newName: string): WorkspaceEdit | null {
            const lex = cache.get(document);
            return lexLanguageService.doRename(document, position, newName, lex);
        },
        onDocumentRemoved(document: TextDocument) {
            cache.onDocumentRemoved(document);
        },
        dispose() {
            cache.dispose();
        }
    };
}
Example #6
Source File: quickfix-select-valid-type.ts    From al-objid with MIT License 6 votes vote down vote up
export async function quickFixSelectValidType(document: TextDocument, range: Range, remainingTypes: string[]) {
    const selectedType = await window.showQuickPick(remainingTypes, {
        placeHolder: "Select an object type...",
    });
    if (!selectedType) {
        return;
    }

    let replace = new WorkspaceEdit();
    replace.set(document.uri, [TextEdit.replace(range, `"${selectedType}"`)]);
    await workspace.applyEdit(replace);
    await document.save();
}
Example #7
Source File: yaccRename.ts    From yash with MIT License 6 votes vote down vote up
export function doYACCRename(document: TextDocument, position: Position, newName: string, yaccDocument: YACCDocument): WorkspaceEdit | null {
    const offset = document.offsetAt(position);
    const node = yaccDocument.getEmbeddedNode(offset);
    if (node) {
        return null;
    }

    const word = document.getText(document.getWordRangeAtPosition(position));
    var symbol: ISymbol | undefined = yaccDocument.types[word] || yaccDocument.symbols[word] || yaccDocument.tokens[word] || yaccDocument.aliases[`"${word}"`];
    const edits = new WorkspaceEdit();
    if (symbol && symbol.name.startsWith('"')) newName = `"${newName}"`
    symbol?.references.forEach(reference => {
        edits.replace(document.uri, new Range(document.positionAt(reference[0]), document.positionAt(reference[1])), newName);
    })
    return edits;
}
Example #8
Source File: extensionUtils.ts    From vscode-todo-md with MIT License 6 votes vote down vote up
/**
 * vscode `WorkspaceEdit` allowes changing files that are not even opened.
 *
 * `document.save()` is needed to prevent opening those files after applying the edit.
 */
export async function applyEdit(edit: WorkspaceEdit, document: TextDocument) {
	await workspace.applyEdit(edit);
	return await document.save();
}
Example #9
Source File: lexRename.ts    From yash with MIT License 6 votes vote down vote up
export function doLEXRename(document: TextDocument, position: Position, newName: string, lexDocument: LexDocument): WorkspaceEdit | null {
    const offset = document.offsetAt(position);
    const node = lexDocument.getEmbeddedCode(offset);
    if (node) {
        return null;
    }

    const word = document.getText(document.getWordRangeAtPosition(position));
    var symbol: ISymbol | undefined = lexDocument.defines[word] || lexDocument.states[word];
    const edits = new WorkspaceEdit();
    symbol?.references.forEach(reference => {
        edits.replace(document.uri, new Range(document.positionAt(reference[0]), document.positionAt(reference[1])), newName);
    })
    return edits;
}
Example #10
Source File: documentActions.ts    From vscode-todo-md with MIT License 6 votes vote down vote up
export function setDueDateWorkspaceEdit(edit: WorkspaceEdit, document: TextDocument, lineNumber: number, newDueDate: string) {
	const dueDate = `{due:${newDueDate}}`;
	const task = getTaskAtLineExtension(lineNumber);
	if (task?.overdueRange) {
		edit.delete(document.uri, task.overdueRange);
	}
	if (task?.dueRange) {
		edit.replace(document.uri, task.dueRange, dueDate);
	} else {
		const line = document.lineAt(lineNumber);
		const isLineEndsWithWhitespace = line.text.endsWith(' ');
		edit.insert(document.uri, line.range.end, `${isLineEndsWithWhitespace ? '' : ' '}${dueDate}`);
	}
}
Example #11
Source File: spRenameProvider.ts    From sourcepawn-vscode with MIT License 6 votes vote down vote up
export function renameProvider(
  itemsRepo: ItemsRepository,
  position: Position,
  document: TextDocument,
  newText: string,
  token: CancellationToken
): WorkspaceEdit | undefined {
  const items = itemsRepo.getItemFromPosition(document, position);
  if (items.length === 0) {
    return undefined;
  }

  const edit = new WorkspaceEdit();
  items.forEach((e1) => {
    if (e1.references !== undefined) {
      e1.references.forEach((e2) => {
        edit.replace(e2.uri, e2.range, newText);
      });
    }
    edit.replace(URI.file(e1.filePath), e1.range, newText);
  });
  return edit;
}
Example #12
Source File: decrementPriority.ts    From vscode-todo-md with MIT License 6 votes vote down vote up
export function decrementPriority(editor: TextEditor) {
	const edit = new WorkspaceEdit();

	for (const line of getSelectedLineNumbers(editor)) {
		incrementOrDecrementPriorityWorkspaceEdit(edit, editor.document, line, 'decrement');
	}

	applyEdit(edit, editor.document);
}
Example #13
Source File: NextObjectIdCompletionItem.ts    From al-objid with MIT License 5 votes vote down vote up
getCompletionCommand(position: Position, uri: Uri, type: string, app: ALApp, objectId: NextObjectIdInfo): Command {
        return {
            command: NinjaCommand.CommitSuggestion,
            title: "",
            arguments: [
                async () => {
                    //TODO Assigning from public range (1..50000) for enum values results in "No more objects..." error
                    output.log(`Committing object ID auto-complete for ${type} ${objectId.id}`, LogLevel.Info);
                    const realId = await Backend.getNextNo(
                        app,
                        type,
                        app.manifest.idRanges,
                        true,
                        objectId.id as number
                    );
                    const notChanged = !realId || this.isIdEqual(realId.id, objectId.id as number);
                    Telemetry.instance.log("getNextNo-commit", app.hash, notChanged ? undefined : "different");
                    if (notChanged) {
                        return;
                    }
                    output.log(
                        `Another user has consumed ${type} ${objectId.id} in the meantime. Retrieved new: ${type} ${realId.id}`,
                        LogLevel.Info
                    );

                    let replacement = `${realId.id}`;
                    if (!realId.available) {
                        if (this._range) {
                            UI.nextId.showNoMoreInLogicalRangeWarning(this._range.description).then(result => {
                                if (result === LABELS.BUTTON_LEARN_MORE) {
                                    showDocument(DOCUMENTS.LOGICAL_RANGES);
                                }
                            });
                        } else {
                            syncIfChosen(app, UI.nextId.showNoMoreNumbersWarning());
                        }
                        replacement = "";
                    }

                    let replace = new WorkspaceEdit();
                    replace.set(uri, [
                        TextEdit.replace(
                            new Range(position, position.translate(0, objectId.id.toString().length)),
                            replacement
                        ),
                    ]);
                    workspace.applyEdit(replace);
                },
            ],
        };
    }
Example #14
Source File: documentActions.ts    From vscode-todo-md with MIT License 5 votes vote down vote up
export function removeCompletionDateWorkspaceEdit(edit: WorkspaceEdit, uri: Uri, task: TheTask) {
	if (task.completionDateRange) {
		edit.delete(uri, task.completionDateRange);
	}
}
Example #15
Source File: server-events.ts    From code-snippet with Apache License 2.0 5 votes vote down vote up
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- must match interface
  public async doApply(we: WorkspaceEdit): Promise<any> {
    // Apply code
  }
Example #16
Source File: documentActions.ts    From vscode-todo-md with MIT License 5 votes vote down vote up
function addOverdueSpecialTagWorkspaceEdit(edit: WorkspaceEdit, uri: Uri, line: TextLine, overdueDateString: string) {
	edit.insert(uri, new Position(line.lineNumber, line.range.end.character), ` {overdue:${overdueDateString}}`);
}
Example #17
Source File: yaccMode.ts    From yash with MIT License 5 votes vote down vote up
export function getYACCMode(yaccLanguageService: YACCLanguageService): LanguageMode {
    const cache = CreateDocumentCache<YACCDocument>(10, 60, document => yaccLanguageService.parseYACCDocument(document));
    return {
        getId() {
            return 'yacc';
        },
        doValidation(document: TextDocument, force?:boolean): Diagnostic[] {
            if (force) {
                return yaccLanguageService.doValidation(document, yaccLanguageService.parseYACCDocument(document));
            }
            const yacc = cache.get(document);
            return yaccLanguageService.doValidation(document, yacc);
        },
        doComplete(document: TextDocument, position: Position): CompletionList | CompletionItem[] {
            const yacc = cache.get(document);
            return yaccLanguageService.doComplete(document, position, yacc);
        },
        doHover(document: TextDocument, position: Position): Hover | null {
            const yacc = cache.get(document);
            return yaccLanguageService.doHover(document, position, yacc);
        },
        findTypeDefinition(document: TextDocument, position: Position): Definition | null {
            const yacc = cache.get(document);
            return yaccLanguageService.findTypeDefinition(document, position, yacc);
        },
        findDefinition(document: TextDocument, position: Position): Definition | null {
            const yacc = cache.get(document);
            return yaccLanguageService.findDefinition(document, position, yacc);
        },
        findReferences(document: TextDocument, position: Position): Location[] {
            const yacc = cache.get(document);
            return yaccLanguageService.findReferences(document, position, yacc);
        },
        doRename(document: TextDocument, position: Position, newName: string): WorkspaceEdit | null {
            const yacc = cache.get(document);
            return yaccLanguageService.doRename(document, position, newName, yacc);
        },
        getSemanticTokens(document: TextDocument): SemanticTokenData[] {
            const yacc = cache.get(document);
            return yaccLanguageService.getSemanticTokens(document, yacc);
        },
        getSemanticTokenLegend() {
            return { types: tokenTypes, modifiers: tokenModifiers };
        },
        onDocumentRemoved(document: TextDocument) {
            cache.onDocumentRemoved(document);
        },
        dispose() {
            cache.dispose();
        }
    };
}
Example #18
Source File: formatting.ts    From twee3-language-tools with MIT License 5 votes vote down vote up
export function styleByWrapping(editor: TextEditor, startPattern: string, endPattern = startPattern) {
    let selections = editor.selections;

    let batchEdit = new WorkspaceEdit();
    let shifts: [Position, number][] = [];
    let newSelections: Selection[] = selections.slice();

    for (const [i, selection] of selections.entries()) {

        let cursorPos = selection.active;
        const shift = shifts.map(([pos, s]) => (selection.start.line === pos.line && selection.start.character >= pos.character) ? s : 0)
            .reduce((a, b) => a + b, 0);

        if (selection.isEmpty) {
            const context = getContext(editor, cursorPos, startPattern, endPattern);

            // Patterns are added for SugarCube
            // No selected text
            if (
                startPattern === endPattern &&
                ["**", "*", "__", "_", "//", "''"].includes(startPattern) &&
                context === `${startPattern}text|${endPattern}`
            ) {
                // `**text|**` to `**text**|`
                let newCursorPos = cursorPos.with({ character: cursorPos.character + shift + endPattern.length });
                newSelections[i] = new Selection(newCursorPos, newCursorPos);
                continue;
            } else if (context === `${startPattern}|${endPattern}`) {
                // `**|**` to `|`
                let start = cursorPos.with({ character: cursorPos.character - startPattern.length });
                let end = cursorPos.with({ character: cursorPos.character + endPattern.length });
                wrapRange(editor, batchEdit, shifts, newSelections, i, shift, cursorPos, new Range(start, end), false, startPattern, endPattern);
            } else {
                // Select word under cursor
                // The markdown extension uses a custom regex very similar to this one for their def
                // of word. This removes the exclusion of certain characters since formats use them.
                let wordRange = editor.document.getWordRangeAtPosition(cursorPos, /(-?\d*\.\d\w*)|([^\!\@\#\%\^\&\(\)\-\=\+\[\{\]\}\\\|\;\:\"\,\.\<\>\?\s\,\。\《\》\?\;\:\‘\“\’\”\(\)\【\】\、]+)/g);
                if (wordRange == undefined) {
                    wordRange = selection;
                }
                // One special case: toggle strikethrough in task list
                const currentTextLine = editor.document.lineAt(cursorPos.line);
                if (startPattern === '~~' && /^\s*[\*\+\-] (\[[ x]\] )? */g.test(currentTextLine.text)) {
                    let match = currentTextLine.text.match(/^\s*[\*\+\-] (\[[ x]\] )? */g) as RegExpMatchArray;
                    wordRange = currentTextLine.range.with(new Position(cursorPos.line, match[0].length));
                }
                wrapRange(editor, batchEdit, shifts, newSelections, i, shift, cursorPos, wordRange, false, startPattern, endPattern);
            }
        } else {
            // Text selected
            wrapRange(editor, batchEdit, shifts, newSelections, i, shift, cursorPos, selection, true, startPattern, endPattern);
        }
    }

    return workspace.applyEdit(batchEdit).then(() => {
        editor.selections = newSelections;
    });
}
Example #19
Source File: ReferenceRenameProvider.ts    From memo with MIT License 5 votes vote down vote up
public async provideRenameEdits(
    document: TextDocument,
    position: Position,
    newName: string,
  ): Promise<WorkspaceEdit> {
    const refAtPos = getReferenceAtPosition(document, position);

    if (refAtPos) {
      const { ref } = refAtPos;

      const workspaceEdit = new WorkspaceEdit();

      const unknownUris = containsUnknownExt(ref) ? await findFilesByExts([extractExt(ref)]) : [];

      const augmentedUris = unknownUris.length
        ? sortPaths([...cache.getWorkspaceCache().allUris, ...unknownUris], {
            pathKey: 'path',
            shallowFirst: true,
          })
        : cache.getWorkspaceCache().allUris;

      const fsPath = findUriByRef(augmentedUris, ref)?.fsPath;

      if (fsPath) {
        const ext = path.parse(newName).ext;
        const newRelativePath = `${newName}${
          ext === '' || (containsUnknownExt(newName) && containsMarkdownExt(fsPath)) ? '.md' : ''
        }`;
        const newUri = Uri.file(
          isLongRef(ref)
            ? path.join(getWorkspaceFolder()!, newRelativePath)
            : path.join(path.dirname(fsPath), newRelativePath),
        );

        workspaceEdit.renameFile(Uri.file(fsPath), newUri);
      }

      return workspaceEdit;
    }

    throw new Error('Rename is not available. Please try when focused on the link.');
  }
Example #20
Source File: setDueDate.ts    From vscode-todo-md with MIT License 5 votes vote down vote up
/**
 * Open vscode input box that aids in creating of due date.
 */
export function openSetDueDateInputbox(document: TextDocument, lineNumbers: number[]) {
	const inputBox = window.createInputBox();
	let value: string | undefined = '+0';
	inputBox.value = value;
	inputBox.title = 'Set due date';
	const docsButtonName = 'Documentation';
	inputBox.onDidTriggerButton(e => {
		if (e.tooltip === docsButtonName) {
			followLink('https://github.com/usernamehw/vscode-todo-md/blob/master/docs/docs.md#set-due-date-helper-function-todomdsetduedate');
		}
	});
	inputBox.buttons = [{
		iconPath: new ThemeIcon('question'),
		tooltip: docsButtonName,
	}];
	inputBox.prompt = inputOffset(new DueDate(helpCreateDueDate(value)!).closestDueDateInTheFuture);
	inputBox.show();

	inputBox.onDidChangeValue((e: string) => {
		value = e;
		const newDueDate = helpCreateDueDate(value);
		if (!newDueDate) {
			inputBox.prompt = inputOffset('❌ Invalid');
			return;
		}
		inputBox.prompt = inputOffset(new DueDate(newDueDate).closestDueDateInTheFuture);
	});

	inputBox.onDidAccept(async () => {
		if (!value) {
			return;
		}
		const newDueDate = helpCreateDueDate(value);

		if (newDueDate) {
			const edit = new WorkspaceEdit();
			for (const line of lineNumbers) {
				setDueDateWorkspaceEdit(edit, document, line, newDueDate);
			}
			await applyEdit(edit, document);
			inputBox.hide();
			inputBox.dispose();
			updateEverything();
		}
	});
}
Example #21
Source File: fileWatcher.spec.ts    From memo with MIT License 4 votes vote down vote up
describe('fileWatcher', () => {
  beforeEach(closeEditorsAndCleanWorkspace);

  afterEach(closeEditorsAndCleanWorkspace);

  let mockContext: ExtensionContext;

  let deactivateFileWatcher: Function;

  beforeEach(() => {
    mockContext = {
      subscriptions: [],
    } as unknown as ExtensionContext;

    deactivateFileWatcher = fileWatcher.activate(mockContext, {
      uriCachingDelay: 50,
      documentChangeDelay: 50,
    });
  });

  afterEach(() => {
    deactivateFileWatcher && deactivateFileWatcher();

    mockContext.subscriptions.forEach((sub) => sub.dispose());
  });

  describe('automatic refs update on file rename', () => {
    it('should update short ref with short ref on file rename', async () => {
      const noteName0 = rndName();
      const noteName1 = rndName();
      const nextNoteName1 = rndName();

      await createFile(`${noteName0}.md`, `[[${noteName1}]]`, false);
      await createFile(`${noteName1}.md`, '', false);

      const edit = new WorkspaceEdit();
      edit.renameFile(
        Uri.file(`${getWorkspaceFolder()}/${noteName1}.md`),
        Uri.file(`${getWorkspaceFolder()}/${nextNoteName1}.md`),
      );

      await workspace.applyEdit(edit);

      const doc = await openTextDocument(`${noteName0}.md`);

      await waitForExpect(() => expect(doc.getText()).toBe(`[[${nextNoteName1}]]`));
    });

    it('should update short ref with label with another short ref with label on file rename', async () => {
      const noteName0 = rndName();
      const noteName1 = rndName();
      const nextNoteName1 = rndName();

      await createFile(`${noteName0}.md`, `[[${noteName1}|Test Label]]`, false);
      await createFile(`${noteName1}.md`, '', false);

      const edit = new WorkspaceEdit();
      edit.renameFile(
        Uri.file(`${getWorkspaceFolder()}/${noteName1}.md`),
        Uri.file(`${getWorkspaceFolder()}/${nextNoteName1}.md`),
      );

      await workspace.applyEdit(edit);

      const doc = await openTextDocument(`${noteName0}.md`);

      await waitForExpect(() => expect(doc.getText()).toBe(`[[${nextNoteName1}|Test Label]]`));
    });

    it('should update long ref with long ref on file rename', async () => {
      const noteName0 = rndName();
      const noteName1 = rndName();
      const nextNoteName1 = rndName();

      await createFile(`${noteName0}.md`, `[[folder1/${noteName1}|Test Label]]`, false);
      await createFile(`${noteName1}.md`, '', false);
      await createFile(`${nextNoteName1}.md`, '', false);
      await createFile(`/folder1/${noteName1}.md`, '', false);

      const edit = new WorkspaceEdit();
      edit.renameFile(
        Uri.file(`${getWorkspaceFolder()}/folder1/${noteName1}.md`),
        Uri.file(`${getWorkspaceFolder()}/folder1/${nextNoteName1}.md`),
      );

      await workspace.applyEdit(edit);

      const doc = await openTextDocument(`${noteName0}.md`);

      await waitForExpect(() =>
        expect(doc.getText()).toBe(`[[folder1/${nextNoteName1}|Test Label]]`),
      );
    });

    it('should update long ref with short ref on file rename', async () => {
      const noteName0 = rndName();
      const noteName1 = rndName();
      const nextNoteName1 = rndName();

      await createFile(`${noteName0}.md`, `[[folder1/${noteName1}]]`, false);
      await createFile(`${noteName1}.md`, '', false);
      await createFile(`/folder1/${noteName1}.md`, '', false);

      const edit = new WorkspaceEdit();
      edit.renameFile(
        Uri.file(`${getWorkspaceFolder()}/folder1/${noteName1}.md`),
        Uri.file(`${getWorkspaceFolder()}/folder1/${nextNoteName1}.md`),
      );

      await workspace.applyEdit(edit);

      const doc = await openTextDocument(`${noteName0}.md`);

      await waitForExpect(() => expect(doc.getText()).toBe(`[[${nextNoteName1}]]`));
    });

    it('should update long ref with short ref on file rename 2', async () => {
      const noteName0 = rndName();
      const noteName1 = rndName();
      const nextNoteName1 = rndName();

      await createFile(`${noteName0}.md`, `[[folder1/${noteName1}]]`, false);
      await createFile(`/folder1/${noteName1}.md`, '', false);

      const edit = new WorkspaceEdit();
      edit.renameFile(
        Uri.file(`${getWorkspaceFolder()}/folder1/${noteName1}.md`),
        Uri.file(`${getWorkspaceFolder()}/folder1/${nextNoteName1}.md`),
      );

      await workspace.applyEdit(edit);

      const doc = await openTextDocument(`${noteName0}.md`);

      await waitForExpect(() => expect(doc.getText()).toBe(`[[${nextNoteName1}]]`));
    });

    it('should update short ref with long ref on file rename', async () => {
      const noteName0 = rndName();
      const noteName1 = rndName();

      await createFile(`${noteName0}.md`, `[[${noteName1}]]`, false);
      await createFile(`${noteName1}.md`, '', false);
      await createFile(`/folder1/${noteName1}.md`, '', false);

      await cacheWorkspace();

      const edit = new WorkspaceEdit();
      edit.renameFile(
        Uri.file(`${getWorkspaceFolder()}/${noteName1}.md`),
        Uri.file(`${getWorkspaceFolder()}/folder2/${noteName1}.md`),
      );

      await workspace.applyEdit(edit);

      const doc = await openTextDocument(`${noteName0}.md`);

      await waitForExpect(() => expect(doc.getText()).toBe(`[[folder2/${noteName1}]]`));
    });

    it('should update short ref to short ref with unknown extension on file rename', async () => {
      const noteName0 = rndName();
      const noteName1 = rndName();
      const nextName = rndName();

      await createFile(`${noteName0}.md`, `[[${noteName1}]]`, false);
      await createFile(`${noteName1}.md`, '', false);

      const edit = new WorkspaceEdit();
      edit.renameFile(
        Uri.file(`${getWorkspaceFolder()}/${noteName1}.md`),
        Uri.file(`${getWorkspaceFolder()}/${nextName}.unknown`),
      );

      await workspace.applyEdit(edit);

      const doc = await openTextDocument(`${noteName0}.md`);

      await waitForExpect(() => expect(doc.getText()).toBe(`[[${nextName}.unknown]]`));
    });

    it('should update short ref with unknown extension to short ref with a known extension on file rename', async () => {
      const noteName0 = rndName();
      const noteName1 = rndName();
      const nextName = rndName();

      await createFile(`${noteName0}.md`, `[[${noteName1}.unknown]]`, false);
      await createFile(`${noteName1}.unknown`, '', false);

      const edit = new WorkspaceEdit();
      edit.renameFile(
        Uri.file(`${getWorkspaceFolder()}/${noteName1}.unknown`),
        Uri.file(`${getWorkspaceFolder()}/${nextName}.gif`),
      );

      await workspace.applyEdit(edit);

      const doc = await openTextDocument(`${noteName0}.md`);

      await waitForExpect(() => expect(doc.getText()).toBe(`[[${nextName}.gif]]`));
    });

    describe('with links.format = long', () => {
      beforeEach(async () => {
        await updateMemoConfigProperty('links.format', 'long');
      });

      it('should update short ref with long ref on file rename', async () => {
        const noteName0 = rndName();
        const noteName1 = rndName();

        await createFile(`${noteName0}.md`, `[[${noteName1}]]`, false);
        await createFile(`/folder1/${noteName1}.md`, '', false);

        await cacheWorkspace();

        const edit = new WorkspaceEdit();
        edit.renameFile(
          Uri.file(`${getWorkspaceFolder()}/folder1/${noteName1}.md`),
          Uri.file(`${getWorkspaceFolder()}/folder2/${noteName1}.md`),
        );

        await workspace.applyEdit(edit);

        const doc = await openTextDocument(`${noteName0}.md`);

        await waitForExpect(() => expect(doc.getText()).toBe(`[[folder2/${noteName1}]]`));
      });

      it('should update long ref with short ref on moving file to workspace root', async () => {
        const noteName0 = rndName();
        const noteName1 = rndName();
        const nextNoteName1 = rndName();

        await createFile(`${noteName0}.md`, `[[folder1/${noteName1}]]`, false);
        await createFile(`/folder1/${noteName1}.md`, '', false);

        const edit = new WorkspaceEdit();
        edit.renameFile(
          Uri.file(`${getWorkspaceFolder()}/folder1/${noteName1}.md`),
          Uri.file(`${getWorkspaceFolder()}/${nextNoteName1}.md`),
        );

        await workspace.applyEdit(edit);

        const doc = await openTextDocument(`${noteName0}.md`);

        await waitForExpect(() => expect(doc.getText()).toBe(`[[${nextNoteName1}]]`));
      });

      it('should update long ref with long ref on file rename', async () => {
        const noteName0 = rndName();
        const noteName1 = rndName();
        const nextNoteName1 = rndName();

        await createFile(`${noteName0}.md`, `[[folder1/${noteName1}]]`, false);
        await createFile(`${noteName1}.md`, '', false);
        await createFile(`/folder1/${noteName1}.md`, '', false);

        const edit = new WorkspaceEdit();
        edit.renameFile(
          Uri.file(`${getWorkspaceFolder()}/folder1/${noteName1}.md`),
          Uri.file(`${getWorkspaceFolder()}/folder1/${nextNoteName1}.md`),
        );

        await workspace.applyEdit(edit);

        const doc = await openTextDocument(`${noteName0}.md`);

        await waitForExpect(() => expect(doc.getText()).toBe(`[[folder1/${nextNoteName1}]]`));
      });

      it('should not touch short ref', async () => {
        const noteName0 = rndName();
        const noteName1 = rndName();
        const nextNoteName1 = rndName();

        await createFile(`${noteName0}.md`, `[[${noteName1}]] [[folder1/${noteName1}]]`, false);
        await createFile(`${noteName1}.md`, '', false);
        await createFile(`/folder1/${noteName1}.md`, '', false);

        const edit = new WorkspaceEdit();
        edit.renameFile(
          Uri.file(`${getWorkspaceFolder()}/folder1/${noteName1}.md`),
          Uri.file(`${getWorkspaceFolder()}/folder1/${nextNoteName1}.md`),
        );

        await workspace.applyEdit(edit);

        const doc = await openTextDocument(`${noteName0}.md`);

        await waitForExpect(() =>
          expect(doc.getText()).toBe(`[[${noteName1}]] [[folder1/${nextNoteName1}]]`),
        );
      });
    });
  });

  it('should sync workspace cache on file create', async () => {
    const noteName = rndName();
    const imageName = rndName();

    const workspaceCache0 = await getWorkspaceCache();

    expect([...workspaceCache0.markdownUris, ...workspaceCache0.imageUris]).toHaveLength(0);

    await createFile(`${noteName}.md`, '', false);
    await createFile(`${imageName}.md`, '', false);

    await waitForExpect(async () => {
      const workspaceCache = await cache.getWorkspaceCache();

      expect([...workspaceCache.markdownUris, ...workspaceCache.imageUris]).toHaveLength(2);
      expect(
        [...workspaceCache.markdownUris, ...workspaceCache.imageUris].map(({ fsPath }) =>
          path.basename(fsPath),
        ),
      ).toEqual(expect.arrayContaining([`${noteName}.md`, `${imageName}.md`]));
    });
  });

  it('should add new dangling refs to cache on file create', async () => {
    const noteName = rndName();

    expect(cache.getWorkspaceCache().danglingRefs).toEqual([]);
    expect(cache.getWorkspaceCache().danglingRefsByFsPath).toEqual({});

    await createFile(`${noteName}.md`, '[[dangling-ref]] [[dangling-ref2]]', false);

    await waitForExpect(() => {
      expect(cache.getWorkspaceCache().danglingRefs).toEqual(['dangling-ref', 'dangling-ref2']);
      expect(Object.values(cache.getWorkspaceCache().danglingRefsByFsPath)).toEqual([
        ['dangling-ref', 'dangling-ref2'],
      ]);
    });
  });

  it('should remove dangling refs from cache on file remove', async () => {
    const noteName = rndName();

    await createFile(`${noteName}.md`, '[[dangling-ref]] [[dangling-ref2]]', false);

    await waitForExpect(() => {
      expect(cache.getWorkspaceCache().danglingRefs).toEqual(['dangling-ref', 'dangling-ref2']);
      expect(Object.values(cache.getWorkspaceCache().danglingRefsByFsPath)).toEqual([
        ['dangling-ref', 'dangling-ref2'],
      ]);
    });

    await removeFile(`${noteName}.md`);

    await waitForExpect(() => {
      expect(cache.getWorkspaceCache().danglingRefs).toEqual([]);
      expect(cache.getWorkspaceCache().danglingRefsByFsPath).toEqual({});
    });
  });

  it('should add and remove dangling refs on file edit', async () => {
    const noteName = rndName();

    await createFile(`${noteName}.md`, '[[dangling-ref]]', false);

    await waitForExpect(() => {
      expect(cache.getWorkspaceCache().danglingRefs).toEqual(['dangling-ref']);
      expect(Object.values(cache.getWorkspaceCache().danglingRefsByFsPath)).toEqual([
        ['dangling-ref'],
      ]);
    });

    const doc = await openTextDocument(`${noteName}.md`);

    const editor = await window.showTextDocument(doc);

    await editor.edit((edit) => edit.insert(new Position(1, 0), '\n[[he]]'));

    await waitForExpect(() => {
      expect(cache.getWorkspaceCache().danglingRefs).toEqual(['dangling-ref', 'he']);
      expect(Object.values(cache.getWorkspaceCache().danglingRefsByFsPath)).toEqual([
        ['dangling-ref', 'he'],
      ]);
    });

    await editor.edit((edit) => {
      edit.delete(new Range(new Position(1, 0), new Position(2, 0)));
      edit.insert(new Position(1, 0), '[[hello]]');
    });

    await waitForExpect(() => {
      expect(cache.getWorkspaceCache().danglingRefs).toEqual(['dangling-ref', 'hello']);
      expect(Object.values(cache.getWorkspaceCache().danglingRefsByFsPath)).toEqual([
        ['dangling-ref', 'hello'],
      ]);
    });
  });

  it('should sync workspace cache on file remove', async () => {
    const noteName = rndName();

    await createFile(`${noteName}.md`, '', false);

    await waitForExpect(async () => {
      const workspaceCache = await cache.getWorkspaceCache();

      expect([...workspaceCache.markdownUris, ...workspaceCache.imageUris]).toHaveLength(1);
      expect(
        [...workspaceCache.markdownUris, ...workspaceCache.imageUris].map(({ fsPath }) =>
          path.basename(fsPath),
        ),
      ).toContain(`${noteName}.md`);
    });

    removeFile(`${noteName}.md`);

    await waitForExpect(async () => {
      const workspaceCache = await cache.getWorkspaceCache();

      expect([...workspaceCache.markdownUris, ...workspaceCache.imageUris]).toHaveLength(0);
      expect(
        [...workspaceCache.markdownUris, ...workspaceCache.imageUris].map(({ fsPath }) =>
          path.basename(fsPath),
        ),
      ).not.toContain(`${noteName}.md`);
    });
  });
});
Example #22
Source File: extension.ts    From language-tools with MIT License 4 votes vote down vote up
function addRenameFileListener(getLS: () => LanguageClient) {
    workspace.onDidRenameFiles(async (evt) => {
        const oldUri = evt.files[0].oldUri.toString(true);
        const parts = oldUri.split(/\/|\\/);
        const lastPart = parts[parts.length - 1];
        // If user moves/renames a folder, the URI only contains the parts up to that folder,
        // and not files. So in case the URI does not contain a '.', check for imports to update.
        if (
            lastPart.includes('.') &&
            !['.ts', '.js', '.json', '.svelte'].some((ending) => lastPart.endsWith(ending))
        ) {
            return;
        }

        window.withProgress(
            { location: ProgressLocation.Window, title: 'Updating Imports..' },
            async () => {
                const editsForFileRename = await getLS().sendRequest<LSWorkspaceEdit | null>(
                    '$/getEditsForFileRename',
                    // Right now files is always an array with a single entry.
                    // The signature was only designed that way to - maybe, in the future -
                    // have the possibility to change that. If that ever does, update this.
                    // In the meantime, just assume it's a single entry and simplify the
                    // rest of the logic that way.
                    {
                        oldUri,
                        newUri: evt.files[0].newUri.toString(true)
                    }
                );
                const edits = editsForFileRename?.documentChanges?.filter(TextDocumentEdit.is);
                if (!edits) {
                    return;
                }

                const workspaceEdit = new WorkspaceEdit();
                // We need to take into account multiple cases:
                // - A Svelte file is moved/renamed
                //      -> all updates will be related to that Svelte file, do that here. The TS LS won't even notice the update
                // - A TS/JS file is moved/renamed
                //      -> all updates will be related to that TS/JS file
                //      -> let the TS LS take care of these updates in TS/JS files, do Svelte file updates here
                // - A folder with TS/JS AND Svelte files is moved/renamed
                //      -> all Svelte file updates are handled here
                //      -> all TS/JS file updates that consist of only TS/JS import updates are handled by the TS LS
                //      -> all TS/JS file updates that consist of only Svelte import updates are handled here
                //      -> all TS/JS file updates that are mixed are handled here, but also possibly by the TS LS
                //         if the TS plugin doesn't prevent it. This trades risk of broken updates with certainty of missed updates
                edits.forEach((change) => {
                    const isTsOrJsFile =
                        change.textDocument.uri.endsWith('.ts') ||
                        change.textDocument.uri.endsWith('.js');
                    const containsSvelteImportUpdate = change.edits.some((edit) =>
                        edit.newText.endsWith('.svelte')
                    );
                    if (isTsOrJsFile && !containsSvelteImportUpdate) {
                        return;
                    }

                    change.edits.forEach((edit) => {
                        if (
                            isTsOrJsFile &&
                            !TsPlugin.isEnabled() &&
                            !edit.newText.endsWith('.svelte')
                        ) {
                            // TS plugin enabled -> all mixed imports are handled here
                            // TS plugin disabled -> let TS/JS path updates be handled by the TS LS, Svelte here
                            return;
                        }

                        // Renaming a file should only result in edits of existing files
                        workspaceEdit.replace(
                            Uri.parse(change.textDocument.uri),
                            new Range(
                                new Position(edit.range.start.line, edit.range.start.character),
                                new Position(edit.range.end.line, edit.range.end.character)
                            ),
                            edit.newText
                        );
                    });
                });
                workspace.applyEdit(workspaceEdit);
            }
        );
    });
}
Example #23
Source File: DrawioEditorProviderText.ts    From vscode-drawio with GNU General Public License v3.0 4 votes vote down vote up
public async resolveCustomTextEditor(
		document: TextDocument,
		webviewPanel: WebviewPanel,
		token: CancellationToken
	): Promise<void> {
		try {
			const readonlySchemes = new Set(["git", "conflictResolution"]);
			const isReadOnly = readonlySchemes.has(document.uri.scheme);

			const editor =
				await this.drawioEditorService.createDrawioEditorInWebview(
					webviewPanel,
					{
						kind: "text",
						document,
					},
					{ isReadOnly }
				);
			const drawioClient = editor.drawioClient;

			interface NormalizedDocument {
				equals(other: this): boolean;
			}

			function getNormalizedDocument(src: string): NormalizedDocument {
				const result = {
					src,
					equals: (o: any) => o.src === src,
				};
				return result;
			}

			let lastDocument = getNormalizedDocument(document.getText());
			let isThisEditorSaving = false;

			workspace.onDidChangeTextDocument(async (evt) => {
				if (evt.document !== document) {
					return;
				}
				if (isThisEditorSaving) {
					// We don't want to process our own changes.
					return;
				}
				if (evt.contentChanges.length === 0) {
					// Sometimes VS Code reports a document change without a change.
					return;
				}

				const newText = evt.document.getText();
				const newDocument = getNormalizedDocument(newText);
				if (newDocument.equals(lastDocument)) {
					return;
				}
				lastDocument = newDocument;

				await drawioClient.mergeXmlLike(newText);
			});

			drawioClient.onChange.sub(async ({ oldXml, newXml }) => {
				// We format the xml so that it can be easily edited in a second text editor.
				async function getOutput(): Promise<string> {
					if (document.uri.path.endsWith(".svg")) {
						const svg =
							await drawioClient.exportAsSvgWithEmbeddedXml();
						newXml = svg.toString("utf-8");

						// This adds a host to track which files are created by this extension and which by draw.io desktop.
						newXml = newXml.replace(
							/^<svg /,
							() => `<svg host="65bd71144e" `
						);

						return formatter(newXml);
					} else {
						if (newXml.startsWith('<mxfile host="')) {
							newXml = newXml.replace(
								/^<mxfile host="(.*?)"/,
								() => `<mxfile host="65bd71144e"`
							);
						} else {
							// in case there is no host attribute
							newXml = newXml
								.replace(
									/^<mxfile /,
									() => `<mxfile host="65bd71144e"`
								)
								.replace(
									/^<mxfile>/,
									() => `<mxfile host="65bd71144e">`
								);
						}

						return formatter(
							// This normalizes the host
							newXml
						);
					}
				}

				const output = await getOutput();
				const newDocument = getNormalizedDocument(output);
				if (newDocument.equals(lastDocument)) {
					return;
				}
				lastDocument = newDocument;

				const workspaceEdit = new WorkspaceEdit();

				// TODO diff the new document with the old document and only edit the changes.
				workspaceEdit.replace(
					document.uri,
					new Range(0, 0, document.lineCount, 0),
					output
				);

				isThisEditorSaving = true;
				try {
					if (!(await workspace.applyEdit(workspaceEdit))) {
						window.showErrorMessage(
							"Could not apply Draw.io document changes to the underlying document. Try to save again!"
						);
					}
				} finally {
					isThisEditorSaving = false;
				}
			});

			drawioClient.onSave.sub(async () => {
				await document.save();
			});

			drawioClient.onInit.sub(async () => {
				drawioClient.loadXmlLike(document.getText());
			});
		} catch (e) {
			window.showErrorMessage(`Failed to open diagram: ${e}`);
			throw e;
		}
	}