Skip to content

Commit

Permalink
#476 "Open File" action is now available in the Visual Studio Code Di…
Browse files Browse the repository at this point in the history
…ff View Title Menu, when the Diff View is opened from Git Graph.
  • Loading branch information
mhutchie committed Mar 10, 2021
1 parent dbea5a1 commit 8238ea3
Show file tree
Hide file tree
Showing 9 changed files with 340 additions and 95 deletions.
20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@
"category": "Git Graph",
"command": "git-graph.version",
"title": "Get Version Information"
},
{
"category": "Git Graph",
"command": "git-graph.openFile",
"title": "Open File",
"icon": "$(go-to-file)",
"enablement": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported"
}
],
"configuration": {
Expand Down Expand Up @@ -1422,6 +1429,19 @@
}
},
"menus": {
"commandPalette": [
{
"command": "git-graph.openFile",
"when": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported"
}
],
"editor/title": [
{
"command": "git-graph.openFile",
"group": "navigation",
"when": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported"
}
],
"scm/title": [
{
"when": "scmProvider == git && config.git-graph.sourceCodeProviderIntegrationLocation == 'Inline'",
Expand Down
44 changes: 43 additions & 1 deletion src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import * as vscode from 'vscode';
import { AvatarManager } from './avatarManager';
import { getConfig } from './config';
import { DataSource } from './dataSource';
import { DiffDocProvider, decodeDiffDocUri } from './diffDocProvider';
import { CodeReviewData, CodeReviews, ExtensionState } from './extensionState';
import { GitGraphView } from './gitGraphView';
import { Logger } from './logger';
import { RepoManager } from './repoManager';
import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, abbrevCommit, abbrevText, copyToClipboard, getExtensionVersion, getPathFromUri, getRelativeTimeDiff, getRepoName, isPathInWorkspace, resolveToSymbolicPath, showErrorMessage, showInformationMessage } from './utils';
import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, abbrevCommit, abbrevText, copyToClipboard, doesVersionMeetRequirement, getExtensionVersion, getPathFromUri, getRelativeTimeDiff, getRepoName, isPathInWorkspace, openFile, resolveToSymbolicPath, showErrorMessage, showInformationMessage } from './utils';
import { Disposable } from './utils/disposable';
import { Event } from './utils/event';

Expand Down Expand Up @@ -44,6 +45,7 @@ export class CommandManager extends Disposable {
this.repoManager = repoManager;
this.gitExecutable = gitExecutable;

// Register Extension Commands
this.registerCommand('git-graph.view', (arg) => this.view(arg));
this.registerCommand('git-graph.addGitRepository', () => this.addGitRepository());
this.registerCommand('git-graph.removeGitRepository', () => this.removeGitRepository());
Expand All @@ -53,12 +55,20 @@ export class CommandManager extends Disposable {
this.registerCommand('git-graph.endSpecificWorkspaceCodeReview', () => this.endSpecificWorkspaceCodeReview());
this.registerCommand('git-graph.resumeWorkspaceCodeReview', () => this.resumeWorkspaceCodeReview());
this.registerCommand('git-graph.version', () => this.version());
this.registerCommand('git-graph.openFile', (arg) => this.openFile(arg));

this.registerDisposable(
onDidChangeGitExecutable((gitExecutable) => {
this.gitExecutable = gitExecutable;
})
);

// Register Extension Contexts
try {
this.registerContext('git-graph:codiconsSupported', doesVersionMeetRequirement(vscode.version, '1.42.0'));
} catch (_) {
this.logger.logError('Unable to set Visual Studio Code Context "git-graph:codiconsSupported"');
}
}

/**
Expand All @@ -72,6 +82,18 @@ export class CommandManager extends Disposable {
);
}

/**
* Register a context with Visual Studio Code.
* @param key The Context Key.
* @param value The Context Value.
*/
private registerContext(key: string, value: any) {
return vscode.commands.executeCommand('setContext', key, value).then(
() => this.logger.log('Successfully set Visual Studio Code Context "' + key + '" to "' + JSON.stringify(value) + '"'),
() => this.logger.logError('Failed to set Visual Studio Code Context "' + key + '" to "' + JSON.stringify(value) + '"')
);
}


/* Commands */

Expand Down Expand Up @@ -292,6 +314,26 @@ export class CommandManager extends Disposable {
}
}

/**
* Opens a file in Visual Studio Code, based on a Git Graph URI (from the Diff View).
* The method run when the `git-graph.openFile` command is invoked.
* @param arg The Git Graph URI.
*/
private openFile(arg?: vscode.Uri) {
const uri = arg || vscode.window.activeTextEditor?.document.uri;
if (typeof uri === 'object' && uri.scheme === DiffDocProvider.scheme) {
// A Git Graph URI has been provided
const request = decodeDiffDocUri(uri);
return openFile(request.repo, request.filePath, vscode.ViewColumn.Active).then((errorInfo) => {
if (errorInfo !== null) {
return showErrorMessage('Unable to Open File: ' + errorInfo);
}
});
} else {
return showErrorMessage('Unable to Open File: The command was not called with the required arguments.');
}
}


/* Helper Methods */

Expand Down
9 changes: 4 additions & 5 deletions src/dataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { AskpassEnvironment, AskpassManager } from './askpass/askpassManager';
import { getConfig } from './config';
import { Logger } from './logger';
import { CommitOrdering, DateType, DeepWriteable, ErrorInfo, GitCommit, GitCommitDetails, GitCommitStash, GitConfigLocation, GitFileChange, GitFileStatus, GitPushBranchMode, GitRepoConfig, GitRepoConfigBranches, GitResetMode, GitSignatureStatus, GitStash, MergeActionOn, RebaseActionOn, SquashMessageFormat, TagType, Writeable } from './types';
import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED, abbrevCommit, constructIncompatibleGitVersionMessage, getPathFromStr, getPathFromUri, isGitAtLeastVersion, openGitTerminal, pathWithTrailingSlash, realpath, resolveSpawnOutput, showErrorMessage } from './utils';
import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED, abbrevCommit, constructIncompatibleGitVersionMessage, doesVersionMeetRequirement, getPathFromStr, getPathFromUri, openGitTerminal, pathWithTrailingSlash, realpath, resolveSpawnOutput, showErrorMessage } from './utils';
import { Disposable } from './utils/disposable';
import { Event } from './utils/event';

