mirror of
https://github.com/friebetill/obsidian-file-diff.git
synced 2026-07-22 07:40:25 +00:00
Simplify structure
This commit is contained in:
parent
1f55799a1b
commit
e4fbea864f
3 changed files with 216 additions and 209 deletions
168
src/action_line.ts
Normal file
168
src/action_line.ts
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import { TFile } from "obsidian";
|
||||
import { Difference } from "./data/difference";
|
||||
import { replaceLine } from "./utils/string_utils";
|
||||
|
||||
// typedef for a voidcallback
|
||||
type VoidCallback = () => void;
|
||||
|
||||
export class ActionLine {
|
||||
constructor(
|
||||
private difference: Difference,
|
||||
private file1: TFile,
|
||||
private file2: TFile,
|
||||
private triggerRebuild: VoidCallback
|
||||
) {}
|
||||
|
||||
build(container: HTMLDivElement) {
|
||||
const actionLine = container.createDiv({ cls: "flex-row gap-2 py-2" });
|
||||
|
||||
const hasPlusLines = this.difference.lines.some((l) =>
|
||||
l.startsWith("+")
|
||||
);
|
||||
const hasMinusLines = this.difference.lines.some((l) =>
|
||||
l.startsWith("-")
|
||||
);
|
||||
|
||||
if (hasPlusLines && hasMinusLines) {
|
||||
this.buildActionLineButton(actionLine, "Accept Top", (e) =>
|
||||
this.handleAcceptTopClick(e, this.difference)
|
||||
);
|
||||
this.buildActionLineDivider(actionLine);
|
||||
this.buildActionLineButton(actionLine, "Accept Bottom", (e) =>
|
||||
this.handleAcceptBottomClick(e, this.difference)
|
||||
);
|
||||
this.buildActionLineDivider(actionLine);
|
||||
this.buildActionLineButton(actionLine, "Accept All", (e) =>
|
||||
this.handleAcceptAllClick(e, this.difference)
|
||||
);
|
||||
} else if (hasMinusLines) {
|
||||
this.buildActionLineButton(
|
||||
actionLine,
|
||||
`Accept from ${this.file1.name}`,
|
||||
(e) => this.handleAcceptTopClick(e, this.difference)
|
||||
);
|
||||
this.buildActionLineDivider(actionLine);
|
||||
this.buildActionLineButton(actionLine, `Discard`, (e) =>
|
||||
this.handleDiscardClick(e, this.difference)
|
||||
);
|
||||
} else if (hasPlusLines) {
|
||||
this.buildActionLineButton(
|
||||
actionLine,
|
||||
`Accept from ${this.file2.name}`,
|
||||
(e) => this.handleAcceptTopClick(e, this.difference)
|
||||
);
|
||||
this.buildActionLineDivider(actionLine);
|
||||
this.buildActionLineButton(actionLine, `Discard`, (e) =>
|
||||
this.handleDiscardClick(e, this.difference)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private buildActionLineButton(
|
||||
actionLine: HTMLDivElement,
|
||||
text: string,
|
||||
onClick: (event: MouseEvent) => void
|
||||
) {
|
||||
actionLine
|
||||
.createEl("a", { text, cls: "no-decoration text-xxs text-gray" })
|
||||
.onClickEvent(onClick);
|
||||
}
|
||||
|
||||
private buildActionLineDivider(actionLine: HTMLDivElement) {
|
||||
actionLine.createEl("span", { text: "|", cls: "text-xxs text-gray" });
|
||||
}
|
||||
|
||||
private async handleAcceptTopClick(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault();
|
||||
|
||||
const file1Content = await app.vault.read(this.file1);
|
||||
const changedLines = difference.lines
|
||||
.filter((line) => line.startsWith("-"))
|
||||
.map((line) => line.slice(1, line.length))
|
||||
.join("\n");
|
||||
const minusPlusLinesCount = difference.lines.findIndex(
|
||||
(line) => line.startsWith("-") || line.startsWith("+")
|
||||
);
|
||||
const newContent = replaceLine(
|
||||
file1Content,
|
||||
difference.start - 1 + minusPlusLinesCount,
|
||||
changedLines
|
||||
);
|
||||
await app.vault.modify(this.file1, newContent);
|
||||
|
||||
this.triggerRebuild();
|
||||
}
|
||||
|
||||
private async handleAcceptBottomClick(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault();
|
||||
|
||||
const file1Content = await app.vault.read(this.file1);
|
||||
const changedLines = difference.lines
|
||||
.filter((line) => line.startsWith("+"))
|
||||
.map((line) => line.slice(1, line.length))
|
||||
.join("\n");
|
||||
const minusPlusLinesCount = difference.lines.findIndex(
|
||||
(line) => line.startsWith("-") || line.startsWith("+")
|
||||
);
|
||||
const newContent = replaceLine(
|
||||
file1Content,
|
||||
difference.start - 1 + minusPlusLinesCount,
|
||||
changedLines
|
||||
);
|
||||
await app.vault.modify(this.file1, newContent);
|
||||
|
||||
this.triggerRebuild();
|
||||
}
|
||||
|
||||
private async handleAcceptAllClick(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault();
|
||||
|
||||
const file1Content = await app.vault.read(this.file1);
|
||||
const changedLines = difference.lines
|
||||
.filter((line) => line.startsWith("-") || line.startsWith("+"))
|
||||
.map((line) => line.slice(1, line.length))
|
||||
.join("\n");
|
||||
|
||||
const minusPlusLinesCount = difference.lines.findIndex(
|
||||
(line) => line.startsWith("-") || line.startsWith("+")
|
||||
);
|
||||
const newContent = replaceLine(
|
||||
file1Content,
|
||||
difference.start - 1 + minusPlusLinesCount,
|
||||
changedLines
|
||||
);
|
||||
await app.vault.modify(this.file1, newContent);
|
||||
|
||||
this.triggerRebuild();
|
||||
}
|
||||
|
||||
async handleDiscardClick(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault();
|
||||
|
||||
const file1Content = await app.vault.read(this.file1);
|
||||
|
||||
const minusPlusLinesCount = difference.lines.findIndex(
|
||||
(line) => line.startsWith("-") || line.startsWith("+")
|
||||
);
|
||||
const newContent = replaceLine(
|
||||
file1Content,
|
||||
difference.start - 1 + minusPlusLinesCount,
|
||||
""
|
||||
);
|
||||
await app.vault.modify(this.file1, newContent);
|
||||
|
||||
this.triggerRebuild();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +1,64 @@
|
|||
import { structuredPatch } from "diff";
|
||||
import { ItemView, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { ActionLine } from "./action_line";
|
||||
import { Difference } from "./data/difference";
|
||||
import { FileDifferences } from "./data/file_differences";
|
||||
import { preventEmptyString, replaceLine } from "./utils/string_utils";
|
||||
import { preventEmptyString } from "./utils/string_utils";
|
||||
|
||||
export const VIEW_TYPE_PATCH = "patch-view";
|
||||
|
||||
export class DifferencesView extends ItemView {
|
||||
constructor(
|
||||
leaf: WorkspaceLeaf,
|
||||
public file1Content: string,
|
||||
public file2Content: string,
|
||||
public file1: TFile,
|
||||
public file2: TFile,
|
||||
public fileDifferences: FileDifferences
|
||||
) {
|
||||
constructor(leaf: WorkspaceLeaf, public file1: TFile, public file2: TFile) {
|
||||
super(leaf);
|
||||
}
|
||||
|
||||
private fileDifferences: FileDifferences;
|
||||
private file1Lines: string[];
|
||||
private lineCount: number;
|
||||
|
||||
getViewType() {
|
||||
return VIEW_TYPE_PATCH;
|
||||
}
|
||||
|
||||
getDisplayText() {
|
||||
return `Difference between ${this.fileDifferences.file1Name} and ${this.fileDifferences.file2Name}`;
|
||||
return `Difference between ${this.file1.name} and ${this.file2.name}`;
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
this.contentEl.empty();
|
||||
const container = this.contentEl.createDiv({ cls: "container" });
|
||||
const lines = this.file1Content.split("\n");
|
||||
await this.updateState();
|
||||
this.build();
|
||||
}
|
||||
|
||||
const lineCount = Math.max(
|
||||
lines.length,
|
||||
...this.fileDifferences.differences.map((d) => d.start)
|
||||
private async updateState() {
|
||||
const file1Content = await this.app.vault.read(this.file1);
|
||||
this.file1Lines = file1Content.split("\n");
|
||||
|
||||
const file2Content = await this.app.vault.read(this.file2);
|
||||
this.fileDifferences = FileDifferences.fromParsedDiff(
|
||||
structuredPatch(
|
||||
this.file1.path,
|
||||
this.file2.path,
|
||||
file1Content,
|
||||
file2Content,
|
||||
)
|
||||
);
|
||||
|
||||
for (let i = 0; i <= lineCount; i++) {
|
||||
let line = i in lines ? lines[i] : null;
|
||||
// Find the highest line number we need to go through. This can be the
|
||||
// highest number in the differences, because the second file can have
|
||||
// more lines than the first file.
|
||||
this.lineCount = Math.max(
|
||||
this.file1Lines.length,
|
||||
...this.fileDifferences.differences.map((d) => d.start)
|
||||
);
|
||||
}
|
||||
|
||||
private build() {
|
||||
this.contentEl.empty();
|
||||
|
||||
const container = this.contentEl.createDiv({ cls: "container" });
|
||||
|
||||
for (let i = 0; i <= this.lineCount; i++) {
|
||||
let line = i in this.file1Lines ? this.file1Lines[i] : null;
|
||||
const difference = this.fileDifferences.differences.find(
|
||||
(d) => d.start === i
|
||||
);
|
||||
|
|
@ -61,7 +83,12 @@ export class DifferencesView extends ItemView {
|
|||
container: HTMLDivElement,
|
||||
difference: Difference
|
||||
) {
|
||||
this.buildActionLine(container, difference);
|
||||
new ActionLine(
|
||||
difference,
|
||||
this.file1,
|
||||
this.file2,
|
||||
this.triggerRebuild
|
||||
).build(container);
|
||||
|
||||
difference.lines.forEach((line) => {
|
||||
if (line.startsWith("+")) {
|
||||
|
|
@ -80,171 +107,6 @@ export class DifferencesView extends ItemView {
|
|||
});
|
||||
}
|
||||
|
||||
private buildActionLine(container: HTMLDivElement, difference: Difference) {
|
||||
const actionLine = container.createDiv({ cls: "flex-row gap-2 py-2" });
|
||||
|
||||
const hasPlusLines = difference.lines.some((l) => l.startsWith("+"));
|
||||
const hasMinusLines = difference.lines.some((l) => l.startsWith("-"));
|
||||
|
||||
if (hasPlusLines && hasMinusLines) {
|
||||
this.buildActionLineButton(actionLine, "Accept Top", (e) =>
|
||||
this.handleAcceptTopClick(e, difference)
|
||||
);
|
||||
this.buildActionLineDivider(actionLine);
|
||||
this.buildActionLineButton(actionLine, "Accept Bottom", (e) =>
|
||||
this.handleAcceptBottomClick(e, difference)
|
||||
);
|
||||
this.buildActionLineDivider(actionLine);
|
||||
this.buildActionLineButton(actionLine, "Accept All", (e) =>
|
||||
this.handleAcceptAllClick(e, difference)
|
||||
);
|
||||
} else if (hasMinusLines) {
|
||||
this.buildActionLineButton(
|
||||
actionLine,
|
||||
`Accept from ${this.fileDifferences.file1Name}`,
|
||||
(e) => this.handleAcceptTopClick(e, difference)
|
||||
);
|
||||
this.buildActionLineDivider(actionLine);
|
||||
this.buildActionLineButton(actionLine, `Discard`, (e) =>
|
||||
this.handleDiscardClick(e, difference)
|
||||
);
|
||||
} else if (hasPlusLines) {
|
||||
this.buildActionLineButton(
|
||||
actionLine,
|
||||
`Accept from ${this.fileDifferences.file2Name}`,
|
||||
(e) => this.handleAcceptTopClick(e, difference)
|
||||
);
|
||||
this.buildActionLineDivider(actionLine);
|
||||
this.buildActionLineButton(actionLine, `Discard`, (e) =>
|
||||
this.handleDiscardClick(e, difference)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private buildActionLineButton(
|
||||
actionLine: HTMLDivElement,
|
||||
text: string,
|
||||
onClick: (event: MouseEvent) => void
|
||||
) {
|
||||
actionLine
|
||||
.createEl("a", { text, cls: "no-decoration text-xxs text-gray" })
|
||||
.onClickEvent(onClick);
|
||||
}
|
||||
|
||||
private buildActionLineDivider(actionLine: HTMLDivElement) {
|
||||
actionLine.createEl("span", { text: "|", cls: "text-xxs text-gray" });
|
||||
}
|
||||
|
||||
private async handleAcceptTopClick(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault();
|
||||
|
||||
const file1Content = await this.app.vault.read(this.file1);
|
||||
const changedLines = difference.lines
|
||||
.filter((line) => line.startsWith("-"))
|
||||
.map((line) => line.slice(1, line.length))
|
||||
.join("\n");
|
||||
const minusPlusLinesCount = difference.lines.findIndex(
|
||||
(line) => line.startsWith("-") || line.startsWith("+")
|
||||
);
|
||||
const newContent = replaceLine(
|
||||
file1Content,
|
||||
difference.start - 1 + minusPlusLinesCount,
|
||||
changedLines
|
||||
);
|
||||
await this.app.vault.modify(this.file1, newContent);
|
||||
|
||||
this.fileDifferences.differences.remove(difference);
|
||||
|
||||
this.file1Content = newContent;
|
||||
|
||||
this.triggerRebuild();
|
||||
}
|
||||
|
||||
private async handleAcceptBottomClick(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault();
|
||||
|
||||
const file1Content = await this.app.vault.read(this.file1);
|
||||
const changedLines = difference.lines
|
||||
.filter((line) => line.startsWith("+"))
|
||||
.map((line) => line.slice(1, line.length))
|
||||
.join("\n");
|
||||
const minusPlusLinesCount = difference.lines.findIndex(
|
||||
(line) => line.startsWith("-") || line.startsWith("+")
|
||||
);
|
||||
const newContent = replaceLine(
|
||||
file1Content,
|
||||
difference.start - 1 + minusPlusLinesCount,
|
||||
changedLines
|
||||
);
|
||||
await this.app.vault.modify(this.file1, newContent);
|
||||
|
||||
this.fileDifferences.differences.remove(difference);
|
||||
|
||||
this.file1Content = newContent;
|
||||
|
||||
this.triggerRebuild();
|
||||
}
|
||||
|
||||
private async handleAcceptAllClick(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault();
|
||||
|
||||
const file1Content = await this.app.vault.read(this.file1);
|
||||
const changedLines = difference.lines
|
||||
.filter((line) => line.startsWith("-") || line.startsWith("+"))
|
||||
.map((line) => line.slice(1, line.length))
|
||||
.join("\n");
|
||||
|
||||
const minusPlusLinesCount = difference.lines.findIndex(
|
||||
(line) => line.startsWith("-") || line.startsWith("+")
|
||||
);
|
||||
const newContent = replaceLine(
|
||||
file1Content,
|
||||
difference.start - 1 + minusPlusLinesCount,
|
||||
changedLines
|
||||
);
|
||||
await this.app.vault.modify(this.file1, newContent);
|
||||
|
||||
this.fileDifferences.differences.remove(difference);
|
||||
|
||||
this.file1Content = newContent;
|
||||
|
||||
this.triggerRebuild();
|
||||
}
|
||||
|
||||
async handleDiscardClick(
|
||||
event: MouseEvent,
|
||||
difference: Difference
|
||||
): Promise<void> {
|
||||
event.preventDefault();
|
||||
|
||||
const file1Content = await this.app.vault.read(this.file1);
|
||||
|
||||
const minusPlusLinesCount = difference.lines.findIndex(
|
||||
(line) => line.startsWith("-") || line.startsWith("+")
|
||||
);
|
||||
const newContent = replaceLine(
|
||||
file1Content,
|
||||
difference.start - 1 + minusPlusLinesCount,
|
||||
""
|
||||
);
|
||||
await this.app.vault.modify(this.file1, newContent);
|
||||
|
||||
this.fileDifferences.differences.remove(difference);
|
||||
|
||||
this.file1Content = newContent;
|
||||
|
||||
this.triggerRebuild();
|
||||
}
|
||||
|
||||
private triggerRebuild() {
|
||||
this.onOpen();
|
||||
}
|
||||
|
|
|
|||
27
src/main.ts
27
src/main.ts
|
|
@ -1,7 +1,5 @@
|
|||
import { Editor, Plugin, TFile } from "obsidian";
|
||||
|
||||
import { structuredPatch } from "diff";
|
||||
import { FileDifferences } from "./data/file_differences";
|
||||
import { DifferencesView } from "./differences_view";
|
||||
import { SelectFileModal } from "./select_file_modal";
|
||||
|
||||
|
|
@ -27,31 +25,10 @@ export default class FileDiffPlugin extends Plugin {
|
|||
return;
|
||||
}
|
||||
|
||||
// Create difference between the files
|
||||
const activeFileContent = await this.app.vault.read(activeFile);
|
||||
const compareFileContent = await this.app.vault.read(
|
||||
compareFile
|
||||
);
|
||||
const fileDifferences = FileDifferences.fromParsedDiff(
|
||||
structuredPatch(
|
||||
activeFile.path,
|
||||
compareFile.path,
|
||||
activeFileContent,
|
||||
compareFileContent
|
||||
)
|
||||
);
|
||||
|
||||
// Show difference
|
||||
// Open differences view
|
||||
const workspaceLeaf = this.app.workspace.getLeaf();
|
||||
await workspaceLeaf.open(
|
||||
new DifferencesView(
|
||||
workspaceLeaf,
|
||||
activeFileContent,
|
||||
compareFileContent,
|
||||
activeFile,
|
||||
compareFile,
|
||||
fileDifferences
|
||||
)
|
||||
new DifferencesView(workspaceLeaf, activeFile, compareFile)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue