Skip to content

Commit fc255eb

Browse files
feat: cron instance decorations
1 parent 9ef6e97 commit fc255eb

File tree

5 files changed

+115
-1
lines changed

5 files changed

+115
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
88

99
- Added: Event name autocomplete
1010
- Added: Hovering CRON job schedules will show a human readable version
11+
- Added: Cron job indexer and instance class decorations
1112
- Changed: Implemented batching for the indexer to reduce load
1213

1314
## [1.5.0] - 2025-04-06

resources/icons/cron.svg

Lines changed: 7 additions & 0 deletions
Loading
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { DecorationOptions, TextEditorDecorationType, Uri, window } from 'vscode';
2+
import path from 'path';
3+
import TextDocumentDecorationProvider from './TextDocumentDecorationProvider';
4+
import { PhpClass } from 'parser/php/PhpClass';
5+
import { PhpInterface } from 'parser/php/PhpInterface';
6+
import PhpDocumentParser from 'common/php/PhpDocumentParser';
7+
import { ClasslikeInfo } from 'common/php/ClasslikeInfo';
8+
import MarkdownMessageBuilder from 'common/MarkdownMessageBuilder';
9+
import IndexManager from 'indexer/IndexManager';
10+
import CronIndexer from 'indexer/cron/CronIndexer';
11+
import { Job } from 'indexer/cron/types';
12+
import cronstrue from 'cronstrue';
13+
14+
export default class CronClassDecorationProvider extends TextDocumentDecorationProvider {
15+
public getType(): TextEditorDecorationType {
16+
return window.createTextEditorDecorationType({
17+
gutterIconPath: path.join(__dirname, 'resources', 'icons', 'cron.svg'),
18+
gutterIconSize: '80%',
19+
borderColor: 'rgba(0, 188, 202, 0.5)',
20+
borderStyle: 'dotted',
21+
borderWidth: '0 0 1px 0',
22+
});
23+
}
24+
25+
public async getDecorations(): Promise<DecorationOptions[]> {
26+
const decorations: DecorationOptions[] = [];
27+
const phpFile = await PhpDocumentParser.parse(this.document);
28+
29+
const classLikeNode: PhpClass | PhpInterface | undefined =
30+
phpFile.classes[0] || phpFile.interfaces[0];
31+
32+
if (!classLikeNode) {
33+
return decorations;
34+
}
35+
36+
const classlikeInfo = new ClasslikeInfo(phpFile);
37+
38+
const cronIndexData = IndexManager.getIndexData(CronIndexer.KEY);
39+
40+
if (!cronIndexData) {
41+
return decorations;
42+
}
43+
44+
const jobs = cronIndexData.findJobsByInstance(classlikeInfo.getNamespace());
45+
46+
if (jobs.length === 0) {
47+
return decorations;
48+
}
49+
50+
decorations.push(...this.getCronInstanceDecorations(jobs, classlikeInfo));
51+
52+
return decorations;
53+
}
54+
55+
private getCronInstanceDecorations(
56+
jobs: Job[],
57+
classlikeInfo: ClasslikeInfo
58+
): DecorationOptions[] {
59+
const decorations: DecorationOptions[] = [];
60+
61+
const nameRange = classlikeInfo.getNameRange();
62+
63+
if (!nameRange) {
64+
return decorations;
65+
}
66+
67+
const hoverMessage = MarkdownMessageBuilder.create('Cron Jobs');
68+
69+
for (const job of jobs) {
70+
hoverMessage.appendMarkdown(`- [${job.name}](${Uri.file(job.path)}) (${job.method})\n`);
71+
72+
if (job.schedule) {
73+
hoverMessage.appendMarkdown(
74+
` - \`${job.schedule}\` (${cronstrue.toString(job.schedule)})\n`
75+
);
76+
}
77+
78+
if (job.config_path) {
79+
hoverMessage.appendMarkdown(` - Config: \`${job.config_path}\`\n`);
80+
}
81+
}
82+
83+
decorations.push({
84+
range: nameRange,
85+
hoverMessage: hoverMessage.build(),
86+
});
87+
88+
return decorations;
89+
}
90+
}

src/indexer/cron/CronIndexData.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,16 @@ export class CronIndexData extends AbstractIndexData<Job[]> {
1010
public getJobs(): Job[] {
1111
return this.getValues().flatMap(data => data);
1212
}
13+
14+
public findJobByName(group: string, name: string): Job | undefined {
15+
return this.getJobs().find(job => job.group === group && job.name === name);
16+
}
17+
18+
public findJobsByGroup(group: string): Job[] {
19+
return this.getJobs().filter(job => job.group === group);
20+
}
21+
22+
public findJobsByInstance(instance: string): Job[] {
23+
return this.getJobs().filter(job => job.instance === instance);
24+
}
1325
}

src/observer/ActiveTextEditorChangeObserver.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Observer from './Observer';
33
import PluginClassDecorationProvider from 'decorator/PluginClassDecorationProvider';
44
import Context from 'common/Context';
55
import ObserverInstanceDecorationProvider from 'decorator/ObserverInstanceDecorationProvider';
6+
import CronClassDecorationProvider from 'decorator/CronClassDecorationProvider';
67

78
export default class ActiveTextEditorChangeObserver extends Observer {
89
public async execute(textEditor: TextEditor | undefined): Promise<void> {
@@ -11,14 +12,17 @@ export default class ActiveTextEditorChangeObserver extends Observer {
1112
if (textEditor && textEditor.document.languageId === 'php') {
1213
const pluginProvider = new PluginClassDecorationProvider(textEditor.document);
1314
const observerProvider = new ObserverInstanceDecorationProvider(textEditor.document);
15+
const cronProvider = new CronClassDecorationProvider(textEditor.document);
1416

15-
const [pluginDecorations, observerDecorations] = await Promise.all([
17+
const [pluginDecorations, observerDecorations, cronDecorations] = await Promise.all([
1618
pluginProvider.getDecorations(),
1719
observerProvider.getDecorations(),
20+
cronProvider.getDecorations(),
1821
]);
1922

2023
textEditor.setDecorations(pluginProvider.getType(), pluginDecorations);
2124
textEditor.setDecorations(observerProvider.getType(), observerDecorations);
25+
textEditor.setDecorations(cronProvider.getType(), cronDecorations);
2226
}
2327
}
2428
}

0 commit comments

Comments
 (0)