Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@
"title": "Select Reference",
"icon": "$(git-branch)"
},
{
"category": "Git History",
"command": "git-history.history.toggle.viewMode",
"title": "Toggle Commit Diff Mode",
"icon": "$(diff)"
},
{
"category": "Git History",
"command": "git-history.history.filter",
Expand Down Expand Up @@ -117,6 +123,11 @@
"command": "git-history.history.switch.branch",
"when": "view =~ /git-history.history/",
"group": "navigation@4"
},
{
"command": "git-history.history.toggle.viewMode",
"when": "view =~ /git-history.history/",
"group": "navigation@4"
}
]
}
Expand Down
2 changes: 2 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { getFilterCommandsDisposable } from "./filter";
import { getInputCommandsDisposable } from "./input";
import { getSwitchCommandsDisposable } from "./switch";
import { getToggleCommandsDisposable } from "./toggle";

export function getCommandDisposables() {
return [
...getFilterCommandsDisposable(),
...getSwitchCommandsDisposable(),
...getInputCommandsDisposable(),
...getToggleCommandsDisposable(),
];
}
25 changes: 25 additions & 0 deletions src/commands/toggle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { commands } from "vscode";

import { container } from "../container/inversify.config";
import { Source } from "../views/history/data/source";
import state from "../views/history/data/state";
import { ViewMode } from "../git/types";

export const TOGGLE_VIEW_MODE_COMMAND = "git-history.history.toggle.viewMode";

export function getToggleCommandsDisposable() {
const source = container.get(Source);

return [
commands.registerCommand(TOGGLE_VIEW_MODE_COMMAND, async () => {
const newMode = state.logOptions.mode === ViewMode.COMMIT_DIFF
? ViewMode.NORMAL
: ViewMode.COMMIT_DIFF;

state.logOptions.mode = newMode;

// Notify the UI about the mode change
source.notifyViewModeChanged(newMode);
}),
];
}
19 changes: 19 additions & 0 deletions src/git/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,25 @@ export class GitService {
);
}

async getCommitDiff(repoPath: string, leftRef: string, rightRef: string) {
const args = [
"diff",
"--name-status",
"-z",
`${leftRef}..${rightRef}`,
];

const diffOutput = await this.git!.cwd(repoPath || this.rootRepoPath)
.raw(args)
.then((res) => parseGitChanges(repoPath, res));

return {
ref: `${leftRef}..${rightRef}`,
repoPath,
changes: diffOutput,
};
}

