Browse Source

Adds real-time preview of format strings

main
Eric Amodio 4 years ago
parent
commit
61e616ce6e
9 changed files with 239 additions and 10 deletions
  1. +1
    -1
      src/webviews/apps/scss/popup.scss
  2. +9
    -0
      src/webviews/apps/settings/partials/blame.html
  3. +14
    -0
      src/webviews/apps/settings/partials/current-line.html
  4. +9
    -0
      src/webviews/apps/settings/partials/status-bar.html
  5. +34
    -1
      src/webviews/apps/settings/settings.html
  6. +69
    -4
      src/webviews/apps/shared/appWithConfigBase.ts
  7. +9
    -3
      src/webviews/apps/shared/dom.ts
  8. +21
    -0
      src/webviews/protocol.ts
  9. +73
    -1
      src/webviews/webviewBase.ts

+ 1
- 1
src/webviews/apps/scss/popup.scss View File

@ -3,7 +3,7 @@
cursor: default; cursor: default;
padding: 1em; padding: 1em;
position: absolute; position: absolute;
top: 46px;
top: 72px;
width: 80vw; width: 80vw;
min-width: 373px; min-width: 373px;
max-width: 472px; max-width: 472px;

+ 9
- 0
src/webviews/apps/settings/partials/blame.html View File

@ -58,6 +58,7 @@
type="text" type="text"
placeholder="${message|50?} ${agoOrDate|14-}" placeholder="${message|50?} ${agoOrDate|14-}"
data-setting data-setting
data-setting-preview
data-default-value="${message|50?} ${agoOrDate|14-}" data-default-value="${message|50?} ${agoOrDate|14-}"
data-popup-trigger data-popup-trigger
/> />
@ -66,6 +67,14 @@
</label> </label>
</div> </div>
<div id="blame.format.popup" class="popup hidden"></div> <div id="blame.format.popup" class="popup hidden"></div>
<span class="setting__hint"
>Example:
<span
data-setting-preview="blame.format.format"
data-setting-preview-type="commit"
data-setting-preview-default="${message|50?} ${agoOrDate|14-}"
></span>
</span>
</div> </div>
<div class="setting"> <div class="setting">

+ 14
- 0
src/webviews/apps/settings/partials/current-line.html View File

@ -66,6 +66,7 @@
type="text" type="text"
placeholder="${author, }${agoOrDate}${' via 'pullRequest}${ • message|50?}" placeholder="${author, }${agoOrDate}${' via 'pullRequest}${ • message|50?}"
data-setting data-setting
data-setting-preview
data-default-value="${author, }${agoOrDate}${' via 'pullRequest}${ • message|50?}" data-default-value="${author, }${agoOrDate}${' via 'pullRequest}${ • message|50?}"
data-popup-trigger data-popup-trigger
disabled disabled
@ -75,6 +76,14 @@
</label> </label>
</div> </div>
<div id="currentLine.format.popup" class="popup hidden"></div> <div id="currentLine.format.popup" class="popup hidden"></div>
<span class="setting__hint"
>Example:
<span
data-setting-preview="currentLine.format"
data-setting-preview-type="commit"
data-setting-preview-default="${author, }${agoOrDate}${' via 'pullRequest}${ • message|50?}"
></span>
</span>
</div> </div>
<div class="setting" data-enablement="currentLine.enabled"> <div class="setting" data-enablement="currentLine.enabled">
@ -106,6 +115,11 @@
/> />
<img <img
class="image__preview--overlay hidden" class="image__preview--overlay hidden"
src="#{root}/images/settings/current-line-blame-on+pr.png"
data-visibility="currentLine.enabled &amp; currentLine.pullRequests.enabled"
/>
<img
class="image__preview--overlay hidden"
src="#{root}/images/settings/current-line-blame-on-scrollable.png" src="#{root}/images/settings/current-line-blame-on-scrollable.png"
data-visibility="currentLine.enabled &amp; currentLine.scrollable" data-visibility="currentLine.enabled &amp; currentLine.scrollable"
/> />

+ 9
- 0
src/webviews/apps/settings/partials/status-bar.html View File

@ -48,6 +48,7 @@
type="text" type="text"
placeholder="${author}, ${agoOrDate}${' via 'pullRequest}" placeholder="${author}, ${agoOrDate}${' via 'pullRequest}"
data-setting data-setting
data-setting-preview
data-default-value="${author}, ${agoOrDate}${' via 'pullRequest}" data-default-value="${author}, ${agoOrDate}${' via 'pullRequest}"
data-popup-trigger data-popup-trigger
disabled disabled
@ -57,6 +58,14 @@
</label> </label>
</div> </div>
<div id="statusBar.format.popup" class="popup hidden"></div> <div id="statusBar.format.popup" class="popup hidden"></div>
<span class="setting__hint"
>Example:
<span
data-setting-preview="statusBar.format"
data-setting-preview-type="commit"
data-setting-preview-default="${author}, ${agoOrDate}${' via 'pullRequest}"
></span>
</span>
</div> </div>
<div class="setting" data-enablement="statusBar.enabled"> <div class="setting" data-enablement="statusBar.enabled">

