Skip to content

Commit

Permalink
Expose comments input as text document
Browse files Browse the repository at this point in the history
Fixes https://github.com/microsoft/vscode-pull-request-github/issues/5875
For microsoft#209508

Exposes comment input editors as text documents that extensions can see. Enables a few features in them too such as 'paste as' and 'drop into'
  • Loading branch information
mjbvz committed Apr 4, 2024
1 parent 641eb4c commit 826b140
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 42 deletions.
5 changes: 5 additions & 0 deletions src/vs/base/common/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ export namespace Schemas {
*/
export const vscodeSourceControl = 'vscode-scm';

/**
* Scheme used for input box for creating comments.
*/
export const commentsInput = 'vscode-comments-input';

/**
* Scheme used for special rendering of settings in the release notes
*/
Expand Down
9 changes: 8 additions & 1 deletion src/vs/platform/markers/common/markerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { IMarker, IMarkerData, IMarkerService, IResourceMarker, MarkerSeverity, MarkerStatistics } from './markers';

export const unsupportedSchemas = new Set([Schemas.inMemory, Schemas.vscodeSourceControl, Schemas.walkThrough, Schemas.walkThroughSnippet, Schemas.vscodeChatCodeBlock]);
export const unsupportedSchemas = new Set([
Schemas.inMemory,
Schemas.vscodeSourceControl,
Schemas.walkThrough,
Schemas.walkThroughSnippet,
Schemas.vscodeChatCodeBlock,
Schemas.commentsInput,
]);

class DoubleResourceMap<V> {

Expand Down
42 changes: 22 additions & 20 deletions src/vs/workbench/contrib/comments/browser/commentNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@ import * as dom from 'vs/base/browser/dom';
import * as languages from 'vs/editor/common/languages';
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action, IActionRunner, IAction, Separator, ActionRunner } from 'vs/base/common/actions';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, IReference, dispose } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/model';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService';
Expand Down Expand Up @@ -45,13 +42,14 @@ import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { DomEmitter } from 'vs/base/browser/event';
import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys';
import { FileAccess } from 'vs/base/common/network';
import { FileAccess, Schemas } from 'vs/base/common/network';
import { COMMENTS_SECTION, ICommentsConfiguration } from 'vs/workbench/contrib/comments/common/commentsConfiguration';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { MarshalledCommentThread } from 'vs/workbench/common/comments';
import { IHoverService } from 'vs/platform/hover/browser/hover';
import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService';

class CommentsActionRunner extends ActionRunner {
protected override async runAction(action: IAction, context: any[]): Promise<void> {
Expand All @@ -75,7 +73,7 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
private _reactionActionsContainer?: HTMLElement;
private _commentEditor: SimpleCommentEditor | null = null;
private _commentEditorDisposables: IDisposable[] = [];
private _commentEditorModel: ITextModel | null = null;
private _commentEditorModel: IReference<IResolvedTextEditorModel> | null = null;
private _editorHeight = MIN_EDITOR_HEIGHT;

private _isPendingLabel!: HTMLElement;
Expand Down Expand Up @@ -112,15 +110,14 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
private markdownRenderer: MarkdownRenderer,
@IInstantiationService private instantiationService: IInstantiationService,
@ICommentService private commentService: ICommentService,
@IModelService private modelService: IModelService,
@ILanguageService private languageService: ILanguageService,
@INotificationService private notificationService: INotificationService,
@IContextMenuService private contextMenuService: IContextMenuService,
@IContextKeyService contextKeyService: IContextKeyService,
@IConfigurationService private configurationService: IConfigurationService,
@IHoverService private hoverService: IHoverService,
@IAccessibilityService private accessibilityService: IAccessibilityService,
@IKeybindingService private keybindingService: IKeybindingService
@IKeybindingService private keybindingService: IKeybindingService,
@ITextModelService private readonly textModelService: ITextModelService,
) {
super();

Expand Down Expand Up @@ -494,13 +491,18 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
return (typeof this.comment.body === 'string') ? this.comment.body : this.comment.body.value;
}

private createCommentEditor(editContainer: HTMLElement): void {
private async createCommentEditor(editContainer: HTMLElement): Promise<void> {
const container = dom.append(editContainer, dom.$('.edit-textarea'));
this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, container, SimpleCommentEditor.getEditorOptions(this.configurationService), this._contextKeyService, this.parentThread);
const resource = URI.parse(`comment:commentinput-${this.comment.uniqueIdInThread}-${Date.now()}.md`);
this._commentEditorModel = this.modelService.createModel('', this.languageService.createByFilepathOrFirstLine(resource), resource, false);

this._commentEditor.setModel(this._commentEditorModel);
const resource = URI.from({
scheme: Schemas.commentsInput,
path: `/commentinput-${this.comment.uniqueIdInThread}-${Date.now()}.md`
});
const modelRef = await this.textModelService.createModelReference(resource);
this._commentEditorModel = modelRef;

this._commentEditor.setModel(this._commentEditorModel.object.textEditorModel);
this._commentEditor.setValue(this.pendingEdit ?? this.commentBodyValue);
this.pendingEdit = undefined;
this._commentEditor.layout({ width: container.clientWidth - 14, height: this._editorHeight });
Expand All @@ -511,8 +513,8 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
this._commentEditor!.focus();
});

