Skip to content

Commit 7455c39

Browse files
committed
Merge branch 'main' into copilot/enable-description-generation
2 parents d29b947 + 3632c53 commit 7455c39

File tree

12 files changed

+150
-15
lines changed

12 files changed

+150
-15
lines changed

src/common/user.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
export const ALLOWED_USERS = `[a-zA-Z0-9-]+`;
88

99
// https://jsdoc.app/index.html
10-
export const JSDOC_NON_USERS = ['abstract', 'virtual', 'access', 'alias', 'async', 'augments', 'extends', 'author', 'borrows', 'callback', 'class', 'constructor', 'classdesc', 'constant', 'const', 'constructs', 'copyright', 'default', 'defaultvalue', 'deprecated', 'description', 'desc', 'enum', 'event', 'example', 'exports', 'external', 'host', 'file', 'fileoverview', 'overview', 'fires', 'emits', 'function', 'func', 'method', 'generator', 'global', 'hideconstructor', 'ignore', 'implements', 'inheritdoc', 'inner', 'instance', 'interface', 'kind', 'lends', 'license', 'listens', 'member', 'var', 'memberof', 'mixes', 'mixin', 'module', 'name', 'namespace', 'override', 'package', 'param', 'arg', 'argument', 'private', 'property', 'prop', 'protected', 'public', 'readonly', 'requires', 'returns', 'return', 'see', 'since', 'static', 'summary', 'this', 'throws', 'exception', 'todo', 'tutorial', 'type', 'typedef', 'variation', 'version', 'yields', 'yield', 'link'];
10+
export const JSDOC_NON_USERS = ['abstract', 'virtual', 'access', 'alias', 'async', 'augments', 'extends', 'author', 'borrows', 'callback', 'class', 'constructor', 'classdesc', 'constant', 'const', 'constructs', 'copyright', 'default', 'defaultvalue', 'deprecated', 'description', 'desc', 'effect', 'enum', 'event', 'example', 'exports', 'external', 'host', 'file', 'fileoverview', 'overview', 'fires', 'emits', 'function', 'func', 'method', 'generator', 'global', 'hideconstructor', 'ignore', 'implements', 'inheritdoc', 'inner', 'instance', 'interface', 'kind', 'lends', 'license', 'listens', 'member', 'var', 'memberof', 'mixes', 'mixin', 'module', 'name', 'namespace', 'override', 'package', 'param', 'arg', 'argument', 'private', 'property', 'prop', 'protected', 'public', 'readonly', 'requires', 'returns', 'return', 'see', 'since', 'static', 'summary', 'this', 'throws', 'exception', 'todo', 'tutorial', 'type', 'typedef', 'variation', 'version', 'yields', 'yield', 'link'];
1111

1212
// https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc-tags.md
1313
export const PHPDOC_NON_USERS = ['api', 'author', 'copyright', 'deprecated', 'generated', 'internal', 'link', 'method', 'package', 'param', 'property', 'return', 'see', 'since', 'throws', 'todo', 'uses', 'var', 'version'];

src/github/graphql.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,16 @@ export interface MarkPullRequestReadyForReviewResponse {
506506
};
507507
}
508508