Expand Down Expand Up @@ -88,7 +88,7 @@ export class DataSource extends Disposable {
*/
public setGitExecutable(gitExecutable: GitExecutable | null) {
this.gitExecutable = gitExecutable;
this.gitExecutableSupportsGpgInfo = gitExecutable !== null ? isGitAtLeastVersion(gitExecutable, '2.4.0') : false;
this.gitExecutableSupportsGpgInfo = gitExecutable !== null ? doesVersionMeetRequirement(gitExecutable.version, '2.4.0') : false;
this.generateGitCommandFormats();
}

Expand Down Expand Up @@ -719,7 +719,7 @@ export class DataSource extends Disposable {
if (pruneTags) {
if (!prune) {
return Promise.resolve('In order to Prune Tags, pruning must also be enabled when fetching from ' + (remote !== null ? 'a remote' : 'remote(s)') + '.');
} else if (this.gitExecutable !== null && !isGitAtLeastVersion(this.gitExecutable, '2.17.0')) {
} else if (this.gitExecutable !== null && !doesVersionMeetRequirement(this.gitExecutable.version, '2.17.0')) {
return Promise.resolve(constructIncompatibleGitVersionMessage(this.gitExecutable, '2.17.0', 'pruning tags when fetching'));
}
args.push('--prune-tags');
Expand Down Expand Up @@ -1197,8 +1197,7 @@ export class DataSource extends Disposable {
public pushStash(repo: string, message: string, includeUntracked: boolean): Promise<ErrorInfo> {
if (this.gitExecutable === null) {
return Promise.resolve(UNABLE_TO_FIND_GIT_MSG);
}
if (!isGitAtLeastVersion(this.gitExecutable, '2.13.2')) {
} else if (!doesVersionMeetRequirement(this.gitExecutable.version, '2.13.2')) {
return Promise.resolve(constructIncompatibleGitVersionMessage(this.gitExecutable, '2.13.2'));
}

Expand Down
38 changes: 23 additions & 15 deletions src/diffDocProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,20 @@ export class DiffDocProvider extends Disposable implements vscode.TextDocumentCo
* @returns The content of the text document.
*/
public provideTextDocumentContent(uri: vscode.Uri): string | Thenable<string> {
let document = this.docs.get(uri.toString());
if (document) return document.value;
const document = this.docs.get(uri.toString());
if (document) {
return document.value;
}

let request = decodeDiffDocUri(uri);
if (request === null) return ''; // Return empty file (used for one side of added / deleted file diff)
const request = decodeDiffDocUri(uri);
if (!request.exists) {
// Return empty file (used for one side of added / deleted file diff)
return '';
}

return this.dataSource.getCommitFile(request.repo, request.commit, request.filePath).then(
(contents) => {
let document = new DiffDocument(contents);
const document = new DiffDocument(contents);
this.docs.set(uri.toString(), document);
return document.value;
},
Expand Down Expand Up @@ -99,7 +104,8 @@ type DiffDocUriData = {
filePath: string;
commit: string;
repo: string;
} | null;
exists: boolean;
};

/**
* Produce the URI of a file to be used in the Visual Studio Diff View.
Expand All @@ -115,17 +121,19 @@ export function encodeDiffDocUri(repo: string, filePath: string, commit: string,
return vscode.Uri.file(path.join(repo, filePath));
}

let data: DiffDocUriData, extension: string;
if ((diffSide === DiffSide.Old && type === GitFileStatus.Added) || (diffSide === DiffSide.New && type === GitFileStatus.Deleted)) {
data = null;
const fileDoesNotExist = (diffSide === DiffSide.Old && type === GitFileStatus.Added) || (diffSide === DiffSide.New && type === GitFileStatus.Deleted);
const data: DiffDocUriData = {
filePath: getPathFromStr(filePath),
commit: commit,
repo: repo,
exists: !fileDoesNotExist
};

let extension: string;
if (fileDoesNotExist) {
extension = '';
} else {
data = {
filePath: getPathFromStr(filePath),
commit: commit,
repo: repo
};
let extIndex = data.filePath.indexOf('.', data.filePath.lastIndexOf('/') + 1);
const extIndex = data.filePath.indexOf('.', data.filePath.lastIndexOf('/') + 1);
extension = extIndex > -1 ? data.filePath.substring(extIndex) : '';
}

Expand Down
21 changes: 11 additions & 10 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,16 +334,17 @@ export function openExternalUrl(url: string, type: string = 'External URL'): The
* Open a file within a repository in Visual Studio Code.
* @param repo The repository the file is contained in.
* @param filePath The relative path of the file within the repository.
* @param viewColumn An optional ViewColumn that the file should be opened in.
* @returns A promise resolving to the ErrorInfo of the executed command.
*/
export function openFile(repo: string, filePath: string) {
export function openFile(repo: string, filePath: string, viewColumn: vscode.ViewColumn | null = null) {
return new Promise<ErrorInfo>(resolve => {
const p = path.join(repo, filePath);
fs.access(p, fs.constants.R_OK, (err) => {
if (err === null) {
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(p), {
preview: true,
viewColumn: getConfig().openNewTabEditorGroup
viewColumn: viewColumn === null ? getConfig().openNewTabEditorGroup : viewColumn
}).then(
() => resolve(null),
() => resolve('Visual Studio Code was unable to open ' + filePath + '.')
Expand Down Expand Up @@ -710,17 +711,17 @@ export async function getGitExecutableFromPaths(paths: string[]): Promise<GitExe
}


/* Git Version Handling */
/* Version Handling */

/**
* Checks whether a Git executable is at least the specified version.
* @param executable The Git executable to check.
* @param version The minimum required version.
* @returns TRUE => `executable` is at least `version`, FALSE => `executable` is older than `version`.
* Checks whether a version is at least a required version.
* @param version The version to check.
* @param requiredVersion The minimum required version.
* @returns TRUE => `version` is at least `requiredVersion`, FALSE => `version` is older than `requiredVersion`.
*/
export function isGitAtLeastVersion(executable: GitExecutable, version: string) {
const v1 = parseVersion(executable.version);
const v2 = parseVersion(version);
export function doesVersionMeetRequirement(version: string, requiredVersion: string) {
const v1 = parseVersion(version);
const v2 = parseVersion(requiredVersion);

if (v1 === null || v2 === null) {
// Unable to parse a version number
Expand Down
Loading

0 comments on commit 8238ea3

Please sign in to comment.