Skip to content

Commit f7bbc5b

Browse files
axosoft-ramintsergeibbb
authored andcommitted
Barebones 'start work' command for hacking/experimenting (WIP)
1 parent 702acbf commit f7bbc5b

File tree

5 files changed

+271
-2
lines changed

5 files changed

+271
-2
lines changed

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5807,6 +5807,12 @@
58075807
"category": "GitLens",
58085808
"icon": "$(rocket)"
58095809
},
5810+
{
5811+
"command": "gitlens.startWork",
5812+
"title": "Start Work",
5813+
"category": "GitLens",
5814+
"icon": "$(rocket)"
5815+
},
58105816
{
58115817
"command": "gitlens.showLaunchpadView",
58125818
"title": "Show Launchpad View",
@@ -9932,6 +9938,10 @@
99329938
"command": "gitlens.showLaunchpad",
99339939
"when": "gitlens:enabled"
99349940
},
9941+
{
9942+
"command": "gitlens.startWork",
9943+
"when": "gitlens:enabled"
9944+
},
99359945
{
99369946
"command": "gitlens.showLaunchpadView",
99379947
"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
ShowWelcomePage = 'gitlens.showWelcomePage',
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: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
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 { ensureAccessStep } from '../../commands/quickCommand.steps';
18+
import { proBadge } from '../../constants';
19+
import { HostingIntegrationId } from '../../constants.integrations';
20+
import type { Container } from '../../container';
21+
import { PlusFeatures } from '../../features';
22+
import type { SearchedIssue } from '../../git/models/issue';
23+
import type { QuickPickItemOfT } from '../../quickpicks/items/common';
24+
import { createQuickPickItemOfT, createQuickPickSeparator } from '../../quickpicks/items/common';
25+
import type { DirectiveQuickPickItem } from '../../quickpicks/items/directive';
26+
import { createDirectiveQuickPickItem, Directive } from '../../quickpicks/items/directive';
27+
import { fromNow } from '../../system/date';
28+
29+
export type StartWorkItem = {
30+
item: SearchedIssue;
31+
};
32+
33+
export const StartWorkQuickInputButton: QuickInputButton = {
34+
iconPath: new ThemeIcon('beaker'),
35+
tooltip: 'Start Work on this Item',
36+
};
37+
38+
export type StartWorkResult = { items: StartWorkItem[] };
39+
40+
interface Context {
41+
result: StartWorkResult;
42+
title: string;
43+
}
44+
45+
interface State {
46+
item?: StartWorkItem;
47+
action?: StartWorkAction;
48+
}
49+
50+
type StartWorkStepState<T extends State = State> = RequireSome<StepState<T>, 'item'>;
51+
52+
export type StartWorkAction = 'start';
53+
54+
export interface StartWorkCommandArgs {
55+
readonly command: 'startWork';
56+
}
57+
58+
function assertsStartWorkStepState(state: StepState<State>): asserts state is StartWorkStepState {
59+
if (state.item != null) return;
60+
61+
debugger;
62+
throw new Error('Missing item');
63+
}
64+
65+
export class StartWorkCommand extends QuickCommand<State> {
66+
constructor(container: Container) {
67+
super(container, 'startWork', 'startWork', `Start Work\u00a0\u00a0${proBadge}`, {
68+
description: 'Start work on an issue',
69+
});
70+
71+
this.initialState = {
72+
counter: 0,
73+
};
74+
}
75+
76+
protected async *steps(state: PartialStepState<State>): StepGenerator {
77+
if (this.container.git.isDiscoveringRepositories) {
78+
await this.container.git.isDiscoveringRepositories;
79+
}
80+
81+
const context: Context = {
82+
result: { items: [] },
83+
title: this.title,
84+
};
85+
86+
while (this.canStepsContinue(state)) {
87+
context.title = this.title;
88+
const result = yield* ensureAccessStep(state, context, PlusFeatures.Launchpad);
89+
if (result === StepResultBreak) continue;
90+
91+
await updateContextItems(this.container, context);
92+
93+
if (state.counter < 1 || state.item == null) {
94+
const result = yield* this.pickIssueStep(state, context);
95+
if (result === StepResultBreak) continue;
96+
state.item = result;
97+
}
98+
99+
assertsStartWorkStepState(state);
100+
101+
if (this.confirm(state.confirm)) {
102+
const result = yield* this.confirmStep(state, context);
103+
if (result === StepResultBreak) continue;
104+
105+
state.action = result;
106+
}
107+
108+
if (typeof state.action === 'string') {
109+
switch (state.action) {
110+
case 'start':
111+
startWork(state.item.item);
112+
break;
113+
}
114+
}
115+
116+
endSteps(state);
117+
}
118+
119+
return state.counter < 0 ? StepResultBreak : undefined;
120+
}
121+
122+
private *pickIssueStep(state: StepState<State>, context: Context): StepResultGenerator<StartWorkItem> {
123+
const buildIssueItem = (i: StartWorkItem) => {
124+
const buttons = [StartWorkQuickInputButton];
125+
return {
126+
label:
127+
i.item.issue.title.length > 60 ? `${i.item.issue.title.substring(0, 60)}...` : i.item.issue.title,
128+
// description: `${i.repoAndOwner}#${i.id}, by @${i.author}`,
129+
description: `\u00a0 ${i.item.issue.repository?.owner ?? ''}/${i.item.issue.repository?.repo ?? ''}#${
130+
i.item.issue.id
131+
} \u00a0`,
132+
detail: ` ${fromNow(i.item.issue.updatedDate)} by @${i.item.issue.author.name}`,
133+
buttons: buttons,
134+
iconPath: i.item.issue.author?.avatarUrl != null ? Uri.parse(i.item.issue.author.avatarUrl) : undefined,
135+
item: i,
136+
};
137+
};
138+
139+
const getItems = (result: StartWorkResult) => {
140+
const items: QuickPickItemOfT<StartWorkItem>[] = [];
141+
142+
if (result.items?.length) {
143+
items.push(...result.items.map(buildIssueItem));
144+
}
145+
146+
return items;
147+
};
148+
149+
function getItemsAndPlaceholder() {
150+
if (!context.result.items.length) {
151+
return {
152+
placeholder: 'All done! Take a vacation',
153+
items: [createDirectiveQuickPickItem(Directive.Cancel, undefined, { label: 'OK' })],
154+
};
155+
}
156+
157+
return {
158+
placeholder: 'Choose an item to focus on',
159+
items: getItems(context.result),
160+
};
161+
}
162+
163+
const { items, placeholder } = getItemsAndPlaceholder();
164+
165+
const step = createPickStep({
166+
title: context.title,
167+
placeholder: placeholder,
168+
matchOnDescription: true,
169+
matchOnDetail: true,
170+
items: items,
171+
onDidClickItemButton: (_quickpick, button, { item }) => {
172+
if (button === StartWorkQuickInputButton) {
173+
startWork(item.item);
174+
}
175+
},
176+
});
177+
178+
const selection: StepSelection<typeof step> = yield step;
179+
if (!canPickStepContinue(step, state, selection)) {
180+
return StepResultBreak;
181+
}
182+
const element = selection[0];
183+
return { ...element.item };
184+
}
185+
186+
private *confirmStep(state: StartWorkStepState, _context: Context): StepResultGenerator<StartWorkAction> {
187+
const confirmations: (QuickPickItemOfT<StartWorkAction> | DirectiveQuickPickItem)[] = [
188+
createQuickPickSeparator(fromNow(state.item.item.issue.updatedDate)),
189+
createQuickPickItemOfT(
190+
{
191+
label: state.item.item.issue.title,
192+
description: `${state.item.item.issue.repository?.owner ?? ''}/${
193+
state.item.item.issue.repository?.repo ?? ''
194+
}#${state.item.item.issue.id}`,
195+
detail: state.item.item.issue.body ?? '',
196+
iconPath:
197+
state.item.item.issue.author?.avatarUrl != null
198+
? Uri.parse(state.item.item.issue.author.avatarUrl)
199+
: undefined,
200+
buttons: [StartWorkQuickInputButton],
201+
},
202+
'start',
203+
),
204+
createDirectiveQuickPickItem(Directive.Noop, false, { label: '' }),
205+
createQuickPickSeparator('Actions'),
206+
createQuickPickItemOfT(
207+
{
208+
label: 'Start Work...',
209+
detail: `Will start working on this issue`,
210+
},
211+
'start',
212+
),
213+
];
214+
215+
const step = this.createConfirmStep(
216+
`Issue ${state.item.item.issue.repository?.owner ?? ''}/${state.item.item.issue.repository?.repo ?? ''}#${
217+
state.item.item.issue.id
218+
}`,
219+
confirmations,
220+
undefined,
221+
{
222+
placeholder: 'Choose an action to perform',
223+
onDidClickItemButton: (_quickpick, button, _item) => {
224+
switch (button) {
225+
case StartWorkQuickInputButton:
226+
startWork(state.item.item);
227+
break;
228+
}
229+
},
230+
},
231+
);
232+
233+
const selection: StepSelection<typeof step> = yield step;
234+
return canPickStepContinue(step, state, selection) ? selection[0].item : StepResultBreak;
235+
}
236+
}
237+
238+
async function updateContextItems(container: Container, context: Context) {
239+
context.result = {
240+
items:
241+
(await container.integrations.getMyIssues([HostingIntegrationId.GitHub]))?.map(i => ({
242+
item: i,
243+
})) ?? [],
244+
};
245+
}
246+
247+
function startWork(_issue: SearchedIssue) {
248+
// TODO: Hack here
249+
}

0 commit comments

Comments
 (0)