async getChangesByRef(repoPath: string, ref: string) {
const args = [
"log",
Expand Down
6 changes: 6 additions & 0 deletions src/git/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ export interface LogOptions extends GitOptions {
maxLength?: number;
count?: number;
skip?: number;
mode?: ViewMode;
}

export enum ViewMode {
NORMAL = 'normal',
COMMIT_DIFF = 'commit_diff'
}

export type ICommitGraphSlice = [
Expand Down
13 changes: 13 additions & 0 deletions src/views/history/components/CommitsTable/index.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ $col-padding-right: 8px;
display: flex;
line-height: 24px;
margin-top: 4px;
position: relative;

.diff-mode-message {
position: absolute;
left: 10px;
top: 0;
z-index: 10;
font-size: 12px;
color: var(--vscode-editor-foreground);
background-color: var(--vscode-badge-background);
padding: 2px 8px;
border-radius: 3px;
}

.header-item {
display: flex;
Expand Down
20 changes: 19 additions & 1 deletion src/views/history/components/CommitsTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ import { VSCodeButton } from "@vscode/webview-ui-toolkit/react";
import { useMeasure } from "react-use";

import type { IBatchedCommits } from "../../../../git/types";
import { ViewMode } from "../../../../git/types";

import PickableList from "../PickableList";
import { ChannelContext } from "../../data/channel";

import { ICommit, parseCommit } from "../../../../git/commit";

import state from "../../data/state";

import { useBatchCommits } from "./useBatchCommits";
import { useColumnResize } from "./useColumnResize";

Expand All @@ -25,12 +28,17 @@ import style from "./index.module.scss";

const CommitsTableInner: FC<{ totalWidth: number }> = ({ totalWidth }) => {
const channel = useContext(ChannelContext)!;
const [viewMode, setViewMode] = useState<ViewMode>(state.logOptions.mode || ViewMode.NORMAL);

const { commits, commitsCount, options, setBatchedCommits } =
useBatchCommits();

function diff(sortedRefs: string[]) {
channel.viewChanges(sortedRefs);
if (viewMode === ViewMode.NORMAL || sortedRefs.length < 2) {
channel.viewChanges(sortedRefs);
} else if (viewMode === ViewMode.COMMIT_DIFF && sortedRefs.length === 2) {
channel.viewCommitDiff(sortedRefs[0], sortedRefs[1]);
}
}

const subscribeSwitcher = useCallback(() => {
Expand Down Expand Up @@ -101,11 +109,20 @@ const CommitsTableInner: FC<{ totalWidth: number }> = ({ totalWidth }) => {
useEffect(() => {
subscribeSwitcher();

channel.subscribeViewModeChange((mode: ViewMode) => {
setViewMode(mode);
});

channel.autoRefreshLog();
}, [channel, subscribeSwitcher]);

return (
<>
{viewMode === ViewMode.COMMIT_DIFF && (
<div className={style["diff-mode-message"]}>
<span>Commit Diff Mode: Select two commits to compare.</span>
</div>
)}
<div className={style["commit-headers"]}>
{columns.map(
(
Expand Down Expand Up @@ -191,6 +208,7 @@ const CommitsTableInner: FC<{ totalWidth: number }> = ({ totalWidth }) => {
keyLength={40}
locationIndex={locationIndex}
itemPipe={parseCommit}
mode={viewMode}
itemRender={(commit: ICommit) => (
<div className={style.commit}>
{columns.map(({ prop, size, transformer }) => (
Expand Down
23 changes: 23 additions & 0 deletions src/views/history/components/PickableList/index.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,29 @@
background-color: var(--vscode-list-activeSelectionBackground);
color: var(--vscode-list-activeSelectionForeground);
outline: 1px solid var(--vscode-list-focusOutline);
position: relative;
}

&.leftCommit::before {
content: "LEFT";
position: absolute;
top: 2px;
background-color: var(--vscode-badge-background);
color: var(--vscode-badge-foreground);
padding: 1px 4px;
border-radius: 5px;
z-index: 10;
}

&.rightCommit::before {
content: "RIGHT";
position: absolute;
top: 2px;
background-color: var(--vscode-badge-background);
color: var(--vscode-badge-foreground);
padding: 1px 4px;
border-radius: 5px;
z-index: 10;
}

&:not(.picked) {
Expand Down
103 changes: 71 additions & 32 deletions src/views/history/components/PickableList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import classNames from "classnames";

import { useVirtual } from "react-virtual";

import { ViewMode } from "../../../../git/types";

import { checkScrollBarVisible } from "../../utils/element";

import { useIsKeyPressed } from "./event";
Expand All @@ -21,6 +23,7 @@ interface Props<T> {
itemRender: (o: T) => ReactNode;
size?: number;
onPick?: (ids: Id[]) => void;
mode?: ViewMode;
}

const INDEX_PLACEHOLDER = -1;
Expand All @@ -37,6 +40,7 @@ const PickableList = <T extends Record<string, any>>(
itemRender,
size,
onPick,
mode,
} = props;
const scrollContainerRef = useRef<HTMLDivElement>(null);
const dragContainerRef = useRef<HTMLDivElement>(null);
Expand All @@ -48,6 +52,10 @@ const PickableList = <T extends Record<string, any>>(
});

const [pickedItems, setPickedItems] = useState<Record<Id, number>>({});

useEffect(() => {
setPickedItems({});
}, [mode]);
const [containerRect, setContainerRect] = useState<DOMRect | undefined>();
const [itemYs, setItemYs] = useState<number[]>([]);
const [dragStartIndex, setDragStartIndex] =
Expand All @@ -65,8 +73,9 @@ const PickableList = <T extends Record<string, any>>(
const dragBind = useDrag(({ type, xy, target }) => {
const [x, y] = xy;

const isCommitDiffMode = mode === ViewMode.COMMIT_DIFF;
const existedItems =
checkKeyIsPressed("Meta") || checkKeyIsPressed("Control")
!isCommitDiffMode && (checkKeyIsPressed("Meta") || checkKeyIsPressed("Control"))
? pickedItems
: {};
const firstItemIndex = virtualItems[0].index;
Expand Down Expand Up @@ -103,10 +112,34 @@ const PickableList = <T extends Record<string, any>>(
setDragStartIndex(dragStartIndex);

const id = list![dragStartIndex].slice(0, keyLength);
setPickedItems({
...existedItems,
[id]: dragStartIndex,
});

if (!isCommitDiffMode) {
setPickedItems({
...existedItems,
[id]: dragStartIndex,
});

} else {
/* Logic for handling selections
- First selection is left and pinnned
- Subsequent selections are right
- Selecting left again clears it
*/
if (Object.keys(pickedItems).length === 0) {
setPickedItems({ [id]: 0 });
} else if (pickedItems[id] === 0) {
setPickedItems({});
} else {
const leftItemId = String(Object.keys(pickedItems).find(key => pickedItems[key] === 0));

if (id === leftItemId) {
setPickedItems({ [leftItemId]: 0 });
}
else {
setPickedItems({ [leftItemId]: 0, [id]: 1 });
}
}
}
return;
}

Expand All @@ -132,7 +165,7 @@ const PickableList = <T extends Record<string, any>>(
y > containerRect.y &&
y < containerRect.y + containerRect.height
) {
if (dragStartIndex === INDEX_PLACEHOLDER) {
if (dragStartIndex === INDEX_PLACEHOLDER || mode === ViewMode.COMMIT_DIFF) {
return;
}

Expand Down Expand Up @@ -168,32 +201,38 @@ const PickableList = <T extends Record<string, any>>(
position: "relative",
}}
>
{virtualItems.map((virtualRow) => (
<div
key={virtualRow.index}
ref={virtualRow.measureRef}
className={classNames(style.item, {
[style.picked]:
list[virtualRow.index] &&
Object.prototype.hasOwnProperty.call(
pickedItems,
list[virtualRow.index].slice(0, keyLength)
),
[style.located]: virtualRow.index === locationIndex,
})}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "22px",
transform: `translateY(${virtualRow.start}px)`,
}}
>
{list[virtualRow.index] &&
itemRender(itemPipe(list[virtualRow.index]))}
</div>
))}
{virtualItems.map((virtualRow) => {
const itemId = list[virtualRow.index]?.slice(0, keyLength);
const isPicked = list[virtualRow.index] && Object.prototype.hasOwnProperty.call(pickedItems, itemId);

// Determine if this is the left or right commit in diff mode
const isLeftCommit = mode === ViewMode.COMMIT_DIFF && pickedItems[itemId] === 0;
const isRightCommit = mode === ViewMode.COMMIT_DIFF && pickedItems[itemId] === 1;

return (
<div
key={virtualRow.index}
ref={virtualRow.measureRef}
className={classNames(style.item, {
[style.picked]: isPicked,
[style.located]: virtualRow.index === locationIndex,
[style.leftCommit]: isLeftCommit,
[style.rightCommit]: isRightCommit,
})}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "22px",
transform: `translateY(${virtualRow.start}px)`,
}}
>
{list[virtualRow.index] &&
itemRender(itemPipe(list[virtualRow.index]))}
</div>
);
})}
</div>
</div>
);
Expand Down
Loading