Skip to content

Commit b9bb96a

Browse files
axosoft-ramintchivorotkiv
authored andcommitted
Barebones 'start work' command for hacking/experimenting
(#3621, #3698)
1 parent 1cce55b commit b9bb96a

File tree

5 files changed

+209
-2
lines changed

5 files changed

+209
-2
lines changed

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5899,6 +5899,12 @@
58995899
"category": "GitLens",
59005900
"icon": "$(rocket)"
59015901
},
5902+
{
5903+
"command": "gitlens.startWork",
5904+
"title": "Start Work",
5905+
"category": "GitLens",
5906+
"icon": "$(rocket)"
5907+
},
59025908
{
59035909
"command": "gitlens.showLaunchpadView",
59045910
"title": "Show Launchpad View",
@@ -10336,6 +10342,10 @@
1033610342
"command": "gitlens.showLaunchpad",
1033710343
"when": "gitlens:enabled"
1033810344
},
10345+
{
10346+
"command": "gitlens.startWork",
10347+
"when": "gitlens:enabled"
10348+
},
1033910349
{
1034010350
"command": "gitlens.showLaunchpadView",
1034110351
"when": "gitlens:enabled"

src/commands/quickWizard.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import { Commands } from '../constants.commands';
22
import type { Container } from '../container';
33
import type { LaunchpadCommandArgs } from '../plus/launchpad/launchpad';
4+
import type { StartWorkCommandArgs } from '../plus/startWork/startWork';
45
import { command } from '../system/vscode/command';
56
import type { CommandContext } from './base';
67
import type { QuickWizardCommandArgsWithCompletion } from './quickWizard.base';
78
import { QuickWizardCommandBase } from './quickWizard.base';
89

9-
export type QuickWizardCommandArgs = LaunchpadCommandArgs;
10+
export type QuickWizardCommandArgs = LaunchpadCommandArgs | StartWorkCommandArgs;
1011

1112
@command()
1213
export class QuickWizardCommand extends QuickWizardCommandBase {
1314
constructor(container: Container) {
14-
super(container, [Commands.ShowLaunchpad]);
15+
super(container, [Commands.ShowLaunchpad, Commands.StartWork]);
1516
}
1617

1718
protected override preExecute(
@@ -22,6 +23,9 @@ export class QuickWizardCommand extends QuickWizardCommandBase {
2223
case Commands.ShowLaunchpad:
2324
return this.execute({ command: 'launchpad', ...args });
2425

26+
case Commands.StartWork:
27+
return this.execute({ command: 'startWork', ...args });
28+
2529
default:
2630
return this.execute(args);
2731
}

src/commands/quickWizard.utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { StoredRecentUsage } from '../constants.storage';
22
import type { Container } from '../container';
33
import { LaunchpadCommand } from '../plus/launchpad/launchpad';
4+
import { StartWorkCommand } from '../plus/startWork/startWork';
45
import { configuration } from '../system/vscode/configuration';
56
import { getContext } from '../system/vscode/context';
67
import { BranchGitCommand } from './git/branch';
@@ -112,6 +113,10 @@ export class QuickWizardRootStep implements QuickPickStep<QuickCommand> {
112113
if (args?.command === 'launchpad') {
113114
this.hiddenItems.push(new LaunchpadCommand(container, args));
114115
}
116+
117+
if (args?.command === 'startWork') {
118+
this.hiddenItems.push(new StartWorkCommand(container));
119+
}
115120
}
116121

117122
private _command: QuickCommand | undefined;

src/constants.commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ export const enum Commands {
223223
ShowTimelineView = 'gitlens.showTimelineView',
224224
ShowWorktreesView = 'gitlens.showWorktreesView',
225225
ShowWorkspacesView = 'gitlens.showWorkspacesView',
226+
StartWork = 'gitlens.startWork',
226227
StashApply = 'gitlens.stashApply',
227228
StashSave = 'gitlens.stashSave',
228229
StashSaveFiles = 'gitlens.stashSaveFiles',

src/plus/startWork/startWork.ts

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import type { QuickInputButton } from 'vscode';
2+
import { ThemeIcon, Uri } from 'vscode';
3+
import type {
4+
PartialStepState,
5+
StepGenerator,
6+
StepResultGenerator,
7+
StepSelection,
8+
StepState,
9+
} from '../../commands/quickCommand';
10+
import {
11+
canPickStepContinue,
12+
createPickStep,
13+
endSteps,
14+
QuickCommand,
15+
StepResultBreak,
16+
} from '../../commands/quickCommand';
17+
import { proBadge } from '../../constants';
18+
import { HostingIntegrationId } from '../../constants.integrations';
19+
import type { Container } from '../../container';
20+
import type { SearchedIssue } from '../../git/models/issue';
21+
import type { QuickPickItemOfT } from '../../quickpicks/items/common';
22+
import { createDirectiveQuickPickItem, Directive } from '../../quickpicks/items/directive';
23+
import { fromNow } from '../../system/date';
24+
25+
export type StartWorkItem = {
26+
item: SearchedIssue;
27+
};
28+
29+
export const StartWorkQuickInputButton: QuickInputButton = {
30+
iconPath: new ThemeIcon('beaker'),
31+
tooltip: 'Start Work on this Item',
32+
};
33+
34+
export type StartWorkResult = { items: StartWorkItem[] };
35+
36+
interface Context {
37+
result: StartWorkResult;
38+
title: string;
39+
}
40+
41+
interface State {
42+
item?: StartWorkItem;
43+
action?: StartWorkAction;
44+
}
45+
46+
type StartWorkStepState<T extends State = State> = RequireSome<StepState<T>, 'item'>;
47+
48+
export type StartWorkAction = 'start';
49+
50+
export interface StartWorkCommandArgs {
51+
readonly command: 'startWork';
52+
}
53+
54+
function assertsStartWorkStepState(state: StepState<State>): asserts state is StartWorkStepState {
55+
if (state.item != null) return;
56+
57+
debugger;
58+
throw new Error('Missing item');
59+
}
60+
61+
export class StartWorkCommand extends QuickCommand<State> {
62+
constructor(container: Container) {
63+
super(container, 'startWork', 'startWork', `Start Work\u00a0\u00a0${proBadge}`, {
64+
description: 'Start work on an issue',
65+
});
66+
67+
this.initialState = {
68+
counter: 0,
69+
};
70+
}
71+
72+
protected async *steps(state: PartialStepState<State>): StepGenerator {
73+
if (this.container.git.isDiscoveringRepositories) {
74+
await this.container.git.isDiscoveringRepositories;
75+
}
76+
77+
const context: Context = {
78+
result: { items: [] },
79+
title: this.title,
80+
};
81+
82+
while (this.canStepsContinue(state)) {
83+
context.title = this.title;
84+
85+
await updateContextItems(this.container, context);
86+
87+
if (state.counter < 1 || state.item == null) {
88+
const result = yield* this.pickIssueStep(state, context);
89+
if (result === StepResultBreak) continue;
90+
state.item = result;
91+
}
92+
93+
assertsStartWorkStepState(state);
94+
state.action = 'start';
95+
96+
if (typeof state.action === 'string') {
97+
switch (state.action) {
98+
case 'start':
99+
startWork(state.item.item);
100+
break;
101+
}
102+
}
103+
104+
endSteps(state);
105+
}
106+
107+
return state.counter < 0 ? StepResultBreak : undefined;
108+
}
109+
110+
private *pickIssueStep(state: StepState<State>, context: Context): StepResultGenerator<StartWorkItem> {
111+
const buildIssueItem = (i: StartWorkItem) => {
112+
const buttons = [StartWorkQuickInputButton];
113+
return {
114+
label:
115+
i.item.issue.title.length > 60 ? `${i.item.issue.title.substring(0, 60)}...` : i.item.issue.title,
116+
// description: `${i.repoAndOwner}#${i.id}, by @${i.author}`,
117+
description: `\u00a0 ${i.item.issue.repository?.owner ?? ''}/${i.item.issue.repository?.repo ?? ''}#${
118+
i.item.issue.id
119+
} \u00a0`,
120+
detail: ` ${fromNow(i.item.issue.updatedDate)} by @${i.item.issue.author.name}`,
121+
buttons: buttons,
122+
iconPath: i.item.issue.author?.avatarUrl != null ? Uri.parse(i.item.issue.author.avatarUrl) : undefined,
123+
item: i,
124+
picked: i.item.issue.id === state.item?.item?.issue.id,
125+
};
126+
};
127+
128+
const getItems = (result: StartWorkResult) => {
129+
const items: QuickPickItemOfT<StartWorkItem>[] = [];
130+
131+
if (result.items?.length) {
132+
items.push(...result.items.map(buildIssueItem));
133+
}
134+
135+
return items;
136+
};
137+
138+
function getItemsAndPlaceholder() {
139+
if (!context.result.items.length) {
140+
return {
141+
placeholder: 'All done! Take a vacation',
142+
items: [createDirectiveQuickPickItem(Directive.Cancel, undefined, { label: 'OK' })],
143+
};
144+
}
145+
146+
return {
147+
placeholder: 'Choose an item to focus on',
148+
items: getItems(context.result),
149+
};
150+
}
151+
152+
const { items, placeholder } = getItemsAndPlaceholder();
153+
154+
const step = createPickStep({
155+
title: context.title,
156+
placeholder: placeholder,
157+
matchOnDescription: true,
158+
matchOnDetail: true,
159+
items: items,
160+
onDidClickItemButton: (_quickpick, button, { item }) => {
161+
if (button === StartWorkQuickInputButton) {
162+
startWork(item.item);
163+
}
164+
},
165+
});
166+
167+
const selection: StepSelection<typeof step> = yield step;
168+
if (!canPickStepContinue(step, state, selection)) {
169+
return StepResultBreak;
170+
}
171+
const element = selection[0];
172+
return { ...element.item };
173+
}
174+
}
175+
176+
async function updateContextItems(container: Container, context: Context) {
177+
context.result = {
178+
items:
179+
(await container.integrations.getMyIssues([HostingIntegrationId.GitHub]))?.map(i => ({
180+
item: i,
181+
})) ?? [],
182+
};
183+
}
184+
185+
function startWork(_issue: SearchedIssue) {
186+
// TODO: Hack here
187+
}

0 commit comments

Comments
 (0)