const lastLine = this._commentEditorModel.getLineCount();
const lastColumn = this._commentEditorModel.getLineLength(lastLine) + 1;
const lastLine = this._commentEditorModel.object.textEditorModel.getLineCount();
const lastColumn = this._commentEditorModel.object.textEditorModel.getLineLength(lastLine) + 1;
this._commentEditor.setSelection(new Selection(lastLine, lastColumn, lastLine, lastColumn));

const commentThread = this.commentThread;
Expand Down Expand Up @@ -547,7 +549,7 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {

this.calculateEditorHeight();

this._register((this._commentEditorModel.onDidChangeContent(() => {
this._register((this._commentEditorModel.object.textEditorModel.onDidChangeContent(() => {
if (this._commentEditor && this.calculateEditorHeight()) {
this._commentEditor.layout({ height: this._editorHeight, width: this._commentEditor.getLayoutInfo().width });
this._commentEditor.render(true);
Expand Down Expand Up @@ -604,15 +606,15 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
this._scrollableElement.setScrollDimensions({ width, scrollWidth, height, scrollHeight });
}

public switchToEditMode() {
public async switchToEditMode() {
if (this.isEditing) {
return;
}

this.isEditing = true;
this._body.classList.add('hidden');
this._commentEditContainer = dom.append(this._commentDetailsContainer, dom.$('.edit-container'));
this.createCommentEditor(this._commentEditContainer);
await this.createCommentEditor(this._commentEditContainer);

const formActions = dom.append(this._commentEditContainer, dom.$('.form-actions'));
const otherActions = dom.append(formActions, dom.$('.other-actions'));
Expand Down Expand Up @@ -705,7 +707,7 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
}));
}

update(newComment: languages.Comment) {
async update(newComment: languages.Comment) {

if (newComment.body !== this.comment.body) {
this.updateCommentBody(newComment.body);
Expand All @@ -721,7 +723,7 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {

if (isChangingMode) {
if (newComment.mode === languages.CommentMode.Editing) {
this.switchToEditMode();
await this.switchToEditMode();
} else {
this.removeCommentEditor();
}
Expand Down
47 changes: 27 additions & 20 deletions src/vs/workbench/contrib/comments/browser/commentReply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@
*--------------------------------------------------------------------------------------------*/

import * as dom from 'vs/base/browser/dom';
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
import { IAction } from 'vs/base/common/actions';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { MarshalledId } from 'vs/base/common/marshallingIds';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IRange } from 'vs/editor/common/core/range';
import * as languages from 'vs/editor/common/languages';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/model';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import * as nls from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions';
Expand All @@ -29,11 +31,8 @@ import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/comment
import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { LayoutableEditor, MIN_EDITOR_HEIGHT, SimpleCommentEditor, calculateEditorHeight } from './simpleCommentEditor';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
import { IHoverService } from 'vs/platform/hover/browser/hover';

const COMMENT_SCHEME = 'comment';
let INMEM_MODEL_ID = 0;
export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration';

Expand All @@ -42,8 +41,8 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
form: HTMLElement;
commentEditorIsEmpty: IContextKey<boolean>;
private _error!: HTMLElement;
private _formActions: HTMLElement | null;
private _editorActions: HTMLElement | null;
private readonly _formActions: HTMLElement;
private readonly _editorActions: HTMLElement;
private _commentThreadDisposables: IDisposable[] = [];
private _commentFormActions!: CommentFormActions;
private _commentEditorActions!: CommentFormActions;
Expand All @@ -63,12 +62,11 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
private _parentThread: ICommentThreadWidget,
private _actionRunDelegate: (() => void) | null,
@ICommentService private commentService: ICommentService,
@ILanguageService private languageService: ILanguageService,
@IModelService private modelService: IModelService,
@IThemeService private themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService private keybindingService: IKeybindingService,
@IHoverService private hoverService: IHoverService,
@ITextModelService private readonly textModelService: ITextModelService
) {
super();

Expand All @@ -77,32 +75,44 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
this.commentEditorIsEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService);
this.commentEditorIsEmpty.set(!this._pendingComment);

const formActions = dom.append(this.form, dom.$('.form-actions'));
this._formActions = dom.append(formActions, dom.$('.other-actions'));
this._editorActions = dom.append(formActions, dom.$('.editor-actions'));
this.initialize();
}

async initialize() {
const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0;
const modeId = generateUuid() + '-' + (hasExistingComments ? this._commentThread.threadId : ++INMEM_MODEL_ID);
const params = JSON.stringify({
extensionId: this._commentThread.extensionId,
commentThreadId: this._commentThread.threadId
});

let resource = URI.parse(`${COMMENT_SCHEME}://${this._commentThread.extensionId}/commentinput-${modeId}.md?${params}`); // TODO. Remove params once extensions adopt authority.
const commentController = this.commentService.getCommentController(owner);
let resource = URI.from({
scheme: Schemas.commentsInput,
path: `/${this._commentThread.extensionId}/commentinput-${modeId}.md?${params}` // TODO. Remove params once extensions adopt authority.
});
const commentController = this.commentService.getCommentController(this.owner);
if (commentController) {
resource = resource.with({ authority: commentController.id });
}

const model = this.modelService.createModel(this._pendingComment || '', this.languageService.createByFilepathOrFirstLine(resource), resource, false);
const model = await this.textModelService.createModelReference(resource);
model.object.textEditorModel.setValue(this._pendingComment || '');

this._register(model);
this.commentEditor.setModel(model);
this.commentEditor.setModel(model.object.textEditorModel);
this.calculateEditorHeight();

this._register((model.onDidChangeContent(() => {
this._register(model.object.textEditorModel.onDidChangeContent(() => {
this.setCommentEditorDecorations();
this.commentEditorIsEmpty?.set(!this.commentEditor.getValue());
if (this.calculateEditorHeight()) {
this.commentEditor.layout({ height: this._editorHeight, width: this.commentEditor.getLayoutInfo().width });
this.commentEditor.render(true);
}
})));
}));

this.createTextModelListener(this.commentEditor, this.form);

Expand All @@ -115,11 +125,8 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
this.expandReplyArea();
}
this._error = dom.append(this.form, dom.$('.validation-error.hidden'));
const formActions = dom.append(this.form, dom.$('.form-actions'));
this._formActions = dom.append(formActions, dom.$('.other-actions'));
this.createCommentWidgetFormActions(this._formActions, model);
this._editorActions = dom.append(formActions, dom.$('.editor-actions'));
this.createCommentWidgetEditorActions(this._editorActions, model);
this.createCommentWidgetFormActions(this._formActions, model.object.textEditorModel);
this.createCommentWidgetEditorActions(this._editorActions, model.object.textEditorModel);
}

private calculateEditorHeight(): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/co
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { accessibilityHelpIsShown, accessibleViewCurrentProviderId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { CommentCommandId } from 'vs/workbench/contrib/comments/common/commentCommandIds';
import { registerWorkbenchContribution2, WorkbenchPhase } from 'vs/workbench/common/contributions';
import { CommentsInputContentProvider } from 'vs/workbench/contrib/comments/browser/commentsInputContentProvider';

registerEditorContribution(ID, CommentController, EditorContributionInstantiation.AfterFirstRender);
registerWorkbenchContribution2(CommentsInputContentProvider.ID, CommentsInputContentProvider, WorkbenchPhase.BlockRestore);

KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CommentCommandId.NextThread,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Disposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/model';
import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService';

export class CommentsInputContentProvider extends Disposable implements ITextModelContentProvider, IEditorContribution {

public static readonly ID = 'comments.input.contentProvider';

constructor(
@ITextModelService textModelService: ITextModelService,
@IModelService private readonly _modelService: IModelService,
@ILanguageService private readonly _languageService: ILanguageService,
) {
super();
this._register(textModelService.registerTextModelContentProvider(Schemas.commentsInput, this));
}

async provideTextContent(resource: URI): Promise<ITextModel | null> {
const existing = this._modelService.getModel(resource);
return existing ?? this._modelService.createModel('', this._languageService.createById('markdown'), resource);
}
}
Loading

0 comments on commit 826b140

Please sign in to comment.