+ 34
- 1
src/webviews/apps/settings/settings.html View File

@ -10,7 +10,7 @@
<table class="token-popup__table"> <table class="token-popup__table">
<tbody> <tbody>
<tr> <tr>
<td>Commit Id</td>
<td>Commit SHA</td>
<td><span class="token" data-token="id">id</span></td> <td><span class="token" data-token="id">id</span></td>
</tr> </tr>
<tr> <tr>
@ -18,6 +18,10 @@
<td><span class="token" data-token="author">author</span></td> <td><span class="token" data-token="author">author</span></td>
</tr> </tr>
<tr> <tr>
<td>Commit Author (except you)</td>
<td><span class="token" data-token="authorNotYou">authorNotYou</span></td>
</tr>
<tr>
<td>Commit Author E-mail</td> <td>Commit Author E-mail</td>
<td><span class="token" data-token="email">email</span></td> <td><span class="token" data-token="email">email</span></td>
</tr> </tr>
@ -51,6 +55,15 @@
<td><span class="token" data-token="agoOrDate">agoOrDate</span></td> <td><span class="token" data-token="agoOrDate">agoOrDate</span></td>
</tr> </tr>
<tr> <tr>
<td>
Commit or Authored Date (Short)<br /><i
>Short relative or absolute based on date setting<br />Committed vs Authored based on
setting</i
>
</td>
<td><span class="token" data-token="agoOrDateShort">agoOrDateShort</span></td>
</tr>
<tr>
<td>Authored Date<br /><i>Relative date</i></td> <td>Authored Date<br /><i>Relative date</i></td>
<td><span class="token" data-token="authorAgo">authorAgo</span></td> <td><span class="token" data-token="authorAgo">authorAgo</span></td>
</tr> </tr>
@ -63,6 +76,10 @@
<td><span class="token" data-token="authorAgoOrDate">authorAgoOrDate</span></td> <td><span class="token" data-token="authorAgoOrDate">authorAgoOrDate</span></td>
</tr> </tr>
<tr> <tr>
<td>Authored Date (Short)<br /><i>Short relative or absolute date based on date setting</i></td>
<td><span class="token" data-token="authorAgoOrDateShort">authorAgoOrDateShort</span></td>
</tr>
<tr>
<td>Commit Date<br /><i>Relative date</i></td> <td>Commit Date<br /><i>Relative date</i></td>
<td><span class="token" data-token="committerAgo">committerAgo</span></td> <td><span class="token" data-token="committerAgo">committerAgo</span></td>
</tr> </tr>
@ -75,6 +92,22 @@
<td><span class="token" data-token="committerAgoOrDate">committerAgoOrDate</span></td> <td><span class="token" data-token="committerAgoOrDate">committerAgoOrDate</span></td>
</tr> </tr>
<tr> <tr>
<td>Commit Date (Short)<br /><i>Short relative or absolute date based on date setting</i></td>
<td><span class="token" data-token="committerAgoOrDateShort">committerAgoOrDateShort</span></td>
</tr>
<tr>
<td>Pull Request<br /><i>Pull request (if any) that introduced the commit</i></td>
<td><span class="token" data-token="pullRequest">pullRequest</span></td>
</tr>
<tr>
<td>
Pull Request State<br /><i
>State (open, merged, closed) of the pull request (if any) that introduced the commit</i
>
</td>
<td><span class="token" data-token="pullRequestState">pullRequestState</span></td>
</tr>
<tr>
<td>Branch & Tag Tips<br /><i>Indicates if the commit is a tip of any branches or tags</i></td> <td>Branch & Tag Tips<br /><i>Indicates if the commit is a tip of any branches or tags</i></td>
<td><span class="token" data-token="tips">tips</span></td> <td><span class="token" data-token="tips">tips</span></td>
</tr> </tr>

+ 69
- 4
src/webviews/apps/shared/appWithConfigBase.ts View File

