未验证 提交 4af42491 编写于 作者: M Megan Rogge 提交者: GitHub

SCM: Support past commit message navigation (#107619)

* logging prior commit messages

* Logging prior commit messages

* Logging prior commit messages

* now works in forward and backward directions

* reset index on empty input

* cleaning up code

* introduce historyNavigator, working but not persisting on window reload

* introduce historyNavigator, working but not persisting on window reload

* add history

* saves search entries on window reload but doesn't save last typed message in input box

* save input

* change where the save occurs

* working

* add remove last method

* now replaces most recent input

* remove check for null value

* now at least lets you see most recent commit

* before adding objects

* add scmi value class

* add scmi value class

* new commit

* fix removal / insertion order

* change function modifiers

* working correctly

* change conditional

* undo inadvertant changes

* Update README.md

* fix tricky bug

* working and removed unnecessary conditional

* fix another bug

* make elements private again

* change order of save

* now working as expected, about to add context keys

* hook up context keys

* save on shutdown

* improve variable name

* disable show prior/next commit when there's no history and ensure that input is last in history

* formatting

* add new history navigator

* fix bad ==

* rename scm input history methods

* adopt HistoryNavigator2 in SCMInput

* remove unnecessary method

* revert history.ts

* 💄

* change size of history required to be valid

* revert change

* on reload, display latest input

* remove rogue console.log

* fix issue with saving uncommitted message
Co-authored-by: NJoão Moreno <joao.moreno@microsoft.com>
上级 3c463a20
......@@ -97,3 +97,95 @@ export class HistoryNavigator<T> implements INavigator<T> {
return elements;
}
}
interface HistoryNode<T> {
value: T;
previous: HistoryNode<T> | undefined;
next: HistoryNode<T> | undefined;
}
export class HistoryNavigator2<T> {
private head: HistoryNode<T>;
private tail: HistoryNode<T>;
private cursor: HistoryNode<T>;
private size: number;
constructor(history: readonly T[], private capacity: number = 10) {
if (history.length < 1) {
throw new Error('not supported');
}
this.size = 1;
this.head = this.tail = this.cursor = {
value: history[0],
previous: undefined,
next: undefined
};
for (let i = 1; i < history.length; i++) {
this.add(history[i]);
}
}
add(value: T): void {
const node: HistoryNode<T> = {
value,
previous: this.tail,
next: undefined
};
this.tail.next = node;
this.tail = node;
this.cursor = this.tail;
this.size++;
while (this.size > this.capacity) {
this.head = this.head.next!;
this.head.previous = undefined;
this.size--;
}
}
replaceLast(value: T): void {
this.tail.value = value;
}
isAtEnd(): boolean {
return this.cursor === this.tail;
}
current(): T {
return this.cursor.value;
}
previous(): T {
if (this.cursor.previous) {
this.cursor = this.cursor.previous;
}
return this.cursor.value;
}
next(): T {
if (this.cursor.next) {
this.cursor = this.cursor.next;
}
return this.cursor.value;
}
resetCursor(): T {
this.cursor = this.tail;
return this.cursor.value;
}
*[Symbol.iterator](): Iterator<T> {
let node: HistoryNode<T> | undefined = this.head;
while (node) {
yield node.value;
node = node.next;
}
}
}
......@@ -397,7 +397,7 @@ export class MainThreadSCM implements MainThreadSCMShape {
return;
}
repository.input.value = value;
repository.input.setValue(value, false);
}
$setInputBoxPlaceholder(sourceControlHandle: number, placeholder: string): void {
......
......@@ -221,7 +221,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
if (!repository || !repository.provider.acceptInputCommand) {
return Promise.resolve(null);
}
const id = repository.provider.acceptInputCommand.id;
const args = repository.provider.acceptInputCommand.arguments;
......@@ -230,6 +229,34 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'scm.viewNextCommit',
description: { description: localize('scm view next commit', "SCM: View Next Commit"), args: [] },
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.has('scmInputIsInLastLine'),
primary: KeyCode.DownArrow,
handler: accessor => {
const contextKeyService = accessor.get(IContextKeyService);
const context = contextKeyService.getContext(document.activeElement);
const repository = context.getValue<ISCMRepository>('scmRepository');
repository?.input.showNextHistoryValue();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'scm.viewPriorCommit',
description: { description: localize('scm view prior commit', "SCM: View Prior Commit"), args: [] },
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.has('scmInputIsInFirstLine'),
primary: KeyCode.UpArrow,
handler: accessor => {
const contextKeyService = accessor.get(IContextKeyService);
const context = contextKeyService.getContext(document.activeElement);
const repository = context.getValue<ISCMRepository>('scmRepository');
repository?.input.showPreviousHistoryValue();
}
});
CommandsRegistry.registerCommand('scm.openInTerminal', async (accessor, provider: ISCMProvider) => {
if (!provider || !provider.rootUri) {
return;
......
......@@ -1307,14 +1307,14 @@ class SCMInputWidget extends Disposable {
if (value === textModel.getValue()) { // circuit breaker
return;
}
textModel.setValue(input.value);
textModel.setValue(value);
this.inputEditor.setPosition(textModel.getFullModelRange().getEndPosition());
}));
// Keep API in sync with model, update placeholder visibility and validate
const updatePlaceholderVisibility = () => this.placeholderTextContainer.classList.toggle('hidden', textModel.getValueLength() > 0);
this.repositoryDisposables.add(textModel.onDidChangeContent(() => {
input.value = textModel.getValue();
input.setValue(textModel.getValue(), true);
updatePlaceholderVisibility();
triggerValidation();
}));
......@@ -1433,6 +1433,18 @@ class SCMInputWidget extends Disposable {
this.validationDisposable.dispose();
}));
const firstLineKey = contextKeyService2.createKey('scmInputIsInFirstLine', false);
const lastLineKey = contextKeyService2.createKey('scmInputIsInLastLine', false);
this._register(this.inputEditor.onDidChangeCursorPosition(({ position }) => {
const viewModel = this.inputEditor._getViewModel()!;
const lastLineNumber = viewModel.getLineCount();
const viewPosition = viewModel.coordinatesConverter.convertModelPositionToViewPosition(position);
firstLineKey.set(viewPosition.lineNumber === 1);
lastLineKey.set(viewPosition.lineNumber === lastLineNumber);
}));
const onInputFontFamilyChanged = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.inputFontFamily'));
this._register(onInputFontFamilyChanged(() => this.inputEditor.updateOptions({ fontFamily: this.getInputEditorFontFamily() })));
......
......@@ -86,7 +86,8 @@ export interface IInputValidator {
export interface ISCMInput {
readonly repository: ISCMRepository;
value: string;
readonly value: string;
setValue(value: string, fromKeyboard: boolean): void;
readonly onDidChange: Event<string>;
placeholder: string;
......@@ -97,6 +98,9 @@ export interface ISCMInput {
visible: boolean;
readonly onDidChangeVisibility: Event<boolean>;
showNextHistoryValue(): void;
showPreviousHistoryValue(): void;
}
export interface ISCMRepository extends IDisposable {
......
......@@ -8,7 +8,8 @@ import { Event, Emitter } from 'vs/base/common/event';
import { ISCMService, ISCMProvider, ISCMInput, ISCMRepository, IInputValidator } from './scm';
import { ILogService } from 'vs/platform/log/common/log';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage';
import { HistoryNavigator2 } from 'vs/base/common/history';
class SCMInput implements ISCMInput {
......@@ -18,21 +19,6 @@ class SCMInput implements ISCMInput {
return this._value;
}
set value(value: string) {
if (value === this._value) {
return;
}
this._value = value;
if (this.repository.provider.rootUri) {
const key = `scm/input:${this.repository.provider.label}:${this.repository.provider.rootUri.path}`;
this.storageService.store(key, value, StorageScope.WORKSPACE);
}
this._onDidChange.fire(value);
}
private readonly _onDidChange = new Emitter<string>();
readonly onDidChange: Event<string> = this._onDidChange.event;
......@@ -79,14 +65,71 @@ class SCMInput implements ISCMInput {
private readonly _onDidChangeValidateInput = new Emitter<void>();
readonly onDidChangeValidateInput: Event<void> = this._onDidChangeValidateInput.event;
private historyNavigator: HistoryNavigator2<string>;
constructor(
readonly repository: ISCMRepository,
@IStorageService private storageService: IStorageService
) {
if (this.repository.provider.rootUri) {
const key = `scm/input:${this.repository.provider.label}:${this.repository.provider.rootUri.path}`;
this._value = this.storageService.get(key, StorageScope.WORKSPACE, '');
const historyKey = `scm/input:${this.repository.provider.label}:${this.repository.provider.rootUri?.path}`;
let history: string[] | undefined;
let rawHistory = this.storageService.get(historyKey, StorageScope.WORKSPACE, '');
if (rawHistory) {
try {
history = JSON.parse(rawHistory);
} catch {
// noop
}
}
if (!history || history.length === 0) {
history = [this._value];
} else {
this._value = history[history.length - 1];
}
this.historyNavigator = new HistoryNavigator2(history, 50);
this.storageService.onWillSaveState(e => {
if (e.reason === WillSaveStateReason.SHUTDOWN) {
if (this.historyNavigator.isAtEnd()) {
this.historyNavigator.replaceLast(this._value);
}
if (this.repository.provider.rootUri) {
this.storageService.store(historyKey, JSON.stringify([...this.historyNavigator]), StorageScope.WORKSPACE);
}
}
});
}
setValue(value: string, transient: boolean) {
if (value === this._value) {
return;
}
if (!transient) {
this.historyNavigator.replaceLast(this._value);
this.historyNavigator.add(value);
}
this._value = value;
this._onDidChange.fire(value);
}
showNextHistoryValue(): void {
const value = this.historyNavigator.next();
this.setValue(value, true);
}
showPreviousHistoryValue(): void {
if (this.historyNavigator.isAtEnd()) {
this.historyNavigator.replaceLast(this._value);
}
const value = this.historyNavigator.previous();
this.setValue(value, true);
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册