Files
theia-code-os/packages/scm/src/browser/scm-commit-widget.tsx
mawkone 8bb5110148
Some checks failed
Playwright Tests / Playwright Tests (ubuntu-22.04, Node.js 22.x) (push) Has been cancelled
3PP License Check / 3PP License Check (11, 22.x, ubuntu-22.04) (push) Has been cancelled
Publish packages to NPM / Perform Publishing (push) Has been cancelled
deploy: current vibn theia state
Made-with: Cursor
2026-02-27 12:01:08 -08:00

217 lines
7.8 KiB
TypeScript

// *****************************************************************************
// 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-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable, inject } from '@theia/core/shared/inversify';
import { DisposableCollection } from '@theia/core';
import { Message } from '@theia/core/shared/@lumino/messaging';
import * as React from '@theia/core/shared/react';
import TextareaAutosize from 'react-textarea-autosize';
import { ScmInput, ScmInputIssueType } from './scm-input';
import {
ContextMenuRenderer, ReactWidget, KeybindingRegistry, StatefulWidget
} from '@theia/core/lib/browser';
import { ScmService } from './scm-service';
@injectable()
export class ScmCommitWidget extends ReactWidget implements StatefulWidget {
static ID = 'scm-commit-widget';
@inject(ScmService) protected readonly scmService: ScmService;
@inject(KeybindingRegistry) protected readonly keybindings: KeybindingRegistry;
protected readonly toDisposeOnRepositoryChange = new DisposableCollection();
protected shouldScrollToRow = true;
/**
* Don't modify DOM use React! only exposed for `focusInput`
* Use `this.scmService.selectedRepository?.input.value` as a single source of truth!
*/
protected readonly inputRef = React.createRef<HTMLTextAreaElement>();
constructor(
@inject(ContextMenuRenderer) protected readonly contextMenuRenderer: ContextMenuRenderer,
) {
super();
this.scrollOptions = {
suppressScrollX: true,
minScrollbarLength: 35
};
this.addClass('theia-scm-commit');
this.id = ScmCommitWidget.ID;
}
protected override onAfterAttach(msg: Message): void {
super.onAfterAttach(msg);
this.refreshOnRepositoryChange();
this.toDisposeOnDetach.push(this.scmService.onDidChangeSelectedRepository(() => {
this.refreshOnRepositoryChange();
this.update();
}));
}
protected refreshOnRepositoryChange(): void {
this.toDisposeOnRepositoryChange.dispose();
const repository = this.scmService.selectedRepository;
if (repository) {
this.toDisposeOnRepositoryChange.push(repository.provider.onDidChange(async () => {
this.update();
}));
this.toDisposeOnRepositoryChange.push(repository.provider.onDidChangeCommitTemplate(e => {
this.setInputValue(e);
}));
}
}
protected override onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
this.focus();
}
public focus(): void {
(this.inputRef.current || this.node).focus();
}
protected render(): React.ReactNode {
const repository = this.scmService.selectedRepository;
if (repository) {
return React.createElement('div', this.createContainerAttributes(), this.renderInput(repository.input));
}
}
/**
* Create the container attributes for the widget.
*/
protected createContainerAttributes(): React.HTMLAttributes<HTMLElement> {
return {
style: { flexGrow: 0 }
};
}
protected renderInput(input: ScmInput): React.ReactNode {
let validationStatus = 'idle';
if (input.issue) {
switch (input.issue.type) {
case ScmInputIssueType.Error:
validationStatus = 'error';
break;
case ScmInputIssueType.Information:
validationStatus = 'info';
break;
case ScmInputIssueType.Warning:
validationStatus = 'warning';
break;
}
}
const validationMessage = input.issue ? input.issue.message : '';
const format = (value: string, ...args: string[]): string => {
if (args.length !== 0) {
return value.replace(/{(\d+)}/g, (found, n) => {
const i = parseInt(n);
return isNaN(i) || i < 0 || i >= args.length ? found : args[i];
});
}
return value;
};
const keybinding = this.keybindings.acceleratorFor(this.keybindings.getKeybindingsForCommand('scm.acceptInput')[0]).join('+');
const message = format(input.placeholder || '', keybinding);
const textArea = input.visible &&
<TextareaAutosize
className={`${ScmCommitWidget.Styles.INPUT_MESSAGE} theia-input theia-scm-input-message-${validationStatus}`}
id={ScmCommitWidget.Styles.INPUT_MESSAGE}
placeholder={message}
spellCheck={false}
autoFocus={true}
value={input.value}
disabled={!input.enabled}
onChange={this.setInputValue}
ref={this.inputRef}
rows={1}
maxRows={6} /* from VS Code */
>
</TextareaAutosize>;
return <div className={ScmCommitWidget.Styles.INPUT_MESSAGE_CONTAINER}>
{textArea}
<div
className={
`${ScmCommitWidget.Styles.VALIDATION_MESSAGE} ${ScmCommitWidget.Styles.NO_SELECT}
theia-scm-validation-message-${validationStatus} theia-scm-input-message-${validationStatus}`
}
style={{
display: !!input.issue ? 'block' : 'none'
}}>{validationMessage}</div>
</div>;
}
protected setInputValue = (event: React.FormEvent<HTMLTextAreaElement> | React.ChangeEvent<HTMLTextAreaElement> | string) => {
const repository = this.scmService.selectedRepository;
if (repository) {
repository.input.value = typeof event === 'string' ? event : event.currentTarget.value;
}
};
/**
* Store the tree state.
*/
storeState(): ScmCommitWidget.State {
const message = this.scmService.selectedRepository?.input.value;
return { message };
}
/**
* Restore the state.
* @param oldState the old state object.
*/
restoreState(oldState: ScmCommitWidget.State): void {
const value = oldState.message;
if (!value) {
return;
}
let repository = this.scmService.selectedRepository;
if (repository) {
repository.input.value = value;
} else {
const listener = this.scmService.onDidChangeSelectedRepository(() => {
repository = this.scmService.selectedRepository;
if (repository) {
listener.dispose();
if (!repository.input.value) {
repository.input.value = value;
}
}
});
this.toDispose.push(listener);
}
}
}
export namespace ScmCommitWidget {
export namespace Styles {
export const INPUT_MESSAGE_CONTAINER = 'theia-scm-input-message-container';
export const INPUT_MESSAGE = 'theia-scm-input-message';
export const VALIDATION_MESSAGE = 'theia-scm-input-validation-message';
export const NO_SELECT = 'no-select';
}
export interface State {
message?: string
}
}