Browse Source

Changes Results view into Compare view

Adds ability to start a compare directly from the view
main
Eric Amodio 6 years ago
parent
commit
55bc8c243f
12 changed files with 552 additions and 295 deletions
  1. +68
    -34
      package.json
  2. +1
    -1
      src/constants.ts
  3. +13
    -0
      src/container.ts
  4. +132
    -20
      src/logger.ts
  5. +109
    -106
      src/system/decorators.ts
  6. +1
    -0
      src/ui/config.ts
  7. +1
    -1
      src/views/nodes/common.ts
  8. +62
    -0
      src/views/nodes/comparePickerNode.ts
  9. +105
    -85
      src/views/nodes/resultsNode.ts
  10. +7
    -4
      src/views/nodes/viewNode.ts
  11. +15
    -20
      src/views/resultsView.ts
  12. +38
    -24
      src/views/viewCommands.ts

+ 68
- 34
package.json View File

@ -1281,6 +1281,12 @@
"description": "Specifies whether to show the tracking branch when displaying local branches in the `Repositories` view",
"scope": "window"
},
"gitlens.views.results.enabled": {
"type": "boolean",
"default": true,
"description": "Specifies whether to show the `Results` view",
"scope": "window"
},
"gitlens.views.results.files.compact": {
"type": "boolean",
"default": true,
@ -2133,6 +2139,21 @@
"category": "GitLens"
},
{
"command": "gitlens.views.selectForCompare",
"title": "Select for Compare",
"category": "GitLens"
},
{
"command": "gitlens.views.compareFileWithSelected",
"title": "Compare with Selected",
"category": "GitLens"
},
{
"command": "gitlens.views.selectFileForCompare",
"title": "Select for Compare",
"category": "GitLens"
},
{
"command": "gitlens.views.compareWithWorking",
"title": "Compare with Working Tree",
"category": "GitLens",
@ -2142,11 +2163,6 @@
}
},
{
"command": "gitlens.views.selectForCompare",
"title": "Select for Compare",
"category": "GitLens"
},
{
"command": "gitlens.views.terminalCheckoutBranch",
"title": "Checkout Branch (via Terminal)",
"category": "GitLens"
@ -2353,12 +2369,12 @@
"category": "GitLens"
},
{
"command": "gitlens.views.results.close",
"title": "Close",
"command": "gitlens.views.results.clear",
"title": "Clear Results",
"category": "GitLens",
"icon": {
"dark": "images/dark/icon-close.svg",
"light": "images/light/icon-close.svg"
"dark": "images/dark/icon-clear.svg",
"light": "images/light/icon-clear.svg"
}
},
{
@ -2482,19 +2498,19 @@
"commandPalette": [
{
"command": "gitlens.showRepositoriesView",
"when": "gitlens:enabled && config.gitlens.views.repositories.enabled"
"when": "gitlens:enabled"
},
{
"command": "gitlens.showFileHistoryView",
"when": "gitlens:enabled && config.gitlens.views.fileHistory.enabled"
"when": "gitlens:enabled"
},
{
"command": "gitlens.showLineHistoryView",
"when": "gitlens:enabled && config.gitlens.views.lineHistory.enabled"
"when": "gitlens:enabled"
},
{
"command": "gitlens.showResultsView",
"when": "gitlens:enabled && gitlens:views:results"
"when": "gitlens:enabled"
},
{
"command": "gitlens.diffDirectory",
@ -2809,11 +2825,19 @@
"when": "false"
},
{
"command": "gitlens.views.compareWithWorking",
"command": "gitlens.views.selectForCompare",
"when": "false"
},
{
"command": "gitlens.views.selectForCompare",
"command": "gitlens.views.compareFileWithSelected",
"when": "false"
},
{
"command": "gitlens.views.selectFileForCompare",
"when": "false"
},
{
"command": "gitlens.views.compareWithWorking",
"when": "false"
},
{
@ -2953,7 +2977,7 @@
"when": "false"
},
{
"command": "gitlens.views.results.close",
"command": "gitlens.views.results.clear",
"when": "false"
},
{
@ -3358,24 +3382,24 @@
"group": "1_gitlens"
},
{
"command": "gitlens.views.results.clear",
"when": "view =~ /^gitlens\\.views\\.results:/",
"group": "navigation@2"
},
{
"command": "gitlens.views.results.setKeepResultsToOn",
"when": "view =~ /^gitlens\\.views\\.results:/ && !gitlens:views:results:keepResults",
"group": "navigation@2"
"group": "navigation@3"
},
{
"command": "gitlens.views.results.setKeepResultsToOff",
"when": "view =~ /^gitlens\\.views\\.results:/ && gitlens:views:results:keepResults",
"group": "navigation@2"
},
{
"command": "gitlens.views.results.refresh",
"when": "view =~ /^gitlens\\.views\\.results:/",
"group": "navigation@3"
},
{
"command": "gitlens.views.results.close",
"command": "gitlens.views.results.refresh",
"when": "view =~ /^gitlens\\.views\\.results:/",
"group": "navigation@9"
"group": "navigation@99"
},
{
"command": "gitlens.views.results.setFilesLayoutToAuto",
@ -3487,12 +3511,22 @@
},
{
"command": "gitlens.views.compareWithSelected",
"when": "viewItem =~ /gitlens:(branch|commit|stash|tag|file:)\\b/ && gitlens:views:canCompare",
"when": "viewItem =~ /gitlens:(branch|commit|stash|tag)\\b/ && gitlens:views:canCompare",
"group": "7_gitlens_@1"
},
{
"command": "gitlens.views.selectForCompare",
"when": "viewItem =~ /gitlens:(branch|commit|stash|tag|file:)\\b/",
"when": "viewItem =~ /gitlens:(branch|commit|stash|tag)\\b/",
"group": "7_gitlens_@2"
},
{
"command": "gitlens.views.compareFileWithSelected",
"when": "viewItem =~ /gitlens:file:\\b/ && gitlens:views:canCompare:file",
"group": "7_gitlens_@1"
},
{
"command": "gitlens.views.selectFileForCompare",
"when": "viewItem =~ /gitlens:file:\\b/",
"group": "7_gitlens_@2"
},
{
@ -3871,12 +3905,12 @@
},
{
"command": "gitlens.views.dismissNode",
"when": "viewItem =~ /gitlens:(results|search)\\b(?!:(commits|files))/",
"when": "viewItem =~ /gitlens:(compare:picker:ref|results|search)\\b(?!:(commits|files))/",
"group": "inline@2"
},
{
"command": "gitlens.views.dismissNode",
"when": "viewItem =~ /gitlens:(results|search)\\b(?!:(commits|files))/",
"when": "viewItem =~ /gitlens:(compare:picker:ref|results|search)\\b(?!:(commits|files))/",
"group": "1_gitlens@1"
},
{
@ -4151,8 +4185,8 @@
},
{
"id": "gitlens.views.results:gitlens",
"name": "Results",
"when": "gitlens:enabled && gitlens:views:results && config.gitlens.views.results.location == gitlens"
"name": "Compare",
"when": "gitlens:enabled && config.gitlens.views.results.enabled && config.gitlens.views.results.location == gitlens"
},
{
"id": "gitlens.views.search:gitlens",
@ -4178,8 +4212,8 @@
},
{
"id": "gitlens.views.results:explorer",
"name": "GitLens: Results",
"when": "gitlens:enabled && gitlens:views:results && config.gitlens.views.results.location == explorer"
"name": "GitLens: Compare",
"when": "gitlens:enabled && config.gitlens.views.results.enabled && config.gitlens.views.results.location == explorer"
},
{
"id": "gitlens.views.search:explorer",
@ -4205,8 +4239,8 @@
},
{
"id": "gitlens.views.results:scm",
"name": "GitLens: Results",
"when": "gitlens:enabled && gitlens:views:results && config.gitlens.views.results.location == scm"
"name": "GitLens: Compare",
"when": "gitlens:enabled && config.gitlens.views.results.enabled && config.gitlens.views.results.location == scm"
},
{
"id": "gitlens.views.search:scm",

+ 1
- 1
src/constants.ts View File

@ -34,10 +34,10 @@ export enum CommandContext {
HasRemotes = 'gitlens:hasRemotes',
Key = 'gitlens:key',
ViewsCanCompare = 'gitlens:views:canCompare',
ViewsCanCompareFile = 'gitlens:views:canCompare:file',
ViewsFileHistoryEditorFollowing = 'gitlens:views:fileHistory:editorFollowing',
ViewsLineHistoryEditorFollowing = 'gitlens:views:lineHistory:editorFollowing',
ViewsRepositoriesAutoRefresh = 'gitlens:views:repositories:autoRefresh',
ViewsResults = 'gitlens:views:results',
ViewsResultsKeepResults = 'gitlens:views:results:keepResults',
ViewsSearchKeepResults = 'gitlens:views:search:keepResults'
}

+ 13
- 0
src/container.ts View File

@ -80,6 +80,19 @@ export class Container {
});
}
if (config.views.results.enabled) {
context.subscriptions.push((this._resultsView = new ResultsView()));
}
else {
let disposable: Disposable;
disposable = configuration.onDidChange(e => {
if (configuration.changed(e, configuration.name('views')('results')('enabled').value)) {
disposable.dispose();
context.subscriptions.push((this._resultsView = new ResultsView()));
}
});
}
if (config.views.search.enabled) {
context.subscriptions.push((this._searchView = new SearchView()));
}

+ 132
- 20
src/logger.ts View File

@ -10,6 +10,11 @@ const ConsolePrefix = `[${extensionOutputChannelName}]`;
const isDebuggingRegex = /\bgitlens\b/i;
export interface LogCallerContext {
correlationId?: number;
prefix: string;
}
export class Logger {
static level: LogLevel = LogLevel.Silent;
static output: OutputChannel | undefined;
@ -36,64 +41,153 @@ export class Logger {
}
}
static debug(message?: any, ...params: any[]): void {
static debug(message: string, ...params: any[]): void;
static debug(caller: Function, message: string, ...params: any[]): void;
static debug(callerOrMessage: Function | string, ...params: any[]): void {
if (this.level !== LogLevel.Debug && !Logger.isDebugging) return;
let message;
if (typeof callerOrMessage === 'string') {
message = callerOrMessage;
}
else {
message = params.shift();
const context = this.getCallerContext(callerOrMessage);
if (context !== undefined) {
message = `${context.prefix} ${message || ''}`;
}
}
if (Logger.isDebugging) {
console.log(this.timestamp, ConsolePrefix, message || '', ...params);
}
if (this.level !== LogLevel.Debug) return;
if (this.output !== undefined) {
if (this.output !== undefined && this.level === LogLevel.Debug) {
this.output.appendLine(`${this.timestamp} ${message || ''} ${this.toLoggableParams(true, params)}`);
}
}
static error(ex: Error, message?: string, ...params: any[]): void {
static error(ex: Error, message?: string, ...params: any[]): void;
static error(ex: Error, caller: Function, message?: string, ...params: any[]): void;
static error(ex: Error, callerOrMessage: Function | string | undefined, ...params: any[]): void {
if (this.level === LogLevel.Silent && !Logger.isDebugging) return;
let message;
if (callerOrMessage === undefined || typeof callerOrMessage === 'string') {
message = callerOrMessage;
}
else {
message = params.shift();
const context = this.getCallerContext(callerOrMessage);
if (context !== undefined) {
message = `${context.prefix} ${message || ''}`;
}
}
if (message === undefined) {
const stack = ex.stack;
if (stack) {
const match = /.*\s*?at\s(.+?)\s/.exec(stack);
if (match != null) {
message = match[1];
}
}
}
if (Logger.isDebugging) {
console.error(this.timestamp, ConsolePrefix, message || '', ...params, ex);
}
if (this.level === LogLevel.Silent) return;
if (this.output !== undefined) {
if (this.output !== undefined && this.level !== LogLevel.Silent) {
this.output.appendLine(`${this.timestamp} ${message || ''} ${this.toLoggableParams(false, params)}\n${ex}`);
}
// Telemetry.trackException(ex);
}
static log(message?: any, ...params: any[]): void {
static log(message: string, ...params: any[]): void;
static log(caller: Function, message: string, ...params: any[]): void;
static log(callerOrMessage: Function | string, ...params: any[]): void {
if (this.level !== LogLevel.Verbose && this.level !== LogLevel.Debug && !Logger.isDebugging) {
return;
}
let message;
if (typeof callerOrMessage === 'string') {
message = callerOrMessage;
}
else {
message = params.shift();
const context = this.getCallerContext(callerOrMessage);
if (context !== undefined) {
message = `${context.prefix} ${message || ''}`;
}
}
if (Logger.isDebugging) {
console.log(this.timestamp, ConsolePrefix, message || '', ...params);
}
if (this.level !== LogLevel.Verbose && this.level !== LogLevel.Debug) return;
if (this.output !== undefined) {
if (this.output !== undefined && (this.level === LogLevel.Verbose || this.level === LogLevel.Debug)) {
this.output.appendLine(`${this.timestamp} ${message || ''} ${this.toLoggableParams(false, params)}`);
}
}
static logWithDebugParams(message?: any, ...params: any[]): void {
static logWithDebugParams(message: string, ...params: any[]): void;
static logWithDebugParams(caller: Function, message: string, ...params: any[]): void;
static logWithDebugParams(callerOrMessage: Function | string, ...params: any[]): void {
if (this.level !== LogLevel.Verbose && this.level !== LogLevel.Debug && !Logger.isDebugging) {
return;
}
let message;
if (typeof callerOrMessage === 'string') {
message = callerOrMessage;
}
else {
message = params.shift();
const context = this.getCallerContext(callerOrMessage);
if (context !== undefined) {
message = `${context.prefix} ${message || ''}`;
}
}
if (Logger.isDebugging) {
console.log(this.timestamp, ConsolePrefix, message || '', ...params);
}
if (this.level !== LogLevel.Verbose && this.level !== LogLevel.Debug) return;
if (this.output !== undefined) {
if (this.output !== undefined && (this.level === LogLevel.Verbose || this.level === LogLevel.Debug)) {
this.output.appendLine(`${this.timestamp} ${message || ''} ${this.toLoggableParams(true, params)}`);
}
}
static warn(message?: any, ...params: any[]): void {
static warn(message: string, ...params: any[]): void;
static warn(caller: Function, message: string, ...params: any[]): void;
static warn(callerOrMessage: Function | string, ...params: any[]): void {
if (this.level === LogLevel.Silent && !Logger.isDebugging) return;
let message;
if (typeof callerOrMessage === 'string') {
message = callerOrMessage;
}
else {
message = params.shift();
const context = this.getCallerContext(callerOrMessage);
if (context !== undefined) {
message = `${context.prefix} ${message || ''}`;
}
}
if (Logger.isDebugging) {
console.warn(this.timestamp, ConsolePrefix, message || '', ...params);
}
if (this.level === LogLevel.Silent) return;
if (this.output !== undefined) {
if (this.output !== undefined && this.level !== LogLevel.Silent) {
this.output.appendLine(`${this.timestamp} ${message || ''} ${this.toLoggableParams(false, params)}`);
}
}
@ -104,6 +198,24 @@ export class Logger {
}
}
static toLoggableName(instance: { constructor: Function }) {
const name = instance.constructor != null ? instance.constructor.name : '';
// Strip webpack module name (since I never name classes with an _)
const index = name.indexOf('_');
return index === -1 ? name : name.substr(index + 1);
}
private static getCallerContext(caller: Function): LogCallerContext | undefined {
let context = (caller as any).$log;
if (context == null && caller.prototype != null) {
context = caller.prototype.$log;
if (context == null && caller.prototype.constructor != null) {
context = caller.prototype.constructor.$log;
}
}
return context;
}
private static get timestamp(): string {
const now = new Date();
return `[${now

+ 109
- 106
src/system/decorators.ts View File

@ -1,5 +1,5 @@
'use strict';
import { Logger, LogLevel } from '../logger';
import { LogCallerContext, Logger, LogLevel } from '../logger';
import { Functions } from './function';
import { Strings } from './string';
@ -44,10 +44,12 @@ export function logName(fn: (c: T, name: string) => string) {
export function debug<T>(
options: {
args?: boolean | { [arg: string]: (arg: any) => string };
condition?(this: any, ...args: any[]): boolean;
correlate?: boolean;
enter?(this: any, ...args: any[]): string;
exit?(this: any, result: any): string;
prefix?(this: any, context: LogContext<T>, ...args: any[]): string;
sanitize?(this: any, key: string, value: any): any;
timed?: boolean;
} = { args: true, timed: true }
) {
@ -57,6 +59,7 @@ export function debug(
export function log<T>(
options: {
args?: boolean | { [arg: string]: (arg: any) => string };
condition?(this: any, ...args: any[]): boolean;
correlate?: boolean;
debug?: boolean;
enter?(this: any, ...args: any[]): string;
@ -82,138 +85,138 @@ export function log(
fnBody.slice(fnBody.indexOf('(') + 1, fnBody.indexOf(')')).match(/([^\s,]+)/g) || [];
descriptor.value = function(this: any, ...args: any[]) {
if (Logger.level === LogLevel.Debug || (Logger.level === LogLevel.Verbose && !options.debug)) {
let instanceName: string;
if (this != null) {
instanceName = this.constructor != null ? this.constructor.name : '';
// Strip webpack module name (since I never name classes with an _)
const index = instanceName.indexOf('_');
if (index !== -1) {
instanceName = instanceName.substr(index + 1);
}
if (
(Logger.level !== LogLevel.Debug && !(Logger.level === LogLevel.Verbose && !options.debug)) ||
(typeof options.condition === 'function' && !options.condition(...args))
) {
return fn.apply(this, args);
}
if (this.constructor != null && this.constructor[LogInstanceNameFn]) {
instanceName = target.constructor[LogInstanceNameFn](this, instanceName);
}
}
else {
instanceName = '';
let instanceName: string;
if (this != null) {
instanceName = Logger.toLoggableName(this);
if (this.constructor != null && this.constructor[LogInstanceNameFn]) {
instanceName = target.constructor[LogInstanceNameFn](this, instanceName);
}
}
else {
instanceName = '';
}
let correlationId;
let prefix: string;
if (options.correlate || options.timed) {
correlationId = correlationCounter++;
if (options.correlate) {
// If we are correlating, get the class fn in order to store the correlationId if needed
(isClass ? target[key] : fn).logCorrelationId = correlationId;
}
prefix = `[${correlationId.toString(16)}] ${instanceName ? `${instanceName}.` : ''}${key}`;
}
else {
prefix = `${instanceName ? `${instanceName}.` : ''}${key}`;
}
let correlationId;
let prefix: string;
if (options.correlate || options.timed) {
correlationId = correlationCounter++;
prefix = `[${correlationId.toString(16)}] ${instanceName ? `${instanceName}.` : ''}${key}`;
}
else {
prefix = `${instanceName ? `${instanceName}.` : ''}${key}`;
}
if (options.prefix != null) {
prefix = options.prefix(
{
prefix: prefix,
instance: this,
name: key,
instanceName: instanceName,
id: correlationId
} as LogContext<T>,
...args
);
}
if (options.prefix != null) {
prefix = options.prefix(
{
prefix: prefix,
instance: this,
name: key,
instanceName: instanceName,
id: correlationId
} as LogContext<T>,
...args
);
}
if (!options.args || args.length === 0) {
if (options.enter != null) {
logFn(prefix, options.enter(...args));
}
else {
logFn(prefix);
}
// Get the class fn in order to store the current log context
(isClass ? target[key] : fn).$log = {
correlationId: correlationId,
prefix: prefix
} as LogCallerContext;
if (!options.args || args.length === 0) {
if (options.enter != null) {
logFn(prefix, options.enter(...args));
}
else {
let loggableParams = args
.map((v: any, index: number) => {
const p = parameters[index];
let loggable;
if (typeof options.args === 'object' && options.args[index]) {
loggable = options.args[index](v);
}
else {
if (typeof v === 'object') {
try {
loggable = JSON.stringify(v, options.sanitize);
}
catch {
loggable = `<error>`;
}
logFn(prefix);
}
}
else {
let loggableParams = args
.map((v: any, index: number) => {
const p = parameters[index];
let loggable;
if (typeof options.args === 'object' && options.args[index]) {
loggable = options.args[index](v);
}
else {
if (typeof v === 'object') {
try {
loggable = JSON.stringify(v, options.sanitize);
}
else {
loggable = String(v);
catch {
loggable = `<error>`;
}
}
else {
loggable = String(v);
}
}
return p ? `${p}=${loggable}` : loggable;
})
.join(', ');
if (options.enter != null) {
loggableParams = `${options.enter(...args)} ${loggableParams}`;
}
return p ? `${p}=${loggable}` : loggable;
})
.join(', ');
if (options.debug) {
Logger.debug(prefix, loggableParams);
}
else {
Logger.logWithDebugParams(prefix, loggableParams);
}
if (options.enter != null) {
loggableParams = `${options.enter(...args)} ${loggableParams}`;
}
if (options.timed || options.exit != null) {
const start = options.timed ? process.hrtime() : undefined;
const result = fn.apply(this, args);
if (options.debug) {
Logger.debug(prefix, loggableParams);
}
else {
Logger.logWithDebugParams(prefix, loggableParams);
}
}
if (result != null && Functions.isPromise(result)) {
const promise = result.then((r: any) => {
const timing =
start !== undefined ? ` \u2022 ${Strings.getDurationMilliseconds(start)} ms` : '';
let exit;
try {
exit = options.exit != null ? options.exit(r) : '';
}
catch (ex) {
exit = `@log.exit error: ${ex}`;
}
logFn(prefix, `completed${timing}${exit}`);
});
if (options.timed || options.exit != null) {
const start = options.timed ? process.hrtime() : undefined;
const result = fn.apply(this, args);
if (typeof promise.catch === 'function') {
promise.catch((ex: any) => {
const timing =
start !== undefined ? ` \u2022 ${Strings.getDurationMilliseconds(start)} ms` : '';
Logger.error(ex, prefix, `failed${timing}`);
});
}
}
else {
if (result != null && Functions.isPromise(result)) {
const promise = result.then((r: any) => {
const timing =
start !== undefined ? ` \u2022 ${Strings.getDurationMilliseconds(start)} ms` : '';
let exit;
try {
exit = options.exit !== undefined ? options.exit(result) : '';
exit = options.exit != null ? options.exit(r) : '';
}
catch (ex) {
exit = `@log.exit error: ${ex}`;
}
logFn(prefix, `completed${timing}${exit}`);
});
if (typeof promise.catch === 'function') {
promise.catch((ex: any) => {
const timing =
start !== undefined ? ` \u2022 ${Strings.getDurationMilliseconds(start)} ms` : '';
Logger.error(ex, prefix, `failed${timing}`);
});
}
return result;
}
else {
const timing = start !== undefined ? ` \u2022 ${Strings.getDurationMilliseconds(start)} ms` : '';
let exit;
try {
exit = options.exit !== undefined ? options.exit(result) : '';
}
catch (ex) {
exit = `@log.exit error: ${ex}`;
}
logFn(prefix, `completed${timing}${exit}`);
}
return result;
}
return fn.apply(this, args);

+ 1
- 0
src/ui/config.ts View File

@ -234,6 +234,7 @@ export interface RepositoriesViewConfig {
}
export interface ResultsViewConfig {
enabled: boolean;
files: ViewsFilesConfig;
location: 'explorer' | 'gitlens' | 'scm';
}

+ 1
- 1
src/views/nodes/common.ts View File

@ -163,7 +163,7 @@ export abstract class PagerNode extends ViewNode {
return {
title: 'Refresh',
command: 'gitlens.views.refreshNode',
arguments: [this._parent, this._args]
arguments: [this.parent, this._args]
} as Command;
}
}

+ 62
- 0
src/views/nodes/comparePickerNode.ts View File

@ -0,0 +1,62 @@
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { GlyphChars } from '../../constants';
import { Container } from '../../container';
import { Strings } from '../../system';
import { ResultsView } from '../resultsView';
import { ResultsNode } from './resultsNode';
import { ResourceType, unknownGitUri, ViewNode } from './viewNode';
export class ComparePickerNode extends ViewNode<ResultsView> {
constructor(
view: ResultsView,
protected readonly parent: ResultsNode
) {
super(unknownGitUri, view, parent);
}
getChildren(): ViewNode[] {
return [];
}
async getTreeItem(): Promise<TreeItem> {
const selectedRef = this.parent.selectedRef;
const repoPath = selectedRef !== undefined ? selectedRef.repoPath : undefined;
let repository = '';
if (repoPath !== undefined) {
if ((await Container.git.getRepositoryCount()) > 1) {
const repo = await Container.git.getRepository(repoPath);
repository = ` ${Strings.pad(GlyphChars.Dash, 1, 1)} ${(repo && repo.formattedName) || repoPath}`;
}
}
let item;
if (selectedRef === undefined) {
item = new TreeItem(
`Compare &lt;branch, tag, or ref&gt; to &lt;branch, tag, or ref&gt;${repository}`,
TreeItemCollapsibleState.None
);
item.contextValue = ResourceType.ComparePicker;
item.tooltip = `Click to select branch or tag for compare${GlyphChars.Ellipsis}`;
item.command = {
title: `Select branch or tag for compare${GlyphChars.Ellipsis}`,
command: 'gitlens.views.results.selectForCompare'
};
}
else {
item = new TreeItem(
`Compare ${selectedRef.label} to &lt;branch, tag, or ref&gt;${repository}`,
TreeItemCollapsibleState.None
);
item.contextValue = ResourceType.ComparePickerWithRef;
item.tooltip = `Click to compare ${selectedRef.label} to${GlyphChars.Ellipsis}`;
item.command = {
title: `Compare ${selectedRef.label} with${GlyphChars.Ellipsis}`,
command: 'gitlens.views.results.compareWithSelected'
};
}
return item;
}
}

+ 105
- 85
src/views/nodes/resultsNode.ts View File

@ -1,98 +1,50 @@
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { getRepoPathOrPrompt } from '../../commands';
import { CommandContext, GlyphChars, setCommandContext } from '../../constants';
import { GitService } from '../../git/gitService';
import { BranchesAndTagsQuickPick, CommandQuickPickItem } from '../../quickpicks';
import { debug, Functions, gate, log } from '../../system';
import { ResultsView } from '../resultsView';
import { MessageNode } from './common';
import { ResourceType, unknownGitUri, ViewNode } from './viewNode';
import { ComparePickerNode } from './comparePickerNode';
import { NamedRef, ResourceType, unknownGitUri, ViewNode } from './viewNode';
interface RepoRef {
label: string;
repoPath: string;
ref: string | NamedRef;
}
export class ResultsNode extends ViewNode<ResultsView> {
private _children: (ViewNode | MessageNode)[] = [];
private _comparePickerNode: ComparePickerNode | undefined;
constructor(view: ResultsView) {
super(unknownGitUri, view);
}
private _selectedRef: RepoRef | undefined;
get selectedRef(): RepoRef | undefined {
return this._selectedRef;
}
async getChildren(): Promise<ViewNode[]> {
if (this._children.length === 0) {
this._children = [];
// const command = {
// title: 'Search Commits',
// command: 'gitlens.showCommitSearch'
// };
// return [
// new CommandMessageNode(
// this.view,
// this,
// {
// ...command,
// arguments: [this, { searchBy: GitRepoSearchBy.Message } as ShowCommitSearchCommandArgs]
// },
// `Start a commit search by`,
// 'Click to search'
// ),
// new CommandMessageNode(
// this.view,
// this,
// {
// ...command,
// arguments: [this, { searchBy: GitRepoSearchBy.Message } as ShowCommitSearchCommandArgs]
// },
// `${GlyphChars.Space.repeat(4)} message ${Strings.pad(GlyphChars.Dash, 1, 1)} use <message-pattern>`,
// 'Click to search by message'
// ),
// new CommandMessageNode(
// this.view,
// this,
// {
// ...command,
// arguments: [this, { searchBy: GitRepoSearchBy.Author } as ShowCommitSearchCommandArgs]
// },
// `${GlyphChars.Space.repeat(4)} author ${Strings.pad(GlyphChars.Dash, 1, 1)} use @<author-pattern>`,
// 'Click to search by author'
// ),
// new CommandMessageNode(
// this.view,
// this,
// {
// ...command,
// arguments: [this, { searchBy: GitRepoSearchBy.Sha } as ShowCommitSearchCommandArgs]
// },
// `${GlyphChars.Space.repeat(4)} commit id ${Strings.pad(GlyphChars.Dash, 1, 1)} use #<sha>`,
// 'Click to search by commit id'
// ),
// new CommandMessageNode(
// this.view,
// this,
// {
// ...command,
// arguments: [this, { searchBy: GitRepoSearchBy.Files } as ShowCommitSearchCommandArgs]
// },
// `${GlyphChars.Space.repeat(4)} files ${Strings.pad(GlyphChars.Dash, 1, 1)} use :<file-pattern>`,
// 'Click to search by files'
// ),
// new CommandMessageNode(
// this.view,
// this,
// {
// ...command,
// arguments: [this, { searchBy: GitRepoSearchBy.Changes } as ShowCommitSearchCommandArgs]
// },
// `${GlyphChars.Space.repeat(4)} changes ${Strings.pad(GlyphChars.Dash, 1, 1)} use =<pattern>`,
// 'Click to search by changes'
// ),
// new CommandMessageNode(
// this.view,
// this,
// {
// ...command,
// arguments: [this, { searchBy: GitRepoSearchBy.ChangedLines } as ShowCommitSearchCommandArgs]
// },
// `${GlyphChars.Space.repeat(4)} changed lines ${Strings.pad(GlyphChars.Dash, 1, 1)} use ~<pattern>`,
// 'Click to search by changed lines'
// )
// ];
// Not really sure why I can't reuse this node -- but if I do the Tree errors out with an id already exists error
this._comparePickerNode = new ComparePickerNode(this.view, this);
this._children = [this._comparePickerNode];
}
else if (
this._selectedRef !== undefined &&
(this._comparePickerNode === undefined || !this._children.includes(this._comparePickerNode))
) {
// Not really sure why I can't reuse this node -- but if I do the Tree errors out with an id already exists error
this._comparePickerNode = new ComparePickerNode(this.view, this);
this._children.splice(0, 0, this._comparePickerNode);
const node = this._comparePickerNode;
setImmediate(() => this.view.reveal(node, { focus: false, select: true }));
}
return this._children;
@ -112,6 +64,13 @@ export class ResultsNode extends ViewNode {
this._children.push(results);
}
else {
if (this._comparePickerNode !== undefined) {
const index = this._children.indexOf(this._comparePickerNode);
if (index !== -1) {
this._children.splice(index, 1);
}
}
this._children.splice(0, 0, results);
}
@ -120,7 +79,8 @@ export class ResultsNode extends ViewNode {
@log()
clear() {
if (this._children.length === 0) return;
this._selectedRef = undefined;
setCommandContext(CommandContext.ViewsCanCompare, false);
this._children.length = 0;
this.view.triggerNodeChange();
@ -130,12 +90,15 @@ export class ResultsNode extends ViewNode {
args: { 0: (n: ViewNode) => n.toString() }
})
dismiss(node: ViewNode) {
if (this._children.length === 0) return;
this._selectedRef = undefined;
setCommandContext(CommandContext.ViewsCanCompare, false);
const index = this._children.findIndex(n => n === node);
if (index === -1) return;
if (this._children.length !== 0) {
const index = this._children.indexOf(node);
if (index === -1) return;
this._children.splice(index, 1);
this._children.splice(index, 1);
}
this.view.triggerNodeChange();
}
@ -146,4 +109,61 @@ export class ResultsNode extends ViewNode {
await Promise.all(this._children.map(c => c.refresh()).filter(Functions.isPromise) as Promise<any>[]);
}
async compareWithSelected(repoPath?: string, ref?: string | NamedRef) {
if (this._selectedRef === undefined) return;
if (repoPath === undefined) {
repoPath = this._selectedRef.repoPath;
}
else if (repoPath !== this._selectedRef.repoPath) {
// If we don't have a matching repoPath, then start over
this.selectForCompare(repoPath, ref);
return;
}
if (ref === undefined) {
const pick = await new BranchesAndTagsQuickPick(repoPath).show(
`Compare ${this.getRefName(this._selectedRef.ref)} to${GlyphChars.Ellipsis}`
);
if (pick === undefined || pick instanceof CommandQuickPickItem) return;
ref = pick.name;
}
const ref1 = this._selectedRef;
this._selectedRef = undefined;
setCommandContext(CommandContext.ViewsCanCompare, false);
void (await this.view.compare(repoPath, ref1.ref, ref));
}
async selectForCompare(repoPath?: string, ref?: string | NamedRef) {
if (repoPath === undefined) {
repoPath = await getRepoPathOrPrompt(
undefined,
`Select branch or tag in which repository${GlyphChars.Ellipsis}`
);
}
if (repoPath === undefined) return;
if (ref === undefined) {
const pick = await new BranchesAndTagsQuickPick(repoPath).show(
`Select branch or tag for compare${GlyphChars.Ellipsis}`
);
if (pick === undefined || pick instanceof CommandQuickPickItem) return;
ref = pick.name;
}
this._selectedRef = { label: this.getRefName(ref), repoPath: repoPath, ref: ref };
setCommandContext(CommandContext.ViewsCanCompare, true);
void (await this.triggerChange());
}
private getRefName(ref: string | NamedRef) {
return typeof ref === 'string' ? GitService.shortenSha(ref)! : ref.label || GitService.shortenSha(ref.ref)!;
}
}

+ 7
- 4
src/views/nodes/viewNode.ts View File

@ -1,6 +1,7 @@
'use strict';
import { Command, Disposable, Event, TreeItem, TreeItemCollapsibleState, TreeViewVisibilityChangeEvent } from 'vscode';
import { GitUri } from '../../git/gitService';
import { Logger } from '../../logger';
import { debug, gate, logName } from '../../system';
import { RefreshReason, TreeViewNodeStateChangeEvent, View } from '../viewBase';
@ -18,6 +19,8 @@ export enum ResourceType {
CommitOnCurrentBranch = 'gitlens:commit:current',
CommitFile = 'gitlens:file:commit',
Commits = 'gitlens:commits',
ComparePicker = 'gitlens:compare:picker',
ComparePickerWithRef = 'gitlens:compare:picker:ref',
ComparisonResults = 'gitlens:results:comparison',
FileHistory = 'gitlens:history:file',
FileStaged = 'gitlens:file:staged',
@ -64,13 +67,13 @@ export abstract class ViewNode {
constructor(
uri: GitUri,
public readonly view: TView,
protected readonly _parent?: ViewNode
protected readonly parent?: ViewNode
) {
this._uri = uri;
}
toString() {
return `${this.constructor.name}${this.id != null ? `(${this.id})` : ''}`;
return `${Logger.toLoggableName(this)}${this.id != null ? `(${this.id})` : ''}`;
}
protected _uri: GitUri;
@ -81,7 +84,7 @@ export abstract class ViewNode {
abstract getChildren(): ViewNode[] | Promise<ViewNode[]>;
getParent(): ViewNode | undefined {
return this._parent;
return this.parent;
}
abstract getTreeItem(): TreeItem | Promise<TreeItem>;
@ -198,7 +201,7 @@ export abstract class SubscribeableViewNode extends V
this._state = e.state;
this.onStateChanged(e.state);
}
else if (e.element === this._parent) {
else if (e.element === this.parent) {
this.onParentStateChanged(e.state);
}
}

+ 15
- 20
src/views/resultsView.ts View File

@ -23,8 +23,7 @@ export class ResultsView extends ViewBase {
protected registerCommands() {
void Container.viewCommands;
// commands.registerCommand(this.getQualifiedCommand('clear'), () => this.clear(), this);
commands.registerCommand(this.getQualifiedCommand('close'), () => this.close(), this);
commands.registerCommand(this.getQualifiedCommand('clear'), () => this.clear(), this);
commands.registerCommand(this.getQualifiedCommand('refresh'), () => this.refresh(), this);
commands.registerCommand(
this.getQualifiedCommand('setFilesLayoutToAuto'),
@ -48,6 +47,9 @@ export class ResultsView extends ViewBase {
this
);
commands.registerCommand(this.getQualifiedCommand('swapComparision'), this.swapComparision, this);
commands.registerCommand(this.getQualifiedCommand('selectForCompare'), this.selectForCompare, this);
commands.registerCommand(this.getQualifiedCommand('compareWithSelected'), this.compareWithSelected, this);
}
protected onConfigurationChanged(e: ConfigurationChangeEvent) {
@ -60,7 +62,7 @@ export class ResultsView extends ViewBase {
}
if (configuration.changed(e, configuration.name('views')('results')('location').value)) {
this.initialize(this.config.location);
this.initialize(this.config.location /*, { showCollapseAll: true } */);
}
if (!configuration.initializing(e) && this._root !== undefined) {
@ -72,11 +74,6 @@ export class ResultsView extends ViewBase {
return { ...Container.config.views, ...Container.config.views.results };
}
private _enabled: boolean = false;
get enabled(): boolean {
return this._enabled;
}
get keepResults(): boolean {
return Container.context.workspaceState.get<boolean>(WorkspaceState.ViewsResultsKeepResults, false);
}
@ -87,15 +84,6 @@ export class ResultsView extends ViewBase {
this._root.clear();
}
close() {
if (this._root === undefined) return;
this._root.clear();
this._enabled = false;
setCommandContext(CommandContext.ViewsResults, false);
}
dismissNode(node: ViewNode) {
if (this._root === undefined) return;
@ -113,13 +101,20 @@ export class ResultsView extends ViewBase {
);
}
compareWithSelected(repoPath?: string, ref?: string | NamedRef) {
const root = this.ensureRoot();
void root.compareWithSelected(repoPath, ref);
}
selectForCompare(repoPath?: string, ref?: string | NamedRef) {
const root = this.ensureRoot();
void root.selectForCompare(repoPath, ref);
}
private async addResults(results: ViewNode) {
const root = this.ensureRoot();
root.addOrReplace(results, !this.keepResults);
this._enabled = true;
await setCommandContext(CommandContext.ViewsResults, true);
setTimeout(() => this._tree!.reveal(results, { select: true }), 250);
}

+ 38
- 24
src/views/viewCommands.ts View File

@ -91,8 +91,10 @@ export class ViewCommands implements Disposable {
commands.registerCommand('gitlens.views.compareWithHead', this.compareWithHead, this);
commands.registerCommand('gitlens.views.compareWithRemote', this.compareWithRemote, this);
commands.registerCommand('gitlens.views.compareWithSelected', this.compareWithSelected, this);
commands.registerCommand('gitlens.views.compareWithWorking', this.compareWithWorking, this);
commands.registerCommand('gitlens.views.selectForCompare', this.selectForCompare, this);
commands.registerCommand('gitlens.views.compareFileWithSelected', this.compareFileWithSelected, this);
commands.registerCommand('gitlens.views.selectFileForCompare', this.selectFileForCompare, this);
commands.registerCommand('gitlens.views.compareWithWorking', this.compareWithWorking, this);
commands.registerCommand('gitlens.views.terminalCheckoutBranch', this.terminalCheckoutBranch, this);
commands.registerCommand('gitlens.views.terminalCreateBranch', this.terminalCreateBranch, this);
@ -191,42 +193,54 @@ export class ViewCommands implements Disposable {
}
private compareWithSelected(node: ViewNode) {
if (this._selection === undefined || !(node instanceof ViewRefNode)) return;
if (this._selection.repoPath !== node.repoPath) return;
if (!(node instanceof ViewRefNode)) return;
if (this._selection.uri !== undefined) {
if (!(node instanceof CommitFileNode)) return;
Container.resultsView.compareWithSelected(node.repoPath, node.ref);
}
const diffArgs: DiffWithCommandArgs = {
repoPath: this._selection.repoPath,
lhs: {
sha: this._selection.ref,
uri: this._selection.uri!
},
rhs: {
sha: node.ref,
uri: node.uri
}
};
commands.executeCommand(Commands.DiffWith, diffArgs);
private selectForCompare(node: ViewNode) {
if (!(node instanceof ViewRefNode)) return;
Container.resultsView.selectForCompare(node.repoPath, node.ref);
}
private compareFileWithSelected(node: ViewNode) {
if (this._selectedFile === undefined || !(node instanceof CommitFileNode)) return;
if (this._selectedFile.repoPath !== node.repoPath) {
this.selectFileForCompare(node);
return;
}
return Container.resultsView.compare(this._selection.repoPath, this._selection.ref, node.ref);
const selected = this._selectedFile;
this._selectedFile = undefined;
setCommandContext(CommandContext.ViewsCanCompareFile, false);
const diffArgs: DiffWithCommandArgs = {
repoPath: selected.repoPath,
lhs: {
sha: selected.ref,
uri: selected.uri!
},
rhs: {
sha: node.ref,
uri: node.uri
}
};
return commands.executeCommand(Commands.DiffWith, diffArgs);
}
private _selection: ICompareSelected | undefined;
private _selectedFile: ICompareSelected | undefined;
private selectForCompare(node: ViewNode) {
if (!(node instanceof ViewRefNode)) return;
private selectFileForCompare(node: ViewNode) {
if (!(node instanceof CommitFileNode)) return;
this._selection = {
this._selectedFile = {
ref: node.ref,
repoPath: node.repoPath,
uri: node instanceof CommitFileNode ? node.uri : undefined
uri: node.uri
};
setCommandContext(CommandContext.ViewsCanCompare, true);
setCommandContext(CommandContext.ViewsCanCompareFile, true);
}
private exploreRepoRevision(node: ViewRefNode, options: { openInNewWindow?: boolean } = {}) {

Loading…
Cancel
Save