mirror of
https://github.com/ckaczor/vscode-gitlens.git
synced 2026-01-14 01:25:43 -05:00
Adds an experimental custom view (wip)
This commit is contained in:
4
images/dark/icon-commit.svg
Normal file
4
images/dark/icon-commit.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="256" height="256" viewBox="0 0 14 16" xml:space="preserve">
|
||||
<path fill="#FFFFFF" fill-rule="evenodd" d="M10.86 7c-.45-1.72-2-3-3.86-3-1.86 0-3.41 1.28-3.86 3H0v2h3.14c.45 1.72 2 3 3.86 3 1.86 0 3.41-1.28 3.86-3H14V7h-3.14zM7 10.2c-1.22 0-2.2-.98-2.2-2.2 0-1.22.98-2.2 2.2-2.2 1.22 0 2.2.98 2.2 2.2 0 1.22-.98 2.2-2.2 2.2z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 500 B |
0
images/dark/icon-refresh.svg
Normal file
0
images/dark/icon-refresh.svg
Normal file
4
images/light/icon-commit.svg
Normal file
4
images/light/icon-commit.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="256" height="256" viewBox="0 0 14 16" xml:space="preserve">
|
||||
<path fill="#00000" fill-rule="evenodd" d="M10.86 7c-.45-1.72-2-3-3.86-3-1.86 0-3.41 1.28-3.86 3H0v2h3.14c.45 1.72 2 3 3.86 3 1.86 0 3.41-1.28 3.86-3H14V7h-3.14zM7 10.2c-1.22 0-2.2-.98-2.2-2.2 0-1.22.98-2.2 2.2-2.2 1.22 0 2.2.98 2.2 2.2 0 1.22-.98 2.2-2.2 2.2z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 499 B |
2829
package.json
2829
package.json
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,7 @@ export * from './commands/showQuickCurrentBranchHistory';
|
||||
export * from './commands/showQuickFileHistory';
|
||||
export * from './commands/showQuickRepoStatus';
|
||||
export * from './commands/showQuickStashList';
|
||||
export * from './commands/showStashList';
|
||||
export * from './commands/stashApply';
|
||||
export * from './commands/stashDelete';
|
||||
export * from './commands/stashSave';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
import { commands, Disposable, SourceControlResourceGroup, SourceControlResourceState, TextDocumentShowOptions, TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
|
||||
import { ExplorerNode } from '../views/gitExplorerNodes';
|
||||
import { Logger } from '../logger';
|
||||
import { Telemetry } from '../telemetry';
|
||||
|
||||
@@ -34,6 +35,7 @@ export type Commands = 'gitlens.closeUnchangedFiles' |
|
||||
'gitlens.showQuickRepoHistory' |
|
||||
'gitlens.showQuickRepoStatus' |
|
||||
'gitlens.showQuickStashList' |
|
||||
'gitlens.showStashList' |
|
||||
'gitlens.stashApply' |
|
||||
'gitlens.stashDelete' |
|
||||
'gitlens.stashSave' |
|
||||
@@ -73,6 +75,7 @@ export const Commands = {
|
||||
ShowQuickCurrentBranchHistory: 'gitlens.showQuickRepoHistory' as Commands,
|
||||
ShowQuickRepoStatus: 'gitlens.showQuickRepoStatus' as Commands,
|
||||
ShowQuickStashList: 'gitlens.showQuickStashList' as Commands,
|
||||
ShowStashList: 'gitlens.showStashList' as Commands,
|
||||
StashApply: 'gitlens.stashApply' as Commands,
|
||||
StashDelete: 'gitlens.stashDelete' as Commands,
|
||||
StashSave: 'gitlens.stashSave' as Commands,
|
||||
@@ -116,7 +119,12 @@ export interface CommandUriContext extends CommandBaseContext {
|
||||
type: 'uri';
|
||||
}
|
||||
|
||||
export type CommandContext = CommandScmGroupsContext | CommandScmStatesContext | CommandUnknownContext | CommandUriContext;
|
||||
export interface CommandViewContext extends CommandBaseContext {
|
||||
type: 'view';
|
||||
node: ExplorerNode;
|
||||
}
|
||||
|
||||
export type CommandContext = CommandScmGroupsContext | CommandScmStatesContext | CommandUnknownContext | CommandUriContext | CommandViewContext;
|
||||
|
||||
function isScmResourceGroup(group: any): group is SourceControlResourceGroup {
|
||||
if (group === undefined) return false;
|
||||
@@ -180,6 +188,11 @@ export abstract class Command extends Disposable {
|
||||
return [{ type: 'uri', editor: editor, uri: uri }, rest];
|
||||
}
|
||||
|
||||
if (firstArg instanceof ExplorerNode) {
|
||||
const [node, ...rest] = args as [ExplorerNode, any];
|
||||
return [{ type: 'view', node: node, uri: node.uri }, rest];
|
||||
}
|
||||
|
||||
if (isScmResourceState(firstArg)) {
|
||||
const states = [];
|
||||
let count = 0;
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
'use strict';
|
||||
import { Arrays } from '../system';
|
||||
import { commands, TextEditor, Uri, window } from 'vscode';
|
||||
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri } from './common';
|
||||
import { GitBlameCommit, GitService, GitUri } from '../gitService';
|
||||
import { Logger } from '../logger';
|
||||
import { Messages } from '../messages';
|
||||
import { OpenInRemoteCommandArgs } from './openInRemote';
|
||||
import { CommitNode } from '../views/gitExplorer';
|
||||
|
||||
export interface OpenCommitInRemoteCommandArgs {
|
||||
sha?: string;
|
||||
}
|
||||
|
||||
export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
|
||||
|
||||
@@ -13,7 +18,17 @@ export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
|
||||
super(Commands.OpenCommitInRemote);
|
||||
}
|
||||
|
||||
async execute(editor?: TextEditor, uri?: Uri) {
|
||||
protected async preExecute(context: CommandContext, args: OpenCommitInRemoteCommandArgs = {}): Promise<any> {
|
||||
if (context.type === 'view' && context.node instanceof CommitNode) {
|
||||
args = { ...args };
|
||||
args.sha = context.node.commit.sha;
|
||||
return this.execute(context.editor, context.node.commit.uri, args);
|
||||
}
|
||||
|
||||
return this.execute(context.editor, context.uri, args);
|
||||
}
|
||||
|
||||
async execute(editor?: TextEditor, uri?: Uri, args: OpenCommitInRemoteCommandArgs = {}) {
|
||||
uri = getCommandUri(uri, editor);
|
||||
if (uri === undefined) return undefined;
|
||||
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
|
||||
@@ -21,26 +36,29 @@ export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
|
||||
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||
if (!gitUri.repoPath) return undefined;
|
||||
|
||||
const line = editor === undefined ? gitUri.offset : editor.selection.active.line;
|
||||
|
||||
try {
|
||||
const blameline = line - gitUri.offset;
|
||||
if (blameline < 0) return undefined;
|
||||
if (args.sha === undefined) {
|
||||
const line = editor === undefined ? gitUri.offset : editor.selection.active.line;
|
||||
const blameline = line - gitUri.offset;
|
||||
if (blameline < 0) return undefined;
|
||||
|
||||
const blame = await this.git.getBlameForLine(gitUri, blameline);
|
||||
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open commit in remote provider');
|
||||
const blame = await this.git.getBlameForLine(gitUri, blameline);
|
||||
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open commit in remote provider');
|
||||
|
||||
let commit = blame.commit;
|
||||
// If the line is uncommitted, find the previous commit
|
||||
if (commit.isUncommitted) {
|
||||
commit = new GitBlameCommit(commit.repoPath, commit.previousSha!, commit.previousFileName!, commit.author, commit.date, commit.message, []);
|
||||
let commit = blame.commit;
|
||||
// If the line is uncommitted, find the previous commit
|
||||
if (commit.isUncommitted) {
|
||||
commit = new GitBlameCommit(commit.repoPath, commit.previousSha!, commit.previousFileName!, commit.author, commit.date, commit.message, []);
|
||||
}
|
||||
|
||||
args.sha = commit.sha;
|
||||
}
|
||||
|
||||
const remotes = Arrays.uniqueBy(await this.git.getRemotes(gitUri.repoPath), _ => _.url, _ => !!_.provider);
|
||||
return commands.executeCommand(Commands.OpenInRemote, uri, {
|
||||
resource: {
|
||||
type: 'commit',
|
||||
sha: commit.sha
|
||||
sha: args.sha
|
||||
},
|
||||
remotes
|
||||
} as OpenInRemoteCommandArgs);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||
import { Commands, EditorCommand, getCommandUri } from './common';
|
||||
import { BuiltInCommands } from '../constants';
|
||||
import { GitExplorer } from '../views/gitExplorer';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
import { Messages } from '../messages';
|
||||
import { Logger } from '../logger';
|
||||
@@ -14,7 +15,7 @@ export interface ShowFileHistoryCommandArgs {
|
||||
|
||||
export class ShowFileHistoryCommand extends EditorCommand {
|
||||
|
||||
constructor(private git: GitService) {
|
||||
constructor(private git: GitService, private explorer?: GitExplorer) {
|
||||
super(Commands.ShowFileHistory);
|
||||
}
|
||||
|
||||
@@ -32,6 +33,11 @@ export class ShowFileHistoryCommand extends EditorCommand {
|
||||
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||
|
||||
try {
|
||||
if (this.explorer !== undefined) {
|
||||
this.explorer.addHistory(gitUri);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const locations = await this.git.getLogLocations(gitUri, args.sha, args.line);
|
||||
if (locations === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show file history');
|
||||
|
||||
|
||||
30
src/commands/showStashList.ts
Normal file
30
src/commands/showStashList.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
import { TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||
import { Commands, EditorCommand, getCommandUri } from './common';
|
||||
import { GitExplorer } from '../views/gitExplorer';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
import { Messages } from '../messages';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export class ShowStashListCommand extends EditorCommand {
|
||||
|
||||
constructor(private git: GitService, private explorer: GitExplorer) {
|
||||
super(Commands.ShowStashList);
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) {
|
||||
uri = getCommandUri(uri, editor);
|
||||
|
||||
try {
|
||||
const repoPath = await this.git.getRepoPathFromUri(uri);
|
||||
if (!repoPath) return Messages.showNoRepositoryWarningMessage(`Unable to show stashed changes`);
|
||||
|
||||
this.explorer.addStash(new GitUri(uri, { repoPath: repoPath, fileName: uri!.fsPath }));
|
||||
return undefined;
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error(ex, 'ShowStashListCommand');
|
||||
return window.showErrorMessage(`Unable to show stash list. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,6 +303,13 @@ export interface IConfig {
|
||||
|
||||
defaultDateFormat: string | null;
|
||||
|
||||
explorer: {
|
||||
commitFormat: string;
|
||||
commitFileFormat: string;
|
||||
stashFormat: string;
|
||||
// dateFormat: string | null;
|
||||
};
|
||||
|
||||
statusBar: {
|
||||
enabled: boolean;
|
||||
alignment: 'left' | 'right';
|
||||
|
||||
@@ -12,13 +12,14 @@ import { ShowBlameHistoryCommand, ShowFileHistoryCommand } from './commands';
|
||||
import { ShowLastQuickPickCommand } from './commands';
|
||||
import { ShowQuickBranchHistoryCommand, ShowQuickCurrentBranchHistoryCommand, ShowQuickFileHistoryCommand } from './commands';
|
||||
import { ShowCommitSearchCommand, ShowQuickCommitDetailsCommand, ShowQuickCommitFileDetailsCommand } from './commands';
|
||||
import { ShowQuickRepoStatusCommand, ShowQuickStashListCommand } from './commands';
|
||||
import { ShowQuickRepoStatusCommand, ShowQuickStashListCommand, ShowStashListCommand } from './commands';
|
||||
import { StashApplyCommand, StashDeleteCommand, StashSaveCommand } from './commands';
|
||||
import { ToggleCodeLensCommand } from './commands';
|
||||
import { CodeLensLocations, IConfig, LineHighlightLocations } from './configuration';
|
||||
import { ApplicationInsightsKey, CommandContext, ExtensionKey, QualifiedExtensionId, setCommandContext, WorkspaceState } from './constants';
|
||||
import { CurrentLineController, LineAnnotationType } from './currentLineController';
|
||||
import { GitContentProvider } from './gitContentProvider';
|
||||
import { GitExplorer } from './views/gitExplorer';
|
||||
import { GitRevisionCodeLensProvider } from './gitRevisionCodeLensProvider';
|
||||
import { GitContextTracker, GitService } from './gitService';
|
||||
import { Keyboard } from './keyboard';
|
||||
@@ -87,6 +88,12 @@ export async function activate(context: ExtensionContext) {
|
||||
|
||||
context.subscriptions.push(new Keyboard());
|
||||
|
||||
let explorer: GitExplorer | undefined = undefined;
|
||||
if (cfg.insiders) {
|
||||
explorer = new GitExplorer(context, git);
|
||||
context.subscriptions.push(window.registerTreeDataProvider('gitlens-explorer', explorer));
|
||||
}
|
||||
|
||||
context.subscriptions.push(new CloseUnchangedFilesCommand(git));
|
||||
context.subscriptions.push(new OpenChangedFilesCommand(git));
|
||||
context.subscriptions.push(new CopyMessageToClipboardCommand(git));
|
||||
@@ -111,7 +118,7 @@ export async function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(new ToggleLineBlameCommand(currentLineController));
|
||||
context.subscriptions.push(new ResetSuppressedWarningsCommand(context));
|
||||
context.subscriptions.push(new ShowBlameHistoryCommand(git));
|
||||
context.subscriptions.push(new ShowFileHistoryCommand(git));
|
||||
context.subscriptions.push(new ShowFileHistoryCommand(git, explorer));
|
||||
context.subscriptions.push(new ShowLastQuickPickCommand());
|
||||
context.subscriptions.push(new ShowQuickBranchHistoryCommand(git));
|
||||
context.subscriptions.push(new ShowQuickCurrentBranchHistoryCommand(git));
|
||||
@@ -121,6 +128,9 @@ export async function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(new ShowQuickFileHistoryCommand(git));
|
||||
context.subscriptions.push(new ShowQuickRepoStatusCommand(git));
|
||||
context.subscriptions.push(new ShowQuickStashListCommand(git));
|
||||
if (cfg.insiders) {
|
||||
context.subscriptions.push(new ShowStashListCommand(git, explorer!));
|
||||
}
|
||||
context.subscriptions.push(new StashApplyCommand(git));
|
||||
context.subscriptions.push(new StashDeleteCommand(git));
|
||||
context.subscriptions.push(new StashSaveCommand(git));
|
||||
|
||||
41
src/views/commitFileNode.ts
Normal file
41
src/views/commitFileNode.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { Commands, DiffWithPreviousCommandArgs } from '../commands';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { GitCommit, GitService, GitUri, IGitStatusFile } from '../gitService';
|
||||
|
||||
export class CommitFileNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'commit-file';
|
||||
command: Command;
|
||||
|
||||
constructor(public status: IGitStatusFile, public commit: GitCommit, uri: GitUri, context: ExtensionContext, git: GitService) {
|
||||
super(uri, context, git);
|
||||
|
||||
this.command = {
|
||||
title: 'Compare File with Previous',
|
||||
command: Commands.DiffWithPrevious,
|
||||
arguments: [
|
||||
GitUri.fromFileStatus(this.status, this.commit.repoPath),
|
||||
{
|
||||
commit: commit,
|
||||
showOptions: {
|
||||
preserveFocus: true,
|
||||
preview: true
|
||||
}
|
||||
} as DiffWithPreviousCommandArgs
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
getChildren(): Promise<ExplorerNode[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem {
|
||||
const item = new TreeItem(`${GitUri.getFormattedPath(this.status.fileName)}`, TreeItemCollapsibleState.None);
|
||||
item.contextValue = this.resourceType;
|
||||
item.command = this.command;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
54
src/views/commitNode.ts
Normal file
54
src/views/commitNode.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
import { Iterables } from '../system';
|
||||
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { CommitFileNode } from './commitFileNode';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { CommitFormatter, GitCommit, GitService, GitUri } from '../gitService';
|
||||
|
||||
export class CommitNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'commit';
|
||||
command: Command;
|
||||
|
||||
constructor(public commit: GitCommit, uri: GitUri, context: ExtensionContext, git: GitService) {
|
||||
super(uri, context, git);
|
||||
|
||||
// this.command = {
|
||||
// title: 'Compare File with Previous',
|
||||
// command: Commands.DiffWithPrevious,
|
||||
// arguments: [
|
||||
// Uri.file(commit.uri.fsPath),
|
||||
// {
|
||||
// commit: commit,
|
||||
// showOptions: {
|
||||
// preserveFocus: true,
|
||||
// preview: true
|
||||
// }
|
||||
// } as DiffWithPreviousCommandArgs
|
||||
// ]
|
||||
// };
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
const log = await this.git.getLogForRepo(this.commit.repoPath, this.commit.sha, 1);
|
||||
if (log === undefined) return [];
|
||||
|
||||
const commit = Iterables.first(log.commits.values());
|
||||
if (commit === undefined) return [];
|
||||
|
||||
return [...Iterables.map(commit.fileStatuses, s => new CommitFileNode(s, commit, this.uri, this.context, this.git))];
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem {
|
||||
const label = CommitFormatter.fromTemplate(this.git.config.explorer.commitFormat, this.commit, this.git.config.defaultDateFormat);
|
||||
|
||||
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
|
||||
item.contextValue = this.resourceType;
|
||||
item.iconPath = {
|
||||
dark: this.context.asAbsolutePath('images/dark/icon-commit.svg'),
|
||||
light: this.context.asAbsolutePath('images/light/icon-commit.svg')
|
||||
};
|
||||
item.command = this.command;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
15
src/views/explorerNode.ts
Normal file
15
src/views/explorerNode.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
import { ExtensionContext, TreeItem } from 'vscode';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
|
||||
export declare type ResourceType = 'status' | 'branches' | 'repository' | 'branch-history' | 'file-history' | 'stash-history' | 'commit' | 'stash-commit' | 'commit-file';
|
||||
|
||||
export abstract class ExplorerNode {
|
||||
|
||||
abstract readonly resourceType: ResourceType;
|
||||
|
||||
constructor(public uri: GitUri, protected context: ExtensionContext, protected git: GitService) { }
|
||||
|
||||
abstract getChildren(): ExplorerNode[] | Promise<ExplorerNode[]>;
|
||||
abstract getTreeItem(): TreeItem | Promise<TreeItem>;
|
||||
}
|
||||
29
src/views/fileHistoryNode.ts
Normal file
29
src/views/fileHistoryNode.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
import { Iterables } from '../system';
|
||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { CommitNode } from './commitNode';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
|
||||
export class FileHistoryNode extends ExplorerNode {
|
||||
|
||||
static readonly rootType: ResourceType = 'file-history';
|
||||
readonly resourceType: ResourceType = 'file-history';
|
||||
|
||||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) {
|
||||
super(uri, context, git);
|
||||
}
|
||||
|
||||
async getChildren(): Promise<CommitNode[]> {
|
||||
const log = await this.git.getLogForFile(this.uri.repoPath, this.uri.fsPath, this.uri.sha);
|
||||
if (log === undefined) return [];
|
||||
|
||||
return [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.uri, this.context, this.git))];
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem {
|
||||
const item = new TreeItem(`History of ${this.uri.getFormattedPath()}`, TreeItemCollapsibleState.Expanded);
|
||||
item.contextValue = this.resourceType;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
62
src/views/gitExplorer.ts
Normal file
62
src/views/gitExplorer.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
'use strict';
|
||||
import { Event, EventEmitter, ExtensionContext, TreeDataProvider, TreeItem, Uri, window } from 'vscode';
|
||||
import { UriComparer } from '../comparers';
|
||||
import { ExplorerNode, FileHistoryNode, RepositoryNode, ResourceType, StashNode } from './gitExplorerNodes';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
|
||||
export * from './gitExplorerNodes';
|
||||
|
||||
export class GitExplorer implements TreeDataProvider<ExplorerNode> {
|
||||
|
||||
private _onDidChangeTreeData = new EventEmitter<ExplorerNode>();
|
||||
public get onDidChangeTreeData(): Event<ExplorerNode> {
|
||||
return this._onDidChangeTreeData.event;
|
||||
}
|
||||
|
||||
private _roots: ExplorerNode[] = [];
|
||||
|
||||
constructor(private context: ExtensionContext, private git: GitService) {
|
||||
const editor = window.activeTextEditor;
|
||||
|
||||
const uri = (editor !== undefined && editor.document !== undefined)
|
||||
? new GitUri(editor.document.uri, { repoPath: git.repoPath, fileName: editor.document.uri.fsPath })
|
||||
: new GitUri(Uri.file(git.repoPath), { repoPath: git.repoPath, fileName: git.repoPath });
|
||||
|
||||
this._roots.push(new RepositoryNode(uri, context, git));
|
||||
}
|
||||
|
||||
async getTreeItem(node: ExplorerNode): Promise<TreeItem> {
|
||||
return node.getTreeItem();
|
||||
}
|
||||
|
||||
async getChildren(node?: ExplorerNode): Promise<ExplorerNode[]> {
|
||||
if (this._roots.length === 0) return [];
|
||||
if (node === undefined) return this._roots;
|
||||
|
||||
return node.getChildren();
|
||||
}
|
||||
|
||||
addHistory(uri: GitUri) {
|
||||
this._add(uri, FileHistoryNode);
|
||||
}
|
||||
|
||||
addStash(uri: GitUri) {
|
||||
this._add(uri, StashNode);
|
||||
}
|
||||
|
||||
private _add<T extends ExplorerNode>(uri: GitUri, type: { new (uri: GitUri, context: ExtensionContext, git: GitService): T, rootType: ResourceType }) {
|
||||
if (!this._roots.some(_ => _.resourceType === type.rootType && UriComparer.equals(uri, _.uri))) {
|
||||
this._roots.push(new type(uri, this.context, this.git));
|
||||
}
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._roots = [];
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
}
|
||||
9
src/views/gitExplorerNodes.ts
Normal file
9
src/views/gitExplorerNodes.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
export * from './explorerNode';
|
||||
export * from './commitFileNode';
|
||||
export * from './commitNode';
|
||||
export * from './fileHistoryNode';
|
||||
export * from './repositoryNode';
|
||||
export * from './stashCommitNode';
|
||||
export * from './stashNode';
|
||||
105
src/views/repositoryNode.ts
Normal file
105
src/views/repositoryNode.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
'use strict';
|
||||
import { Iterables, Strings } from '../system';
|
||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { CommitNode } from './commitNode';
|
||||
import { GlyphChars } from '../constants';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { GitBranch, GitService, GitUri } from '../gitService';
|
||||
import { StashNode } from './stashNode';
|
||||
|
||||
export class RepositoryNode extends ExplorerNode {
|
||||
|
||||
static readonly rootType: ResourceType = 'repository';
|
||||
readonly resourceType: ResourceType = 'repository';
|
||||
|
||||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) {
|
||||
super(uri, context, git);
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
return [
|
||||
new StatusNode(this.uri, this.context, this.git),
|
||||
new StashNode(this.uri, this.context, this.git),
|
||||
new BranchesNode(this.uri, this.context, this.git)
|
||||
];
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem {
|
||||
const item = new TreeItem(`Repository ${GlyphChars.Dash} ${this.uri.repoPath}`, TreeItemCollapsibleState.Expanded);
|
||||
item.contextValue = this.resourceType;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
export class BranchesNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'branches';
|
||||
|
||||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) {
|
||||
super(uri, context, git);
|
||||
}
|
||||
|
||||
async getChildren(): Promise<BranchHistoryNode[]> {
|
||||
const branches = await this.git.getBranches(this.uri.repoPath!);
|
||||
if (branches === undefined) return [];
|
||||
|
||||
return [...Iterables.filterMap(branches.sort(_ => _.current ? 0 : 1), b => b.remote ? undefined : new BranchHistoryNode(b, this.uri, this.context, this.git))];
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem {
|
||||
const item = new TreeItem(`Branches`, TreeItemCollapsibleState.Collapsed);
|
||||
item.contextValue = this.resourceType;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
export class BranchHistoryNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'branch-history';
|
||||
|
||||
constructor(public branch: GitBranch, uri: GitUri, context: ExtensionContext, git: GitService) {
|
||||
super(uri, context, git);
|
||||
}
|
||||
|
||||
async getChildren(): Promise<CommitNode[]> {
|
||||
const log = await this.git.getLogForRepo(this.uri.repoPath!, this.branch.name);
|
||||
if (log === undefined) return [];
|
||||
|
||||
return [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.uri, this.context, this.git))];
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem {
|
||||
const item = new TreeItem(`${this.branch.name}${this.branch.current ? ` ${GlyphChars.Dash} current` : ''}`, TreeItemCollapsibleState.Collapsed);
|
||||
item.contextValue = this.resourceType;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
export class StatusNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'status';
|
||||
|
||||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) {
|
||||
super(uri, context, git);
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
return [];
|
||||
// const status = await this.git.getStatusForRepo(this.uri.repoPath!);
|
||||
// if (status === undefined) return [];
|
||||
|
||||
// return [...Iterables.map(status.files, b => new CommitFile(b, this.uri, this.context, this.git))];
|
||||
}
|
||||
|
||||
async getTreeItem(): Promise<TreeItem> {
|
||||
const status = await this.git.getStatusForRepo(this.uri.repoPath!);
|
||||
let suffix = '';
|
||||
if (status !== undefined) {
|
||||
suffix = ` ${GlyphChars.Dash} ${GlyphChars.ArrowUp} ${status.state.ahead} ${GlyphChars.ArrowDown} ${status.state.behind} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${status.branch} ${GlyphChars.ArrowLeftRight} ${status.upstream}`;
|
||||
}
|
||||
|
||||
const item = new TreeItem(`Status${suffix}`, TreeItemCollapsibleState.Collapsed);
|
||||
item.contextValue = this.resourceType;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
40
src/views/stashCommitNode.ts
Normal file
40
src/views/stashCommitNode.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { CommitFileNode } from './commitFileNode';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { CommitFormatter, GitService, GitStashCommit, GitUri } from '../gitService';
|
||||
|
||||
export class StashCommitNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'stash-commit';
|
||||
command: Command;
|
||||
|
||||
constructor(public commit: GitStashCommit, uri: GitUri, context: ExtensionContext, git: GitService) {
|
||||
super(uri, context, git);
|
||||
|
||||
// this.command = {
|
||||
// title: 'Show Stash Details',
|
||||
// command: Commands.ShowQuickCommitDetails,
|
||||
// arguments: [
|
||||
// new GitUri(commit.uri, commit),
|
||||
// {
|
||||
// commit: commit,
|
||||
// sha: commit.sha
|
||||
// } as ShowQuickCommitDetailsCommandArgs
|
||||
// ]
|
||||
// };
|
||||
}
|
||||
|
||||
getChildren(): Promise<CommitFileNode[]> {
|
||||
return Promise.resolve((this.commit as GitStashCommit).fileStatuses.map(_ => new CommitFileNode(_, this.commit, this.uri, this.context, this.git)));
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem {
|
||||
const label = CommitFormatter.fromTemplate(this.git.config.explorer.stashFormat, this.commit, this.git.config.defaultDateFormat);
|
||||
|
||||
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
|
||||
item.contextValue = this.resourceType;
|
||||
item.command = this.command;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
29
src/views/stashNode.ts
Normal file
29
src/views/stashNode.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
import { Iterables } from '../system';
|
||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
import { StashCommitNode } from './stashCommitNode';
|
||||
|
||||
export class StashNode extends ExplorerNode {
|
||||
|
||||
static readonly rootType: ResourceType = 'stash-history';
|
||||
readonly resourceType: ResourceType = 'stash-history';
|
||||
|
||||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) {
|
||||
super(uri, context, git);
|
||||
}
|
||||
|
||||
async getChildren(): Promise<StashCommitNode[]> {
|
||||
const stash = await this.git.getStashList(this.uri.repoPath!);
|
||||
if (stash === undefined) return [];
|
||||
|
||||
return [...Iterables.map(stash.commits.values(), c => new StashCommitNode(c, this.uri, this.context, this.git))];
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem {
|
||||
const item = new TreeItem(`Stashed Changes`, TreeItemCollapsibleState.Collapsed);
|
||||
item.contextValue = this.resourceType;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user