@ -3,8 +3,10 @@
import { import {
AppStateWithConfig, AppStateWithConfig,
DidChangeConfigurationNotificationType, DidChangeConfigurationNotificationType,
DidPreviewConfigurationNotificationType,
IpcMessage, IpcMessage,
onIpcNotification, onIpcNotification,
PreviewConfigurationCommandType,
UpdateConfigurationCommandType, UpdateConfigurationCommandType,
} from '../../protocol'; } from '../../protocol';
import { App } from './appBase'; import { App } from './appBase';
@ -13,6 +15,17 @@ import { getDateFormatter } from '../shared/date';
const dateFormatter = getDateFormatter(new Date('Wed Jul 25 2018 19:18:00 GMT-0400')); const dateFormatter = getDateFormatter(new Date('Wed Jul 25 2018 19:18:00 GMT-0400'));
let ipcSequence = 0;
function nextIpcId() {
if (ipcSequence === Number.MAX_SAFE_INTEGER) {
ipcSequence = 1;
} else {
ipcSequence++;
}
return `${ipcSequence}`;
}
export abstract class AppWithConfig<TState extends AppStateWithConfig> extends App<TState> { export abstract class AppWithConfig<TState extends AppStateWithConfig> extends App<TState> {
private _changes = Object.create(null) as Record<string, any>; private _changes = Object.create(null) as Record<string, any>;
private _updating: boolean = false; private _updating: boolean = false;
@ -213,8 +226,6 @@ export abstract class AppWithConfig extends A
} }
protected onPopupMouseDown(element: HTMLElement, e: MouseEvent) { protected onPopupMouseDown(element: HTMLElement, e: MouseEvent) {
// e.stopPropagation();
// e.stopImmediatePropagation();
e.preventDefault(); e.preventDefault();
const el = e.target as HTMLElement; const el = e.target as HTMLElement;
@ -234,7 +245,25 @@ export abstract class AppWithConfig extends A
const input = setting.querySelector<HTMLInputElement>('input[type=text], input:not([type])'); const input = setting.querySelector<HTMLInputElement>('input[type=text], input:not([type])');
if (input == null) return; if (input == null) return;
input.value += `\${${element.dataset.token}}`;
const token = `\${${element.dataset.token}}`;
let selectionStart = input.selectionStart;
if (selectionStart != null) {
input.value = `${input.value.substring(0, selectionStart)}${token}${input.value.substr(
input.selectionEnd ?? selectionStart,
)}`;
selectionStart += token.length;
} else {
selectionStart = input.value.length;
}
input.focus();
input.setSelectionRange(selectionStart, selectionStart);
if (selectionStart === input.value.length) {
input.scrollLeft = input.scrollWidth;
}
setTimeout(() => input.focus(), 250);
e.stopPropagation(); e.stopPropagation();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
@ -363,7 +392,7 @@ export abstract class AppWithConfig extends A
private updatePreview(el: HTMLSpanElement, value?: string) { private updatePreview(el: HTMLSpanElement, value?: string) {
switch (el.dataset.settingPreviewType) { switch (el.dataset.settingPreviewType) {
case 'date':
case 'date': {
if (value === undefined) { if (value === undefined) {
value = this.getSettingValue<string>(el.dataset.settingPreview!); value = this.getSettingValue<string>(el.dataset.settingPreview!);
} }
@ -374,7 +403,43 @@ export abstract class AppWithConfig extends A
el.innerText = value == null ? '' : dateFormatter.format(value); el.innerText = value == null ? '' : dateFormatter.format(value);
break; break;
}
case 'commit': {
if (value === undefined) {
value = this.getSettingValue<string>(el.dataset.settingPreview!);
}
if (value == null || value.length === 0) {
value = el.dataset.settingPreviewDefault;
}
if (value == null) {
el.innerText = '';
return;
}
const id = nextIpcId();
const disposable = DOM.on(window, 'message', (e: MessageEvent) => {
const msg = e.data as IpcMessage;
if (msg.method === DidPreviewConfigurationNotificationType.method && msg.params.id === id) {
disposable.dispose();
onIpcNotification(DidPreviewConfigurationNotificationType, msg, params => {
el.innerText = params.preview ?? '';
});
}
});
this.sendCommand(PreviewConfigurationCommandType, {
key: el.dataset.settingPreview!,
type: 'commit',
id: id,
format: value,
});
break;
}
default: default:
break; break;
} }

+ 9
- 3
src/webviews/apps/shared/dom.ts View File

@ -19,10 +19,16 @@ export namespace DOM {
listener: (this: T, ev: DocumentEventMap[K]) => any, listener: (this: T, ev: DocumentEventMap[K]) => any,
options?: boolean | AddEventListenerOptions, options?: boolean | AddEventListenerOptions,
): Disposable; ): Disposable;
export function on<K extends keyof DocumentEventMap, T extends Element>(
selectorOrElement: string | Document | Element,
export function on<K extends keyof WindowEventMap, T extends Element>(
el: Window,
name: K, name: K,
listener: (this: T, ev: DocumentEventMap[K]) => any,
listener: (this: T, ev: WindowEventMap[K]) => any,
options?: boolean | AddEventListenerOptions,
): Disposable;
export function on<K extends keyof (DocumentEventMap | WindowEventMap), T extends Element>(
selectorOrElement: string | Window | Document | Element,
name: K,
listener: (this: T, ev: (DocumentEventMap | WindowEventMap)[K]) => any,
options?: boolean | AddEventListenerOptions, options?: boolean | AddEventListenerOptions,
el?: Element, el?: Element,
): Disposable { ): Disposable {

+ 21
- 0
src/webviews/protocol.ts View File

@ -56,6 +56,27 @@ export const UpdateConfigurationCommandType = new IpcCommandType
'configuration/update', 'configuration/update',
); );
export interface CommitPreviewConfigurationCommandParams {
key: string;
id: string;
type: 'commit';
format: string;
}
type PreviewConfigurationCommandParams = CommitPreviewConfigurationCommandParams;
export const PreviewConfigurationCommandType = new IpcCommandType<PreviewConfigurationCommandParams>(
'configuration/preview',
);
export interface DidPreviewConfigurationNotificationParams {
id: string;
preview: string;
}
export const DidPreviewConfigurationNotificationType = new IpcNotificationType<
DidPreviewConfigurationNotificationParams
>('configuration/didPreview');
export interface SettingsDidRequestJumpToNotificationParams { export interface SettingsDidRequestJumpToNotificationParams {
anchor: string; anchor: string;
} }

+ 73
- 1
src/webviews/webviewBase.ts View File

@ -13,18 +13,21 @@ import {
window, window,
workspace, workspace,
} from 'vscode'; } from 'vscode';
import { Commands } from '../commands';
import { configuration } from '../configuration'; import { configuration } from '../configuration';
import { Container } from '../container'; import { Container } from '../container';
import { CommitFormatter, GitBlameCommit, PullRequest, PullRequestState } from '../git/git';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { import {
DidChangeConfigurationNotificationType, DidChangeConfigurationNotificationType,
DidPreviewConfigurationNotificationType,
IpcMessage, IpcMessage,
IpcNotificationParamsOf, IpcNotificationParamsOf,
IpcNotificationType, IpcNotificationType,
onIpcCommand, onIpcCommand,
PreviewConfigurationCommandType,
UpdateConfigurationCommandType, UpdateConfigurationCommandType,
} from './protocol'; } from './protocol';
import { Commands } from '../commands';
let ipcSequence = 0; let ipcSequence = 0;
function nextIpcId() { function nextIpcId() {
@ -131,6 +134,75 @@ export abstract class WebviewBase implements Disposable {
}); });
break; break;
case PreviewConfigurationCommandType.method:
onIpcCommand(PreviewConfigurationCommandType, e, async params => {
switch (params.type) {
case 'commit': {
const commit = new GitBlameCommit(
'~/code/eamodio/vscode-gitlens-demo',
'fe26af408293cba5b4bfd77306e1ac9ff7ccaef8',
'You',
'eamodio@gmail.com',
new Date('2016-11-12T20:41:00.000Z'),
new Date('2020-11-01T06:57:21.000Z'),
'Supercharged',
'code.ts',
undefined,
'3ac1d3f51d7cf5f438cc69f25f6740536ad80fef',
'code.ts',
[],
);
let includePullRequest = false;
switch (params.key) {
case configuration.name('currentLine', 'format'):
includePullRequest = Container.config.currentLine.pullRequests.enabled;
break;
case configuration.name('statusBar', 'format'):
includePullRequest = Container.config.statusBar.pullRequests.enabled;
break;
}
let pr: PullRequest | undefined;
if (includePullRequest) {
pr = new PullRequest(
'GitHub',
{
name: 'Eric Amodio',
avatarUrl: 'https://avatars1.githubusercontent.com/u/641685?s=32&v=4',
url: 'https://github.com/eamodio',
},
1,
'Supercharged',
'https://github.com/eamodio/vscode-gitlens/pulls/1',
PullRequestState.Merged,
new Date('Sat, 12 Nov 2016 19:41:00 GMT'),
undefined,
new Date('Sat, 12 Nov 2016 20:41:00 GMT'),
);
}
let preview;
try {
preview = CommitFormatter.fromTemplate(params.format, commit, {
dateFormat: Container.config.defaultDateFormat,
pullRequestOrRemote: pr,
messageTruncateAtNewLine: true,
});
} catch {
preview = 'Invalid format';
}
await this.notify(DidPreviewConfigurationNotificationType, {
id: params.id,
preview: preview,
});
}
}
});
break;
default: default:
break; break;
} }

Loading…
Cancel
Save