mirror of
https://github.com/friebetill/obsidian-file-diff.git
synced 2026-07-22 07:40:25 +00:00
Add semicolons to prevent difficult to debug bugs
This commit is contained in:
parent
dee8138564
commit
7dfff85dd1
15 changed files with 331 additions and 330 deletions
|
|
@ -23,6 +23,7 @@
|
|||
"class-methods-use-this": "off",
|
||||
"no-use-before-define": "off",
|
||||
"no-warning-comments": 1,
|
||||
"semi": [2, "always"],
|
||||
"@typescript-eslint/no-use-before-define": ["error"],
|
||||
"react/jsx-filename-extension": [
|
||||
"warn",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"semi": false,
|
||||
"semi": true,
|
||||
"singleQuote": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,88 +1,88 @@
|
|||
import { TFile } from 'obsidian'
|
||||
import { Difference } from '../data/difference'
|
||||
import { deleteLines, insertLine, replaceLine } from '../utils/string_utils'
|
||||
import { ActionLineButton } from './action_line_button'
|
||||
import { ActionLineDivider } from './action_line_divider'
|
||||
import { TFile } from 'obsidian';
|
||||
import { Difference } from '../data/difference';
|
||||
import { deleteLines, insertLine, replaceLine } from '../utils/string_utils';
|
||||
import { ActionLineButton } from './action_line_button';
|
||||
import { ActionLineDivider } from './action_line_divider';
|
||||
|
||||
type VoidCallback = () => void
|
||||
type VoidCallback = () => void;
|
||||
|
||||
export class ActionLine {
|
||||
constructor(args: {
|
||||
difference: Difference
|
||||
file1: TFile
|
||||
file2: TFile
|
||||
file1Content: string
|
||||
file2Content: string
|
||||
triggerRebuild: VoidCallback
|
||||
difference: Difference;
|
||||
file1: TFile;
|
||||
file2: TFile;
|
||||
file1Content: string;
|
||||
file2Content: string;
|
||||
triggerRebuild: VoidCallback;
|
||||
}) {
|
||||
this.difference = args.difference
|
||||
this.file1 = args.file1
|
||||
this.file2 = args.file2
|
||||
this.file1Content = args.file1Content
|
||||
this.file2Content = args.file2Content
|
||||
this.triggerRebuild = args.triggerRebuild
|
||||
this.difference = args.difference;
|
||||
this.file1 = args.file1;
|
||||
this.file2 = args.file2;
|
||||
this.file1Content = args.file1Content;
|
||||
this.file2Content = args.file2Content;
|
||||
this.triggerRebuild = args.triggerRebuild;
|
||||
}
|
||||
|
||||
private difference: Difference
|
||||
private difference: Difference;
|
||||
|
||||
private file1: TFile
|
||||
private file1: TFile;
|
||||
|
||||
private file2: TFile
|
||||
private file2: TFile;
|
||||
|
||||
private file1Content: string
|
||||
private file1Content: string;
|
||||
|
||||
private file2Content: string
|
||||
private file2Content: string;
|
||||
|
||||
private triggerRebuild: VoidCallback
|
||||
private triggerRebuild: VoidCallback;
|
||||
|
||||
build(container: HTMLDivElement): void {
|
||||
const actionLine = container.createDiv({
|
||||
cls: 'flex flex-row gap-1 py-0-5',
|
||||
})
|
||||
});
|
||||
|
||||
const hasMinusLines = this.difference.file1Lines.length > 0
|
||||
const hasPlusLines = this.difference.file2Lines.length > 0
|
||||
const hasMinusLines = this.difference.file1Lines.length > 0;
|
||||
const hasPlusLines = this.difference.file2Lines.length > 0;
|
||||
|
||||
if (hasPlusLines && hasMinusLines) {
|
||||
new ActionLineButton({
|
||||
text: 'Accept Top',
|
||||
onClick: (e) => this.acceptTopClick(e, this.difference),
|
||||
}).build(actionLine)
|
||||
ActionLineDivider.build(actionLine)
|
||||
}).build(actionLine);
|
||||
ActionLineDivider.build(actionLine);
|
||||
new ActionLineButton({
|
||||
text: 'Accept Bottom',
|
||||
onClick: (e) => this.acceptBottomClick(e, this.difference),
|
||||
}).build(actionLine)
|
||||
ActionLineDivider.build(actionLine)
|
||||
}).build(actionLine);
|
||||
ActionLineDivider.build(actionLine);
|
||||
new ActionLineButton({
|
||||
text: 'Accept All',
|
||||
onClick: (e) => this.acceptAllClick(e, this.difference),
|
||||
}).build(actionLine)
|
||||
ActionLineDivider.build(actionLine)
|
||||
}).build(actionLine);
|
||||
ActionLineDivider.build(actionLine);
|
||||
new ActionLineButton({
|
||||
text: 'Accept None',
|
||||
onClick: (e) => this.acceptNoneClick(e, this.difference),
|
||||
}).build(actionLine)
|
||||
}).build(actionLine);
|
||||
} else if (hasMinusLines) {
|
||||
new ActionLineButton({
|
||||
text: `Accept from ${this.file1.name}`,
|
||||
onClick: (e) => this.insertFile1Difference(e, this.difference),
|
||||
}).build(actionLine)
|
||||
ActionLineDivider.build(actionLine)
|
||||
}).build(actionLine);
|
||||
ActionLineDivider.build(actionLine);
|
||||
new ActionLineButton({
|
||||
text: 'Discard',
|
||||
onClick: (e) => this.discardFile1Difference(e, this.difference),
|
||||
}).build(actionLine)
|
||||
}).build(actionLine);
|
||||
} else if (hasPlusLines) {
|
||||
new ActionLineButton({
|
||||
text: `Accept from ${this.file2.name}`,
|
||||
onClick: (e) => this.insertFile2Difference(e, this.difference),
|
||||
}).build(actionLine)
|
||||
ActionLineDivider.build(actionLine)
|
||||
}).build(actionLine);
|
||||
ActionLineDivider.build(actionLine);
|
||||
new ActionLineButton({
|
||||
text: 'Discard',
|
||||
onClick: (e) => this.discardFile2Difference(e, this.difference),
|
||||
}).build(actionLine)
|
||||
}).build(actionLine);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -90,154 +90,154 @@ export class ActionLine {
|
|||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const changedLines = difference.file1Lines.join('\n')
|
||||
const changedLines = difference.file1Lines.join('\n');
|
||||
const newContent = replaceLine({
|
||||
fullText: this.file2Content,
|
||||
newLine: changedLines,
|
||||
position: difference.file2Start,
|
||||
linesToReplace: difference.file2Lines.length,
|
||||
})
|
||||
await app.vault.modify(this.file2, newContent)
|
||||
});
|
||||
await app.vault.modify(this.file2, newContent);
|
||||
|
||||
this.triggerRebuild()
|
||||
this.triggerRebuild();
|
||||
}
|
||||
|
||||
private async acceptBottomClick(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const changedLines = difference.file2Lines.join('\n')
|
||||
const changedLines = difference.file2Lines.join('\n');
|
||||
const newContent = replaceLine({
|
||||
fullText: this.file1Content,
|
||||
newLine: changedLines,
|
||||
position: difference.file1Start,
|
||||
linesToReplace: difference.file1Lines.length,
|
||||
})
|
||||
await app.vault.modify(this.file1, newContent)
|
||||
});
|
||||
await app.vault.modify(this.file1, newContent);
|
||||
|
||||
this.triggerRebuild()
|
||||
this.triggerRebuild();
|
||||
}
|
||||
|
||||
private async acceptAllClick(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const changedLines = [
|
||||
...difference.file1Lines,
|
||||
...difference.file2Lines,
|
||||
].join('\n')
|
||||
].join('\n');
|
||||
|
||||
const newFile1Content = replaceLine({
|
||||
fullText: this.file1Content,
|
||||
newLine: changedLines,
|
||||
position: difference.file1Start,
|
||||
linesToReplace: difference.file1Lines.length,
|
||||
})
|
||||
await app.vault.modify(this.file1, newFile1Content)
|
||||
});
|
||||
await app.vault.modify(this.file1, newFile1Content);
|
||||
|
||||
const newFile2Content = replaceLine({
|
||||
fullText: this.file2Content,
|
||||
newLine: changedLines,
|
||||
position: difference.file2Start,
|
||||
linesToReplace: difference.file2Lines.length,
|
||||
})
|
||||
await app.vault.modify(this.file2, newFile2Content)
|
||||
});
|
||||
await app.vault.modify(this.file2, newFile2Content);
|
||||
|
||||
this.triggerRebuild()
|
||||
this.triggerRebuild();
|
||||
}
|
||||
|
||||
private async acceptNoneClick(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const newFile1Content = deleteLines({
|
||||
fullText: this.file1Content,
|
||||
position: difference.file1Start,
|
||||
count: difference.file1Lines.length,
|
||||
})
|
||||
await app.vault.modify(this.file1, newFile1Content)
|
||||
});
|
||||
await app.vault.modify(this.file1, newFile1Content);
|
||||
|
||||
const newFile2Content = deleteLines({
|
||||
fullText: this.file2Content,
|
||||
position: difference.file2Start,
|
||||
count: difference.file2Lines.length,
|
||||
})
|
||||
await app.vault.modify(this.file2, newFile2Content)
|
||||
});
|
||||
await app.vault.modify(this.file2, newFile2Content);
|
||||
|
||||
this.triggerRebuild()
|
||||
this.triggerRebuild();
|
||||
}
|
||||
|
||||
private async insertFile1Difference(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const changedLines = difference.file1Lines.join('\n')
|
||||
const changedLines = difference.file1Lines.join('\n');
|
||||
const newContent = insertLine({
|
||||
fullText: this.file2Content,
|
||||
newLine: changedLines,
|
||||
position: difference.file2Start,
|
||||
})
|
||||
await app.vault.modify(this.file2, newContent)
|
||||
});
|
||||
await app.vault.modify(this.file2, newContent);
|
||||
|
||||
this.triggerRebuild()
|
||||
this.triggerRebuild();
|
||||
}
|
||||
|
||||
private async insertFile2Difference(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const changedLines = difference.file2Lines.join('\n')
|
||||
const changedLines = difference.file2Lines.join('\n');
|
||||
const newContent = insertLine({
|
||||
fullText: this.file1Content,
|
||||
newLine: changedLines,
|
||||
position: difference.file1Start,
|
||||
})
|
||||
await app.vault.modify(this.file1, newContent)
|
||||
});
|
||||
await app.vault.modify(this.file1, newContent);
|
||||
|
||||
this.triggerRebuild()
|
||||
this.triggerRebuild();
|
||||
}
|
||||
|
||||
async discardFile1Difference(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const newContent = deleteLines({
|
||||
fullText: this.file1Content,
|
||||
position: difference.file1Start,
|
||||
count: difference.file1Lines.length,
|
||||
})
|
||||
await app.vault.modify(this.file1, newContent)
|
||||
});
|
||||
await app.vault.modify(this.file1, newContent);
|
||||
|
||||
this.triggerRebuild()
|
||||
this.triggerRebuild();
|
||||
}
|
||||
|
||||
async discardFile2Difference(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const newContent = deleteLines({
|
||||
fullText: this.file2Content,
|
||||
position: difference.file2Start,
|
||||
count: difference.file2Lines.length,
|
||||
})
|
||||
await app.vault.modify(this.file2, newContent)
|
||||
});
|
||||
await app.vault.modify(this.file2, newContent);
|
||||
|
||||
this.triggerRebuild()
|
||||
this.triggerRebuild();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
export class ActionLineButton {
|
||||
constructor(args: { text: string; onClick: (e: MouseEvent) => void }) {
|
||||
this.text = args.text
|
||||
this.onClick = args.onClick
|
||||
this.text = args.text;
|
||||
this.onClick = args.onClick;
|
||||
}
|
||||
|
||||
public text: string
|
||||
public text: string;
|
||||
|
||||
public onClick: (e: MouseEvent) => void
|
||||
public onClick: (e: MouseEvent) => void;
|
||||
|
||||
build(actionLine: HTMLDivElement): void {
|
||||
actionLine
|
||||
|
|
@ -14,6 +14,6 @@ export class ActionLineButton {
|
|||
text: this.text,
|
||||
cls: 'no-decoration text-xxs file-diff__action-line',
|
||||
})
|
||||
.onClickEvent(this.onClick)
|
||||
.onClickEvent(this.onClick);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@ export class ActionLineDivider {
|
|||
actionLine.createEl('span', {
|
||||
text: '|',
|
||||
cls: 'text-xxs file-diff__action-line',
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,94 +1,94 @@
|
|||
import { structuredPatch } from 'diff'
|
||||
import { ItemView, TFile, WorkspaceLeaf } from 'obsidian'
|
||||
import { Difference } from '../data/difference'
|
||||
import { FileDifferences } from '../data/file_differences'
|
||||
import { preventEmptyString } from '../utils/string_utils'
|
||||
import { ActionLine } from './action_line'
|
||||
import { DeleteFileModal } from './modals/delete_file_modal'
|
||||
import { structuredPatch } from 'diff';
|
||||
import { ItemView, TFile, WorkspaceLeaf } from 'obsidian';
|
||||
import { Difference } from '../data/difference';
|
||||
import { FileDifferences } from '../data/file_differences';
|
||||
import { preventEmptyString } from '../utils/string_utils';
|
||||
import { ActionLine } from './action_line';
|
||||
import { DeleteFileModal } from './modals/delete_file_modal';
|
||||
|
||||
export const VIEW_TYPE_DIFFERENCES = 'differences-view'
|
||||
export const VIEW_TYPE_DIFFERENCES = 'differences-view';
|
||||
|
||||
export class DifferencesView extends ItemView {
|
||||
constructor(args: {
|
||||
leaf: WorkspaceLeaf
|
||||
file1: TFile
|
||||
file2: TFile
|
||||
showMergeOption: boolean
|
||||
leaf: WorkspaceLeaf;
|
||||
file1: TFile;
|
||||
file2: TFile;
|
||||
showMergeOption: boolean;
|
||||
}) {
|
||||
super(args.leaf)
|
||||
this.file1 = args.file1
|
||||
this.file2 = args.file2
|
||||
this.showMergeOption = args.showMergeOption
|
||||
super(args.leaf);
|
||||
this.file1 = args.file1;
|
||||
this.file2 = args.file2;
|
||||
this.showMergeOption = args.showMergeOption;
|
||||
}
|
||||
|
||||
private file1: TFile
|
||||
private file1: TFile;
|
||||
|
||||
private file2: TFile
|
||||
private file2: TFile;
|
||||
|
||||
private file1Content: string
|
||||
private file1Content: string;
|
||||
|
||||
private file2Content: string
|
||||
private file2Content: string;
|
||||
|
||||
private showMergeOption: boolean
|
||||
private showMergeOption: boolean;
|
||||
|
||||
private fileDifferences: FileDifferences
|
||||
private fileDifferences: FileDifferences;
|
||||
|
||||
private file1Lines: string[]
|
||||
private file1Lines: string[];
|
||||
|
||||
private file2Lines: string[]
|
||||
private file2Lines: string[];
|
||||
|
||||
private lineCount: number
|
||||
private lineCount: number;
|
||||
|
||||
private wasDeleteModalShown = false
|
||||
private wasDeleteModalShown = false;
|
||||
|
||||
getViewType(): string {
|
||||
return VIEW_TYPE_DIFFERENCES
|
||||
return VIEW_TYPE_DIFFERENCES;
|
||||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return `Difference between ${this.file1.name} and ${this.file2.name}`
|
||||
return `Difference between ${this.file1.name} and ${this.file2.name}`;
|
||||
}
|
||||
|
||||
async onload(): Promise<void> {
|
||||
this.registerEvent(
|
||||
this.app.vault.on('modify', async (file) => {
|
||||
if (file === this.file1 || file === this.file2) {
|
||||
await this.updateState()
|
||||
this.build()
|
||||
await this.updateState();
|
||||
this.build();
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async onOpen(): Promise<void> {
|
||||
await this.updateState()
|
||||
this.build()
|
||||
await this.updateState();
|
||||
this.build();
|
||||
}
|
||||
|
||||
private async updateState(): Promise<void> {
|
||||
this.file1Content = await this.app.vault.read(this.file1)
|
||||
this.file2Content = await this.app.vault.read(this.file2)
|
||||
this.file1Content = await this.app.vault.read(this.file1);
|
||||
this.file2Content = await this.app.vault.read(this.file2);
|
||||
|
||||
this.file1Lines = this.file1Content
|
||||
// Add trailing new line as this removes edge cases
|
||||
.concat('\n')
|
||||
.split('\n')
|
||||
// Streamline empty lines at the end as this remove edge cases
|
||||
.map((line) => line.trimEnd())
|
||||
.map((line) => line.trimEnd());
|
||||
this.file2Lines = this.file2Content
|
||||
// Add trailing new line as this removes edge cases
|
||||
.concat('\n')
|
||||
.split('\n')
|
||||
// Streamline empty lines at the end as this remove edge cases
|
||||
.map((line) => line.trimEnd())
|
||||
.map((line) => line.trimEnd());
|
||||
|
||||
const parsedDiff = structuredPatch(
|
||||
this.file1.path,
|
||||
this.file2.path,
|
||||
this.file1Lines.join('\n'),
|
||||
this.file2Lines.join('\n')
|
||||
)
|
||||
this.fileDifferences = FileDifferences.fromParsedDiff(parsedDiff)
|
||||
);
|
||||
this.fileDifferences = FileDifferences.fromParsedDiff(parsedDiff);
|
||||
|
||||
this.lineCount = Math.max(
|
||||
this.file1Lines.length -
|
||||
|
|
@ -101,58 +101,58 @@ export class DifferencesView extends ItemView {
|
|||
this.fileDifferences.differences.filter(
|
||||
(d) => d.file2Lines.length > 0
|
||||
).length
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private build(): void {
|
||||
this.contentEl.empty()
|
||||
this.contentEl.empty();
|
||||
|
||||
const container = this.contentEl.createDiv({
|
||||
cls: 'file-diff__container',
|
||||
})
|
||||
});
|
||||
|
||||
this.buildLines(container)
|
||||
this.buildLines(container);
|
||||
|
||||
this.scrollToFirstDifference()
|
||||
this.scrollToFirstDifference();
|
||||
if (
|
||||
this.fileDifferences.differences.length === 0 &&
|
||||
this.showMergeOption &&
|
||||
!this.wasDeleteModalShown
|
||||
) {
|
||||
this.wasDeleteModalShown = true
|
||||
this.showDeleteModal()
|
||||
this.wasDeleteModalShown = true;
|
||||
this.showDeleteModal();
|
||||
}
|
||||
}
|
||||
|
||||
private buildLines(container: HTMLDivElement): void {
|
||||
let lineCount1 = 0
|
||||
let lineCount2 = 0
|
||||
let lineCount1 = 0;
|
||||
let lineCount2 = 0;
|
||||
while (lineCount1 <= this.lineCount || lineCount2 <= this.lineCount) {
|
||||
const difference = this.fileDifferences.differences.find(
|
||||
// eslint-disable-next-line no-loop-func
|
||||
(d) =>
|
||||
d.file1Start === lineCount1 && d.file2Start === lineCount2
|
||||
)
|
||||
);
|
||||
|
||||
if (difference != null) {
|
||||
const differenceContainer = container.createDiv({
|
||||
cls: 'difference',
|
||||
})
|
||||
this.buildDifferenceVisualizer(differenceContainer, difference)
|
||||
lineCount1 += difference.file1Lines.length
|
||||
lineCount2 += difference.file2Lines.length
|
||||
});
|
||||
this.buildDifferenceVisualizer(differenceContainer, difference);
|
||||
lineCount1 += difference.file1Lines.length;
|
||||
lineCount2 += difference.file2Lines.length;
|
||||
} else {
|
||||
const line =
|
||||
lineCount1 <= lineCount2
|
||||
? this.file1Lines[lineCount1]
|
||||
: this.file2Lines[lineCount2]
|
||||
: this.file2Lines[lineCount2];
|
||||
container.createDiv({
|
||||
// Necessary to give the line a height when it's empty.
|
||||
text: preventEmptyString(line),
|
||||
cls: 'file-diff__line',
|
||||
})
|
||||
lineCount1 += 1
|
||||
lineCount2 += 1
|
||||
});
|
||||
lineCount1 += 1;
|
||||
lineCount2 += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -162,9 +162,9 @@ export class DifferencesView extends ItemView {
|
|||
difference: Difference
|
||||
): void {
|
||||
const triggerRebuild = async (): Promise<void> => {
|
||||
await this.updateState()
|
||||
this.build()
|
||||
}
|
||||
await this.updateState();
|
||||
this.build();
|
||||
};
|
||||
|
||||
if (this.showMergeOption) {
|
||||
new ActionLine({
|
||||
|
|
@ -174,55 +174,55 @@ export class DifferencesView extends ItemView {
|
|||
file1Content: this.file1Content,
|
||||
file2Content: this.file2Content,
|
||||
triggerRebuild,
|
||||
}).build(container)
|
||||
}).build(container);
|
||||
}
|
||||
|
||||
for (let i = 0; i < difference.file1Lines.length; i += 1) {
|
||||
const line = difference.file1Lines[i]
|
||||
const line = difference.file1Lines[i];
|
||||
container.createDiv({
|
||||
// Necessary to give the line a height when it's empty.
|
||||
text: preventEmptyString(line),
|
||||
cls: 'file-diff__line file-diff__top-line__bg',
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < difference.file2Lines.length; i += 1) {
|
||||
const line = difference.file2Lines[i]
|
||||
const line = difference.file2Lines[i];
|
||||
container.createDiv({
|
||||
// Necessary to give the line a height when it's empty.
|
||||
text: preventEmptyString(line),
|
||||
cls: 'file-diff__line file-diff__bottom-line__bg',
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private scrollToFirstDifference(): void {
|
||||
if (this.fileDifferences.differences.length === 0) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const containerRect = this.contentEl
|
||||
.getElementsByClassName('file-diff__container')[0]
|
||||
.getBoundingClientRect()
|
||||
.getBoundingClientRect();
|
||||
const elementRect = this.contentEl
|
||||
.getElementsByClassName('difference')[0]
|
||||
.getBoundingClientRect()
|
||||
.getBoundingClientRect();
|
||||
this.contentEl.scrollTo({
|
||||
top: elementRect.top - containerRect.top - 100,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async showDeleteModal(): Promise<void> {
|
||||
// Wait a moment to avoid appearing overly aggressive with the modal
|
||||
await sleep(200)
|
||||
await sleep(200);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
new DeleteFileModal({
|
||||
file1: this.file1,
|
||||
file2: this.file2,
|
||||
onDone: (e) => (e ? reject(e) : resolve()),
|
||||
}).open()
|
||||
})
|
||||
}).open();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +1,64 @@
|
|||
import { Modal, TFile } from 'obsidian'
|
||||
import { Modal, TFile } from 'obsidian';
|
||||
|
||||
export class DeleteFileModal extends Modal {
|
||||
constructor(args: {
|
||||
file1: TFile
|
||||
file2: TFile
|
||||
onDone: (error: Error | null) => void
|
||||
file1: TFile;
|
||||
file2: TFile;
|
||||
onDone: (error: Error | null) => void;
|
||||
}) {
|
||||
super(app)
|
||||
this.file1 = args.file1
|
||||
this.file2 = args.file2
|
||||
this.onDone = args.onDone
|
||||
super(app);
|
||||
this.file1 = args.file1;
|
||||
this.file2 = args.file2;
|
||||
this.onDone = args.onDone;
|
||||
}
|
||||
|
||||
private readonly file1: TFile
|
||||
private readonly file1: TFile;
|
||||
|
||||
private readonly file2: TFile
|
||||
private readonly file2: TFile;
|
||||
|
||||
private readonly onDone: (error: Error | null) => void
|
||||
private readonly onDone: (error: Error | null) => void;
|
||||
|
||||
onOpen(): void {
|
||||
// Counteract gravity pull by moving box up for balanced composition
|
||||
this.modalEl.addClass('mb-20')
|
||||
this.modalEl.addClass('mb-20');
|
||||
|
||||
this.contentEl.createEl('h3', {
|
||||
text: `Delete "${this.file2.name}"?`,
|
||||
})
|
||||
});
|
||||
this.contentEl.createEl('p', {
|
||||
text:
|
||||
`The contents of "${this.file1.name}" and ` +
|
||||
`"${this.file2.name}" are identical. Would you like to ` +
|
||||
`delete the duplicate file? Please note that this action is ` +
|
||||
`irreversible.`,
|
||||
})
|
||||
});
|
||||
|
||||
const buttonContainer = this.contentEl.createDiv(
|
||||
'file-diff__button-container'
|
||||
)
|
||||
);
|
||||
|
||||
const deleteButton = buttonContainer.createEl('button', {
|
||||
text: 'Delete',
|
||||
cls: 'mod-warning mr-2',
|
||||
})
|
||||
});
|
||||
const cancelButton = buttonContainer.createEl('button', {
|
||||
text: 'Cancel',
|
||||
})
|
||||
});
|
||||
|
||||
deleteButton.addEventListener('click', () => this.handleDeleteClick())
|
||||
cancelButton.addEventListener('click', () => this.close())
|
||||
deleteButton.addEventListener('click', () => this.handleDeleteClick());
|
||||
cancelButton.addEventListener('click', () => this.close());
|
||||
}
|
||||
|
||||
handleDeleteClick(): void {
|
||||
this.app.vault.delete(this.file2)
|
||||
this.app.vault.delete(this.file2);
|
||||
|
||||
this.close()
|
||||
this.close();
|
||||
|
||||
const leaf = this.app.workspace.getMostRecentLeaf()
|
||||
const leaf = this.app.workspace.getMostRecentLeaf();
|
||||
if (leaf != null) {
|
||||
leaf.openFile(this.file1)
|
||||
leaf.openFile(this.file1);
|
||||
}
|
||||
|
||||
this.onDone(null)
|
||||
this.onDone(null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,41 @@
|
|||
import { Modal } from 'obsidian'
|
||||
import { Modal } from 'obsidian';
|
||||
|
||||
export class RiskyActionModal extends Modal {
|
||||
constructor(args: { onAccept: (error: Error | null) => void }) {
|
||||
super(app)
|
||||
this.onAccept = args.onAccept
|
||||
super(app);
|
||||
this.onAccept = args.onAccept;
|
||||
}
|
||||
|
||||
private readonly onAccept: (error: Error | null) => void
|
||||
private readonly onAccept: (error: Error | null) => void;
|
||||
|
||||
onOpen(): void {
|
||||
// Counteract gravity pull by moving box up for balanced composition
|
||||
this.modalEl.addClass('mb-20')
|
||||
this.modalEl.addClass('mb-20');
|
||||
|
||||
this.contentEl.createEl('h3', { text: `Do you accept the risk?` })
|
||||
this.contentEl.createEl('h3', { text: `Do you accept the risk?` });
|
||||
this.contentEl.createEl('p', {
|
||||
text:
|
||||
`The merging options alter the files irreversibly. ` +
|
||||
`Proceed with caution and only if you are aware and ` +
|
||||
`accepting of the associated risks.`,
|
||||
})
|
||||
});
|
||||
|
||||
const buttonContainer = this.contentEl.createDiv(
|
||||
'file-diff__button-container'
|
||||
)
|
||||
);
|
||||
|
||||
const deleteButton = buttonContainer.createEl('button', {
|
||||
text: 'Accept Risk',
|
||||
cls: 'mod-warning mr-2',
|
||||
})
|
||||
});
|
||||
const cancelButton = buttonContainer.createEl('button', {
|
||||
text: 'Cancel',
|
||||
})
|
||||
});
|
||||
|
||||
cancelButton.addEventListener('click', () => this.close())
|
||||
cancelButton.addEventListener('click', () => this.close());
|
||||
deleteButton.addEventListener('click', () => {
|
||||
this.close()
|
||||
this.onAccept(null)
|
||||
})
|
||||
this.close();
|
||||
this.onAccept(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
import { SuggestModal, TFile } from 'obsidian'
|
||||
import { SuggestModal, TFile } from 'obsidian';
|
||||
|
||||
export class SelectFileModal extends SuggestModal<TFile> {
|
||||
constructor(args: {
|
||||
selectableFiles: TFile[]
|
||||
onChoose: (error: Error | null, result?: TFile) => void
|
||||
selectableFiles: TFile[];
|
||||
onChoose: (error: Error | null, result?: TFile) => void;
|
||||
}) {
|
||||
super(app)
|
||||
this.selectableFiles = args.selectableFiles
|
||||
this.onChoose = args.onChoose
|
||||
super(app);
|
||||
this.selectableFiles = args.selectableFiles;
|
||||
this.onChoose = args.onChoose;
|
||||
}
|
||||
|
||||
private readonly selectableFiles: TFile[]
|
||||
private readonly selectableFiles: TFile[];
|
||||
|
||||
private readonly onChoose: (error: Error | null, result?: TFile) => void
|
||||
private readonly onChoose: (error: Error | null, result?: TFile) => void;
|
||||
|
||||
getSuggestions(query: string): TFile[] {
|
||||
return this.selectableFiles
|
||||
.filter((file) => {
|
||||
const searchQuery = query?.toLowerCase()
|
||||
return file.name?.toLowerCase().includes(searchQuery)
|
||||
const searchQuery = query?.toLowerCase();
|
||||
return file.name?.toLowerCase().includes(searchQuery);
|
||||
})
|
||||
.sort((a, b) => (a.stat.mtime < b.stat.mtime ? 1 : -1))
|
||||
.sort((a, b) => (a.stat.mtime < b.stat.mtime ? 1 : -1));
|
||||
}
|
||||
|
||||
renderSuggestion(file: TFile, el: HTMLElement): void {
|
||||
el.createEl('div', { text: file.name })
|
||||
el.createEl('div', { text: file.name });
|
||||
}
|
||||
|
||||
onChooseSuggestion(file: TFile): void {
|
||||
this.onChoose(null, file)
|
||||
this.onChoose(null, file);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
export class Difference {
|
||||
constructor(args: {
|
||||
file1Start: number
|
||||
file2Start: number
|
||||
file1Lines: string[]
|
||||
file2Lines: string[]
|
||||
file1Start: number;
|
||||
file2Start: number;
|
||||
file1Lines: string[];
|
||||
file2Lines: string[];
|
||||
}) {
|
||||
this.file1Start = args.file1Start
|
||||
this.file2Start = args.file2Start
|
||||
this.file1Lines = args.file1Lines
|
||||
this.file2Lines = args.file2Lines
|
||||
this.file1Start = args.file1Start;
|
||||
this.file2Start = args.file2Start;
|
||||
this.file1Lines = args.file1Lines;
|
||||
this.file2Lines = args.file2Lines;
|
||||
}
|
||||
|
||||
public readonly file1Start: number
|
||||
public readonly file1Start: number;
|
||||
|
||||
public readonly file2Start: number
|
||||
public readonly file2Start: number;
|
||||
|
||||
public readonly file1Lines: string[]
|
||||
public readonly file1Lines: string[];
|
||||
|
||||
public readonly file2Lines: string[]
|
||||
public readonly file2Lines: string[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { ParsedDiff } from 'diff'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { FileDifferences } from './file_differences'
|
||||
import { ParsedDiff } from 'diff';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { FileDifferences } from './file_differences';
|
||||
|
||||
describe('FileDifferences.fromParsedDiff', () => {
|
||||
it('should work with files containing one line', () => {
|
||||
|
|
@ -10,13 +10,13 @@ describe('FileDifferences.fromParsedDiff', () => {
|
|||
{"oldStart": 1, "oldLines": 1, "newStart": 1, "newLines": 1,
|
||||
"lines": ["-a","+b"]
|
||||
}]}
|
||||
`) as ParsedDiff
|
||||
`) as ParsedDiff;
|
||||
|
||||
const fileDifferences = FileDifferences.fromParsedDiff(test)
|
||||
const fileDifferences = FileDifferences.fromParsedDiff(test);
|
||||
|
||||
expect(JSON.stringify(fileDifferences)).toBe(
|
||||
'{"file1Name":"1","file2Name":"2","differences":' +
|
||||
'[{"start":0,"lines":["-a","+b"]}]}'
|
||||
)
|
||||
})
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
import { ParsedDiff } from 'diff'
|
||||
import { Difference } from './difference'
|
||||
import { ParsedDiff } from 'diff';
|
||||
import { Difference } from './difference';
|
||||
|
||||
/**
|
||||
* A class that contains the differences between two files.
|
||||
*/
|
||||
export class FileDifferences {
|
||||
private constructor(args: {
|
||||
file1Name?: string
|
||||
file2Name?: string
|
||||
differences: Difference[]
|
||||
file1Name?: string;
|
||||
file2Name?: string;
|
||||
differences: Difference[];
|
||||
}) {
|
||||
this.file1Name = args.file1Name
|
||||
this.file2Name = args.file2Name
|
||||
this.differences = args.differences
|
||||
this.file1Name = args.file1Name;
|
||||
this.file2Name = args.file2Name;
|
||||
this.differences = args.differences;
|
||||
}
|
||||
|
||||
public readonly file1Name?: string
|
||||
public readonly file1Name?: string;
|
||||
|
||||
public readonly file2Name?: string
|
||||
public readonly file2Name?: string;
|
||||
|
||||
public readonly differences: Difference[]
|
||||
public readonly differences: Difference[];
|
||||
|
||||
/**
|
||||
* Returns a FileDifferences object from the given ParsedDiff instance.
|
||||
|
|
@ -39,36 +39,36 @@ export class FileDifferences {
|
|||
* chose the latter option as it seemed simpler.
|
||||
*/
|
||||
static fromParsedDiff(parsedDiff: ParsedDiff): FileDifferences {
|
||||
const differences: Difference[] = []
|
||||
const differences: Difference[] = [];
|
||||
|
||||
parsedDiff.hunks.forEach((hunk) => {
|
||||
let line1Count = 0
|
||||
let line2Count = 0
|
||||
let line1Count = 0;
|
||||
let line2Count = 0;
|
||||
for (let i = 0; i < hunk.lines.length; i += 1) {
|
||||
const line = hunk.lines[i]
|
||||
const line = hunk.lines[i];
|
||||
|
||||
if (line.startsWith('+') || line.startsWith('-')) {
|
||||
const start = i
|
||||
const start = i;
|
||||
|
||||
// Find the end of the contiguous lines
|
||||
let end = start
|
||||
let end = start;
|
||||
while (
|
||||
end < hunk.lines.length - 1 &&
|
||||
(hunk.lines[end + 1].startsWith('+') ||
|
||||
hunk.lines[end + 1].startsWith('-'))
|
||||
) {
|
||||
end += 1
|
||||
end += 1;
|
||||
}
|
||||
|
||||
// Add the contiguous lines to the differences
|
||||
const file1Lines = hunk.lines
|
||||
.slice(start, end + 1)
|
||||
.filter((l) => l.startsWith('-'))
|
||||
.map((l) => l.slice(1))
|
||||
.map((l) => l.slice(1));
|
||||
const file2Lines = hunk.lines
|
||||
.slice(start, end + 1)
|
||||
.filter((l) => l.startsWith('+'))
|
||||
.map((l) => l.slice(1))
|
||||
.map((l) => l.slice(1));
|
||||
differences.push(
|
||||
new Difference({
|
||||
file1Start: hunk.oldStart + start - line2Count - 1,
|
||||
|
|
@ -76,19 +76,19 @@ export class FileDifferences {
|
|||
file1Lines,
|
||||
file2Lines,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
line1Count += file1Lines.length
|
||||
line2Count += file2Lines.length
|
||||
i += end - start
|
||||
line1Count += file1Lines.length;
|
||||
line2Count += file2Lines.length;
|
||||
i += end - start;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return new this({
|
||||
file1Name: parsedDiff.oldFileName,
|
||||
file2Name: parsedDiff.newFileName,
|
||||
differences,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
66
src/main.ts
66
src/main.ts
|
|
@ -1,11 +1,11 @@
|
|||
import { Plugin, TFile } from 'obsidian'
|
||||
import { Plugin, TFile } from 'obsidian';
|
||||
|
||||
import { DifferencesView } from './components/differences_view'
|
||||
import { RiskyActionModal } from './components/modals/risky_action_modal'
|
||||
import { SelectFileModal } from './components/modals/select_file_modal'
|
||||
import { DifferencesView } from './components/differences_view';
|
||||
import { RiskyActionModal } from './components/modals/risky_action_modal';
|
||||
import { SelectFileModal } from './components/modals/select_file_modal';
|
||||
|
||||
export default class FileDiffPlugin extends Plugin {
|
||||
fileDiffMergeWarningKey = 'file-diff-merge-warning'
|
||||
fileDiffMergeWarningKey = 'file-diff-merge-warning';
|
||||
|
||||
onload(): void {
|
||||
this.addCommand({
|
||||
|
|
@ -13,19 +13,19 @@ export default class FileDiffPlugin extends Plugin {
|
|||
name: 'Compare',
|
||||
editorCallback: async () => {
|
||||
// Get current active file
|
||||
const activeFile = this.app.workspace.getActiveFile()
|
||||
const activeFile = this.app.workspace.getActiveFile();
|
||||
if (activeFile == null) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// Get file to compare
|
||||
const compareFile = await this.getFileToCompare(activeFile)
|
||||
const compareFile = await this.getFileToCompare(activeFile);
|
||||
if (compareFile == null) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// Open differences view
|
||||
const workspaceLeaf = this.app.workspace.getLeaf()
|
||||
const workspaceLeaf = this.app.workspace.getLeaf();
|
||||
await workspaceLeaf.open(
|
||||
new DifferencesView({
|
||||
leaf: workspaceLeaf,
|
||||
|
|
@ -33,9 +33,9 @@ export default class FileDiffPlugin extends Plugin {
|
|||
file2: compareFile,
|
||||
showMergeOption: false,
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: 'compare-and-merge',
|
||||
|
|
@ -43,26 +43,26 @@ export default class FileDiffPlugin extends Plugin {
|
|||
editorCallback: async () => {
|
||||
// Show warning when this option is selected for the first time
|
||||
if (!localStorage.getItem(this.fileDiffMergeWarningKey)) {
|
||||
await this.showRiskyActionModal()
|
||||
await this.showRiskyActionModal();
|
||||
if (!localStorage.getItem(this.fileDiffMergeWarningKey)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get current active file
|
||||
const activeFile = this.app.workspace.getActiveFile()
|
||||
const activeFile = this.app.workspace.getActiveFile();
|
||||
if (activeFile == null) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// Get file to compare
|
||||
const compareFile = await this.getFileToCompare(activeFile)
|
||||
const compareFile = await this.getFileToCompare(activeFile);
|
||||
if (compareFile == null) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// Open differences view
|
||||
const workspaceLeaf = this.app.workspace.getMostRecentLeaf()
|
||||
const workspaceLeaf = this.app.workspace.getMostRecentLeaf();
|
||||
if (workspaceLeaf != null) {
|
||||
await workspaceLeaf.open(
|
||||
new DifferencesView({
|
||||
|
|
@ -71,27 +71,27 @@ export default class FileDiffPlugin extends Plugin {
|
|||
file2: compareFile,
|
||||
showMergeOption: true,
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
getFileToCompare(activeFile: TFile): Promise<TFile | undefined> {
|
||||
const selectableFiles = this.app.vault.getFiles()
|
||||
selectableFiles.remove(activeFile)
|
||||
return this.showSelectOtherFileModal({ selectableFiles })
|
||||
const selectableFiles = this.app.vault.getFiles();
|
||||
selectableFiles.remove(activeFile);
|
||||
return this.showSelectOtherFileModal({ selectableFiles });
|
||||
}
|
||||
|
||||
showSelectOtherFileModal(args: {
|
||||
selectableFiles: TFile[]
|
||||
selectableFiles: TFile[];
|
||||
}): Promise<TFile | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
new SelectFileModal({
|
||||
selectableFiles: args.selectableFiles,
|
||||
onChoose: (e, f) => (e ? reject(e) : resolve(f)),
|
||||
}).open()
|
||||
})
|
||||
}).open();
|
||||
});
|
||||
}
|
||||
|
||||
showRiskyActionModal(): Promise<void> {
|
||||
|
|
@ -99,19 +99,19 @@ export default class FileDiffPlugin extends Plugin {
|
|||
new RiskyActionModal({
|
||||
onAccept: async (e: Error | null) => {
|
||||
if (e) {
|
||||
reject(e)
|
||||
reject(e);
|
||||
} else {
|
||||
localStorage.setItem(
|
||||
this.fileDiffMergeWarningKey,
|
||||
'true'
|
||||
)
|
||||
);
|
||||
// Wait for the set item dispatch event to be processed
|
||||
await sleep(50)
|
||||
await sleep(50);
|
||||
|
||||
resolve()
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
}).open()
|
||||
})
|
||||
}).open();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,49 @@
|
|||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { replaceLine } from './string_utils'
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { replaceLine } from './string_utils';
|
||||
|
||||
describe('replaceLine', () => {
|
||||
it('should replace fullText with newLine at given position', () => {
|
||||
const fullText = 'line1\nline2\nline3'
|
||||
const fullText = 'line1\nline2\nline3';
|
||||
const newFullText = replaceLine({
|
||||
fullText,
|
||||
newLine: 'newLine2',
|
||||
position: 1,
|
||||
linesToReplace: 1,
|
||||
})
|
||||
expect(newFullText).toBe('line1\nnewLine2\nline3')
|
||||
})
|
||||
});
|
||||
expect(newFullText).toBe('line1\nnewLine2\nline3');
|
||||
});
|
||||
|
||||
it('should replacing with mutliple lines correctly', () => {
|
||||
const fullText = 'line1\nline2\nline3'
|
||||
const fullText = 'line1\nline2\nline3';
|
||||
const newFullText = replaceLine({
|
||||
fullText,
|
||||
newLine: 'newLine2\nnewLine3',
|
||||
position: 1,
|
||||
linesToReplace: 1,
|
||||
})
|
||||
expect(newFullText).toBe('line1\nnewLine2\nnewLine3\nline3')
|
||||
})
|
||||
});
|
||||
expect(newFullText).toBe('line1\nnewLine2\nnewLine3\nline3');
|
||||
});
|
||||
|
||||
it('should remove line when new line is empty', () => {
|
||||
const fullText = 'line1\nline2\nline3'
|
||||
const fullText = 'line1\nline2\nline3';
|
||||
const newFullText = replaceLine({
|
||||
fullText,
|
||||
newLine: '',
|
||||
position: 1,
|
||||
linesToReplace: 1,
|
||||
})
|
||||
expect(newFullText).toBe('line1\nline3')
|
||||
})
|
||||
});
|
||||
expect(newFullText).toBe('line1\nline3');
|
||||
});
|
||||
|
||||
it('should replace two lines', () => {
|
||||
const fullText = 'line1\nline2\nline3\nline4'
|
||||
const fullText = 'line1\nline2\nline3\nline4';
|
||||
const newFullText = replaceLine({
|
||||
fullText,
|
||||
newLine: 'line5',
|
||||
position: 1,
|
||||
linesToReplace: 2,
|
||||
})
|
||||
expect(newFullText).toBe('line1\nline5\nline4')
|
||||
})
|
||||
})
|
||||
});
|
||||
expect(newFullText).toBe('line1\nline5\nline4');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,42 +1,42 @@
|
|||
/** Method to insert a line in a string */
|
||||
export function insertLine(args: {
|
||||
fullText: string
|
||||
newLine: string
|
||||
position: number
|
||||
fullText: string;
|
||||
newLine: string;
|
||||
position: number;
|
||||
}): string {
|
||||
const lines = args.fullText.split('\n')
|
||||
lines.splice(args.position, 0, args.newLine)
|
||||
return lines.join('\n')
|
||||
const lines = args.fullText.split('\n');
|
||||
lines.splice(args.position, 0, args.newLine);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/** Method to replace a line in a string */
|
||||
export function replaceLine(args: {
|
||||
fullText: string
|
||||
newLine: string
|
||||
position: number
|
||||
linesToReplace: number
|
||||
fullText: string;
|
||||
newLine: string;
|
||||
position: number;
|
||||
linesToReplace: number;
|
||||
}): string {
|
||||
const lines = args.fullText.split('\n')
|
||||
const lines = args.fullText.split('\n');
|
||||
if (args.newLine === '') {
|
||||
lines.splice(args.position, args.linesToReplace)
|
||||
lines.splice(args.position, args.linesToReplace);
|
||||
} else {
|
||||
lines.splice(args.position, args.linesToReplace, args.newLine)
|
||||
lines.splice(args.position, args.linesToReplace, args.newLine);
|
||||
}
|
||||
return lines.join('\n')
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/** Method to delete a line in a string */
|
||||
export function deleteLines(args: {
|
||||
fullText: string
|
||||
position: number
|
||||
count: number
|
||||
fullText: string;
|
||||
position: number;
|
||||
count: number;
|
||||
}): string {
|
||||
const lines = args.fullText.split('\n')
|
||||
lines.splice(args.position, args.count)
|
||||
return lines.join('\n')
|
||||
const lines = args.fullText.split('\n');
|
||||
lines.splice(args.position, args.count);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/** Returns an invisible character when the text is empty. */
|
||||
export function preventEmptyString(text: string): string {
|
||||
return text !== '' ? text : ''
|
||||
return text !== '' ? text : '';
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue