Skip to content

Commit

Permalink
Use ScmTreeWidget in both commit details and in the diff widget
Browse files Browse the repository at this point in the history
Signed-off-by: Nigel Westbury <nigelipse@miegel.org>
  • Loading branch information
westbury committed Jun 24, 2020
1 parent 3eb0678 commit 9a45e5e
Show file tree
Hide file tree
Showing 17 changed files with 975 additions and 681 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Breaking Changes:

- [task] Widened the scope of some methods in TaskManager and TaskConfigurations from string to TaskConfigurationScope. This is only breaking for extenders, not callers. [#7928](https://github.com/eclipse-theia/theia/pull/7928)
- [shell] `ApplicationShell.TrackableWidgetProvider.getTrackableWidgets` is sync to register child widgets in the same tick, use `ApplicationShell.TrackableWidgetProvider.onDidChangeTrackableWidgets` if child widgets are added async
- [git] the changes in the commit details (opened from the history view) and in the diff view (opened with 'Compare With...' on a folder's context menu) are now switchable between 'list' and 'tree' modes

## v1.2.0

Expand Down
52 changes: 28 additions & 24 deletions packages/git/src/browser/diff/git-diff-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { FrontendApplication, AbstractViewContribution } from '@theia/core/lib/b
import { WidgetManager } from '@theia/core/lib/browser/widget-manager';
import { injectable, inject } from 'inversify';
import { GitDiffWidget, GIT_DIFF } from './git-diff-widget';
import { GitDiffTreeModel } from './git-diff-tree-model';
import { ScmService } from '@theia/scm/lib/browser/scm-service';
import { open, OpenerService } from '@theia/core/lib/browser';
import { NavigatorContextMenu, FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
Expand Down Expand Up @@ -88,31 +89,34 @@ export class GitDiffContribution extends AbstractViewContribution<GitDiffWidget>
isVisible: uri => !!this.findGitRepository(uri),
isEnabled: uri => !!this.findGitRepository(uri),
execute: async fileUri => {
await this.quickOpenService.chooseTagsAndBranches(
async (fromRevision, toRevision) => {
const uri = fileUri.toString();
const fileStat = await this.fileSystem.getFileStat(uri);
const options: Git.Options.Diff = {
uri,
range: {
fromRevision
}
};
if (fileStat) {
if (fileStat.isDirectory) {
this.showWidget(options);
} else {
const fromURI = fileUri.withScheme(GIT_RESOURCE_SCHEME).withQuery(fromRevision);
const toURI = fileUri;
const diffUri = DiffUris.encode(fromURI, toURI);
if (diffUri) {
open(this.openerService, diffUri).catch(e => {
this.notifications.error(e.message);
});
const repository = this.findGitRepository(fileUri);
if (repository) {
await this.quickOpenService.chooseTagsAndBranches(
async (fromRevision, toRevision) => {
const uri = fileUri.toString();
const fileStat = await this.fileSystem.getFileStat(uri);
const diffOptions: Git.Options.Diff = {
uri,
range: {
fromRevision
}
};
if (fileStat) {
if (fileStat.isDirectory) {
this.showWidget({ rootUri: repository.localUri, diffOptions });
} else {
const fromURI = fileUri.withScheme(GIT_RESOURCE_SCHEME).withQuery(fromRevision);
const toURI = fileUri;
const diffUri = DiffUris.encode(fromURI, toURI);
if (diffUri) {
open(this.openerService, diffUri).catch(e => {
this.notifications.error(e.message);
});
}
}
}
}
}, this.findGitRepository(fileUri));
}, repository);
}
}
}));
}
Expand All @@ -134,7 +138,7 @@ export class GitDiffContribution extends AbstractViewContribution<GitDiffWidget>
return undefined;
}

async showWidget(options: Git.Options.Diff): Promise<GitDiffWidget> {
async showWidget(options: GitDiffTreeModel.Options): Promise<GitDiffWidget> {
const widget = await this.widget;
await widget.setContent(options);
return this.openView({
Expand Down
19 changes: 15 additions & 4 deletions packages/git/src/browser/diff/git-diff-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,30 @@

import { interfaces } from 'inversify';
import { GitDiffContribution } from './git-diff-contribution';
import { WidgetFactory, bindViewContribution } from '@theia/core/lib/browser';
import { WidgetFactory, bindViewContribution, TreeModel } from '@theia/core/lib/browser';
import { GitDiffWidget, GIT_DIFF } from './git-diff-widget';
import { GitDiffHeaderWidget } from './git-diff-header-widget';
import { GitDiffTreeModel } from './git-diff-tree-model';
import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar';

import { ScmTreeModel } from '@theia/scm/lib/browser/scm-tree-model';
import { createScmTreeContainer } from '@theia/scm/lib/browser/scm-frontend-module';
import '../../../src/browser/style/diff.css';

export function bindGitDiffModule(bind: interfaces.Bind): void {

bind(GitDiffWidget).toSelf();
bind(WidgetFactory).toDynamicValue(ctx => ({
id: GIT_DIFF,
createWidget: () => ctx.container.get<GitDiffWidget>(GitDiffWidget)
}));
createWidget: () => {
const child = createScmTreeContainer(ctx.container);
child.bind(TreeModel).toService(GitDiffTreeModel);

child.bind(GitDiffHeaderWidget).toSelf();
child.bind(GitDiffTreeModel).toSelf();
child.bind(ScmTreeModel).toService(GitDiffTreeModel);
return child.get(GitDiffWidget);
}
})).inSingletonScope();

bindViewContribution(bind, GitDiffContribution);
bind(TabBarToolbarContribution).toService(GitDiffContribution);
Expand Down
159 changes: 159 additions & 0 deletions packages/git/src/browser/diff/git-diff-header-widget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/********************************************************************************
* Copyright (C) 2018 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { injectable, inject } from 'inversify';
import URI from '@theia/core/lib/common/uri';
import { ScmService } from '@theia/scm/lib/browser/scm-service';
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
import { ScmFileChangeLabelProvider } from '@theia/scm-extra/lib/browser/scm-file-change-label-provider';
import { ReactWidget, StatefulWidget, KeybindingRegistry } from '@theia/core/lib/browser';
import { Git } from '../../common';
import * as React from 'react';

/* eslint-disable no-null/no-null */

@injectable()
export class GitDiffHeaderWidget extends ReactWidget implements StatefulWidget {

@inject(KeybindingRegistry) protected readonly keybindings: KeybindingRegistry;
@inject(ScmService) protected readonly scmService: ScmService;
@inject(LabelProvider) protected readonly labelProvider: LabelProvider;
@inject(ScmFileChangeLabelProvider) protected readonly scmLabelProvider: ScmFileChangeLabelProvider;

protected options: Git.Options.Diff;

protected authorAvatar: string;

constructor(
) {
super();
this.id = 'git-diff-header';
this.title.closable = true;
this.title.iconClass = 'icon-git-commit tab-git-icon';
}

async setContent(options: Git.Options.Diff): Promise<void> {
this.options = options;
this.update();
}

protected render(): React.ReactNode {
return React.createElement('div', this.createContainerAttributes(), this.renderDiffListHeader());
}

/**
* Create the container attributes for the widget.
*/
protected createContainerAttributes(): React.HTMLAttributes<HTMLElement> {
return {
style: { flexGrow: 0 }
};
}

protected renderDiffListHeader(): React.ReactNode {
return this.doRenderDiffListHeader(
this.renderRepositoryHeader(),
this.renderPathHeader(),
this.renderRevisionHeader(),
);
}

protected doRenderDiffListHeader(...children: React.ReactNode[]): React.ReactNode {
return <div className='diff-header'>{...children}</div>;
}

protected renderRepositoryHeader(): React.ReactNode {
if (this.options && this.options.uri) {
return this.renderHeaderRow({ name: 'repository', value: this.getRepositoryLabel(this.options.uri) });
}
return undefined;
}

protected getRepositoryLabel(uri: string): string | undefined {
const repository = this.scmService.findRepository(new URI(uri));
const isSelectedRepo = this.scmService.selectedRepository && repository && this.scmService.selectedRepository.provider.rootUri === repository.provider.rootUri;
return repository && !isSelectedRepo ? this.labelProvider.getLongName(new URI(repository.provider.rootUri)) : undefined;
}

protected renderPathHeader(): React.ReactNode {
return this.renderHeaderRow({
classNames: ['diff-header'],
name: 'path',
value: this.renderPath()
});
}
protected renderPath(): React.ReactNode {
if (this.options.uri) {
const path = this.scmLabelProvider.relativePath(this.options.uri);
if (path.length > 0) {
return '/' + path;
} else {
return this.labelProvider.getLongName(new URI(this.options.uri));
}
}
return null;
}

protected renderRevisionHeader(): React.ReactNode {
return this.renderHeaderRow({
classNames: ['diff-header'],
name: 'revision: ',
value: this.renderRevision()
});
}
protected renderRevision(): React.ReactNode {
if (!this.fromRevision) {
return null;
}
if (typeof this.fromRevision === 'string') {
return this.fromRevision;
}
return (this.toRevision || 'HEAD') + '~' + this.fromRevision;
}

protected renderHeaderRow({ name, value, classNames, title }: { name: string, value: React.ReactNode, classNames?: string[], title?: string }): React.ReactNode {
if (!value) {
return;
}
const className = ['header-row', ...(classNames || [])].join(' ');
return <div key={name} className={className} title={title}>
<div className='theia-header'>{name}</div>
<div className='header-value'>{value}</div>
</div>;
}

protected get toRevision(): string | undefined {
return this.options.range && this.options.range.toRevision;
}

protected get fromRevision(): string | number | undefined {
return this.options.range && this.options.range.fromRevision;
}

storeState(): object {
const { options } = this;
return {
options
};
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
restoreState(oldState: any): void {
const options = oldState['options'];
this.setContent(options);
}

}
Loading

0 comments on commit 9a45e5e

Please sign in to comment.