509+
export interface ConvertPullRequestToDraftResponse {
510+
convertPullRequestToDraft: {
511+
pullRequest: {
512+
isDraft: boolean;
513+
mergeable: 'MERGEABLE' | 'CONFLICTING' | 'UNKNOWN';
514+
mergeStateStatus: 'BEHIND' | 'BLOCKED' | 'CLEAN' | 'DIRTY' | 'HAS_HOOKS' | 'UNKNOWN' | 'UNSTABLE';
515+
};
516+
};
517+
}
518+
509519
export interface MergeQueueForBranchResponse {
510520
repository: {
511521
mergeQueue?: {

src/github/interface.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ export interface ReadyForReview {
5050
allowAutoMerge: boolean;
5151
}
5252

53+
export interface ConvertToDraft {
54+
isDraft: boolean;
55+
mergeable: PullRequestMergeability;
56+
}
57+
5358
export interface IActor {
5459
login: string;
5560
avatarUrl?: string;

src/github/pullRequestModel.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
AddReactionResponse,
2020
AddReviewRequestResponse as AddReviewsResponse,
2121
AddReviewThreadResponse,
22+
ConvertPullRequestToDraftResponse,
2223
DeleteReactionResponse,
2324
DeleteReviewResponse,
2425
DequeuePullRequestResponse,
@@ -45,6 +46,7 @@ import {
4546
} from './graphql';
4647
import {
4748
AccountType,
49+
ConvertToDraft,
4850
GithubItemStateEnum,
4951
IAccount,
5052
IGitTreeItem,
@@ -1846,6 +1848,44 @@ export class PullRequestModel extends IssueModel<PullRequest> implements IPullRe
18461848
}
18471849
}
18481850

1851+
/**
1852+
* Convert a pull request to draft.
1853+
*/
1854+
async convertToDraft(): Promise<ConvertToDraft> {
1855+
try {
1856+
const { mutate, schema } = await this.githubRepository.ensure();
1857+
1858+
const { data } = await mutate<ConvertPullRequestToDraftResponse>({
1859+
mutation: schema.ConvertToDraft,
1860+
variables: {
1861+
input: {
1862+
pullRequestId: this.graphNodeId,
1863+
},
1864+
},
1865+
});
1866+
1867+
/* __GDPR__
1868+
"pr.convertToDraft.success" : {}
1869+
*/
1870+
this._telemetry.sendTelemetryEvent('pr.convertToDraft.success');
1871+
1872+
const result: ConvertToDraft = {
1873+
isDraft: data!.convertPullRequestToDraft.pullRequest.isDraft,
1874+
mergeable: parseMergeability(data!.convertPullRequestToDraft.pullRequest.mergeable, data!.convertPullRequestToDraft.pullRequest.mergeStateStatus),
1875+
};
1876+
this.item.isDraft = result.isDraft;
1877+
this.item.mergeable = result.mergeable;
1878+
this._onDidChange.fire({ draft: true });
1879+
return result;
1880+
} catch (e) {
1881+
/* __GDPR__
1882+
"pr.convertToDraft.failure" : {}
1883+
*/
1884+
this._telemetry.sendTelemetryErrorEvent('pr.convertToDraft.failure');
1885+
throw e;
1886+
}
1887+
}
1888+
18491889
private updateCommentReactions(graphNodeId: string, reactionGroups: ReactionGroup[]) {
18501890
const reviewThread = this._reviewThreadsCache?.find(thread =>
18511891
thread.comments.some(c => c.graphNodeId === graphNodeId),

src/github/pullRequestOverview.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,8 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
394394
return this.setReadyForReview(message);
395395
case 'pr.readyForReviewAndMerge':
396396
return this.setReadyForReviewAndMerge(message);
397+
case 'pr.convertToDraft':
398+
return this.setConvertToDraft(message);
397399
case 'pr.approve':
398400
return this.approvePullRequestMessage(message);
399401
case 'pr.request-changes':
@@ -688,6 +690,10 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
688690
return PullRequestReviewCommon.setReadyForReviewAndMerge(this.getReviewContext(), message);
689691
}
690692

693+
private async setConvertToDraft(message: IRequestMessage<{}>): Promise<void> {
694+
return PullRequestReviewCommon.setConvertToDraft(this.getReviewContext(), message);
695+
}
696+
691697
private async readyForReviewCommand(): Promise<void> {
692698
return PullRequestReviewCommon.readyForReviewCommand(this.getReviewContext());
693699
}

src/github/pullRequestReviewCommon.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { FolderRepositoryManager } from './folderRepositoryManager';
99
import { IAccount, isITeam, ITeam, MergeMethod, PullRequestMergeability, reviewerId, ReviewState } from './interface';
1010
import { BranchInfo } from './pullRequestGitHelper';
1111
import { PullRequestModel } from './pullRequestModel';
12-
import { PullRequest, ReadyForReviewReply, ReviewType, SubmitReviewReply } from './views';
12+
import { ConvertToDraftReply, PullRequest, ReadyForReviewReply, ReviewType, SubmitReviewReply } from './views';
1313
import { DEFAULT_DELETION_METHOD, PR_SETTINGS_NAMESPACE, SELECT_LOCAL_BRANCH, SELECT_REMOTE } from '../common/settingKeys';
1414
import { ReviewEvent, TimelineEvent } from '../common/timelineEvent';
1515
import { Schemes } from '../common/uri';
@@ -230,6 +230,16 @@ export namespace PullRequestReviewCommon {
230230
}
231231
}
232232

233+
export async function setConvertToDraft(ctx: ReviewContext, _message: IRequestMessage<{}>): Promise<void> {
234+
try {
235+
const result: ConvertToDraftReply = await ctx.item.convertToDraft();
236+
ctx.replyMessage(_message, result);
237+
} catch (e) {
238+
vscode.window.showErrorMessage(vscode.l10n.t('Unable to convert pull request to draft. {0}', formatError(e)));
239+
ctx.throwError(_message, '');
240+
}
241+
}
242+
233243
export async function readyForReviewCommand(ctx: ReviewContext): Promise<void> {
234244
ctx.postMessage({
235245
command: 'pr.readying-for-review'

src/github/queriesShared.gql

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,16 @@ mutation ReadyForReview($input: MarkPullRequestReadyForReviewInput!) {
740740
}
741741
}
742742

743+
mutation ConvertToDraft($input: ConvertPullRequestToDraftInput!) {
744+
convertPullRequestToDraft(input: $input) {
745+
pullRequest {
746+
isDraft
747+
mergeable
748+
mergeStateStatus
749+
}
750+
}
751+
}
752+
743753
mutation StartReview($input: AddPullRequestReviewInput!) {
744754
addPullRequestReview(input: $input) {
745755
pullRequestReview {

src/github/views.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ export interface ReadyForReviewReply {
139139
autoMerge?: boolean;
140140
}
141141

142+
export interface ConvertToDraftReply {
143+
isDraft: boolean;
144+
}
145+
142146
export interface MergeArguments {
143147
title: string | undefined;
144148
description: string | undefined;

src/issues/issueFeatureRegistrar.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,10 +1621,21 @@ ${options?.body ?? ''}\n
16211621
}
16221622
}
16231623

1624-
private getMarkdownLinkText(): string | undefined {
1624+
private getMarkdownLinkText(range?: vscode.Range | vscode.NotebookRange): string | undefined {
16251625
if (!vscode.window.activeTextEditor) {
16261626
return undefined;
16271627
}
1628+
1629+
// If a specific range is provided (e.g., from a gutter click), use that
1630+
// Note: NotebookRange is excluded because getText() only accepts vscode.Range
1631+
if (range && !(range instanceof vscode.NotebookRange)) {
1632+
const text = vscode.window.activeTextEditor.document.getText(range);
1633+
if (text) {
1634+
return text;
1635+
}
1636+
}
1637+
1638+
// Otherwise fall back to the current selection
16281639
let editorSelection: vscode.Range | undefined = vscode.window.activeTextEditor.selection;
16291640
if (editorSelection.start.line !== editorSelection.end.line) {
16301641
editorSelection = new vscode.Range(
@@ -1648,7 +1659,7 @@ ${options?.body ?? ''}\n
16481659
const withPermalinks: (PermalinkInfo & { permalink: string })[] = links.filter((link): link is PermalinkInfo & { permalink: string } => !!link.permalink);
16491660

16501661
if (withPermalinks.length === 1) {
1651-
const selection = this.getMarkdownLinkText();
1662+
const selection = this.getMarkdownLinkText(withPermalinks[0].range);
16521663
if (selection) {
16531664
return vscode.env.clipboard.writeText(`[${selection.trim()}](${withPermalinks[0].permalink})`);
16541665
}

src/issues/util.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ function getFileAndPosition(context: LinkContext, positionInfo?: NewIssue): { ur
225225
uri = fileUri;
226226
if (vscode.window.activeTextEditor?.document.uri.fsPath === uri.fsPath && !vscode.window.activeNotebookEditor) {
227227
if (lineNumber !== undefined && (vscode.window.activeTextEditor.selection.isEmpty || !vscode.window.activeTextEditor.selection.contains(new vscode.Position(lineNumber - 1, 0)))) {
228-
range = new vscode.Range(new vscode.Position(lineNumber - 1, 0), new vscode.Position(lineNumber - 1, 1));
228+
range = new vscode.Range(new vscode.Position(lineNumber - 1, 0), new vscode.Position(lineNumber - 1, vscode.window.activeTextEditor.document.lineAt(lineNumber - 1).text.length));
229229
} else {
230230
range = vscode.window.activeTextEditor.selection;
231231
}
@@ -252,6 +252,7 @@ export interface PermalinkInfo {
252252
permalink: string | undefined;
253253
error: string | undefined;
254254
originalFile: vscode.Uri | undefined;
255+
range: vscode.Range | vscode.NotebookRange | undefined;
255256
}
256257

257258
export function getSimpleUpstream(repository: Repository) {
@@ -311,12 +312,12 @@ export async function createSinglePermalink(
311312
): Promise<PermalinkInfo> {
312313
const { uri, range } = getFileAndPosition(context, positionInfo);
313314
if (!uri) {
314-
return { permalink: undefined, error: vscode.l10n.t('No active text editor position to create permalink from.'), originalFile: undefined };
315+
return { permalink: undefined, error: vscode.l10n.t('No active text editor position to create permalink from.'), originalFile: undefined, range: undefined };
315316
}
316317

317318
const repository = getRepositoryForFile(gitAPI, uri);
318319
if (!repository) {
319-
return { permalink: undefined, error: vscode.l10n.t('The current file isn\'t part of repository.'), originalFile: uri };
320+
return { permalink: undefined, error: vscode.l10n.t('The current file isn\'t part of repository.'), originalFile: uri, range };
320321
}
321322

322323
let commitHash: string | undefined;
@@ -328,7 +329,7 @@ export async function createSinglePermalink(
328329
try {
329330
const log = await repository.log({ maxEntries: 1, path: uri.fsPath });
330331
if (log.length === 0) {
331-
return { permalink: undefined, error: vscode.l10n.t('No branch on a remote contains the most recent commit for the file.'), originalFile: uri };
332+
return { permalink: undefined, error: vscode.l10n.t('No branch on a remote contains the most recent commit for the file.'), originalFile: uri, range };
332333
}
333334
// Now that we know that the file existed at some point in the repo, use the head commit to construct the URI.
334335
if (repository.state.HEAD?.commit && (log[0].hash !== repository.state.HEAD?.commit)) {
@@ -345,7 +346,7 @@ export async function createSinglePermalink(
345346

346347
const rawUpstream = await getBestPossibleUpstream(repositoriesManager, repository, commitHash);
347348
if (!rawUpstream || !rawUpstream.fetchUrl) {
348-
return { permalink: undefined, error: vscode.l10n.t('The selection may not exist on any remote.'), originalFile: uri };
349+
return { permalink: undefined, error: vscode.l10n.t('The selection may not exist on any remote.'), originalFile: uri, range };
349350
}
350351
const upstream: Remote & { fetchUrl: string } = rawUpstream as Remote & { fetchUrl: string };
351352

@@ -357,7 +358,8 @@ export async function createSinglePermalink(
357358
permalink: (`${originOfFetchUrl}/${getOwnerAndRepo(repositoriesManager, repository, upstream)}/blob/${commitHash
358359
}${includeFile ? `${encodedPathSegment}${includeRange ? rangeString(range) : ''}` : ''}`),
359360
error: undefined,
360-
originalFile: uri
361+
originalFile: uri,
362+
range
361363
};
362364
Logger.debug(`permalink generated: ${result.permalink}`, PERMALINK_COMPONENT);
363365
return result;
@@ -431,11 +433,11 @@ export async function createSingleGitHubLink(
431433
): Promise<PermalinkInfo> {
432434
const { uri, range } = getFileAndPosition(context);
433435
if (!uri) {
434-
return { permalink: undefined, error: vscode.l10n.t('No active text editor position to create permalink from.'), originalFile: undefined };
436+
return { permalink: undefined, error: vscode.l10n.t('No active text editor position to create permalink from.'), originalFile: undefined, range: undefined };
435437
}
436438
const folderManager = managers.getManagerForFile(uri);
437439
if (!folderManager) {
438-
return { permalink: undefined, error: vscode.l10n.t('Current file does not belong to an open repository.'), originalFile: undefined };
440+
return { permalink: undefined, error: vscode.l10n.t('Current file does not belong to an open repository.'), originalFile: undefined, range: undefined };
439441
}
440442
let branchName = folderManager.repository.state.HEAD?.name;
441443
if (!branchName) {
@@ -446,7 +448,7 @@ export async function createSingleGitHubLink(
446448
}
447449
const upstream = getSimpleUpstream(folderManager.repository);
448450
if (!upstream?.fetchUrl) {
449-
return { permalink: undefined, error: vscode.l10n.t('Repository does not have any remotes.'), originalFile: undefined };
451+
return { permalink: undefined, error: vscode.l10n.t('Repository does not have any remotes.'), originalFile: undefined, range: undefined };
450452
}
451453
const pathSegment = uri.path.substring(folderManager.repository.rootUri.path.length);
452454
const originOfFetchUrl = getUpstreamOrigin(upstream).replace(/\/$/, '');
@@ -455,7 +457,8 @@ export async function createSingleGitHubLink(
455457
permalink: (`${originOfFetchUrl}/${new Protocol(upstream.fetchUrl).nameWithOwner}/blob/${encodedBranchAndFilePath
456458
}${includeRange ? rangeString(range) : ''}`),
457459
error: undefined,
458-
originalFile: uri
460+
originalFile: uri,
461+
range
459462
};
460463
}
461464

0 commit comments

Comments
 (0)