Skip to content

Commit a2634fb

Browse files
authored
Merge pull request #4788 from continuedev/nate/edit-button
Nate/edit-button
2 parents 710fe6e + d19872b commit a2634fb

File tree

6 files changed

+136
-39
lines changed

6 files changed

+136
-39
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as vscode from "vscode";
2+
3+
export class ConfigYamlDocumentLinkProvider
4+
implements vscode.DocumentLinkProvider
5+
{
6+
private usesPattern = /^\s*#?\s*-\s*uses:\s*(.+)$/;
7+
provideDocumentLinks(
8+
document: vscode.TextDocument,
9+
token: vscode.CancellationToken,
10+
): vscode.ProviderResult<vscode.DocumentLink[]> {
11+
const links: vscode.DocumentLink[] = [];
12+
13+
for (let lineIndex = 0; lineIndex < document.lineCount; lineIndex++) {
14+
const line = document.lineAt(lineIndex);
15+
const match = this.usesPattern.exec(line.text);
16+
17+
if (match) {
18+
const slug = match[1].trim();
19+
const startPos = line.text.indexOf(slug);
20+
const range = new vscode.Range(
21+
lineIndex,
22+
startPos,
23+
lineIndex,
24+
startPos + slug.length,
25+
);
26+
27+
const link = new vscode.DocumentLink(
28+
range,
29+
vscode.Uri.parse(`https://hub.continue.dev/${slug}`),
30+
);
31+
links.push(link);
32+
}
33+
}
34+
35+
return links;
36+
}
37+
resolveDocumentLink(
38+
link: vscode.DocumentLink,
39+
token: vscode.CancellationToken,
40+
): vscode.ProviderResult<vscode.DocumentLink> {
41+
return link;
42+
}
43+
}

extensions/vscode/src/extension/VsCodeExtension.ts

+7
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { Battery } from "../util/battery";
3737
import { FileSearch } from "../util/FileSearch";
3838
import { VsCodeIde } from "../VsCodeIde";
3939

40+
import { ConfigYamlDocumentLinkProvider } from "./ConfigYamlDocumentLinkProvider";
4041
import { VsCodeMessenger } from "./VsCodeMessenger";
4142

4243
import type { VsCodeWebviewProtocol } from "../webviewProtocol";
@@ -401,6 +402,12 @@ export class VsCodeExtension {
401402
),
402403
);
403404

405+
const linkProvider = vscode.languages.registerDocumentLinkProvider(
406+
{ language: "yaml" },
407+
new ConfigYamlDocumentLinkProvider(),
408+
);
409+
context.subscriptions.push(linkProvider);
410+
404411
this.ide.onDidChangeActiveTextEditor((filepath) => {
405412
void this.core.invoke("didChangeActiveTextEditor", { filepath });
406413
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { ConfigYaml } from "@continuedev/config-yaml";
2+
import { PencilIcon } from "@heroicons/react/24/outline";
3+
import { useContext } from "react";
4+
import { useAuth } from "../../../context/Auth";
5+
import { IdeMessengerContext } from "../../../context/IdeMessenger";
6+
7+
type SectionKey = Exclude<keyof ConfigYaml, "name" | "version" | "schema">;
8+
9+
interface EditBlockButtonProps<T extends SectionKey> {
10+
blockType: T;
11+
block?: NonNullable<ConfigYaml[T]>[number];
12+
className?: string;
13+
}
14+
15+
function isUsesBlock(block: any): block is { uses: string } {
16+
return typeof block !== "string" && "uses" in block;
17+
}
18+
19+
export default function EditBlockButton<T extends SectionKey>({
20+
block,
21+
blockType,
22+
className = "",
23+
}: EditBlockButtonProps<T>) {
24+
const ideMessenger = useContext(IdeMessengerContext);
25+
const { selectedProfile } = useAuth();
26+
27+
const openUrl = (path: string) =>
28+
ideMessenger.request("controlPlane/openUrl", {
29+
path,
30+
orgSlug: undefined,
31+
});
32+
33+
const handleEdit = () => {
34+
if (selectedProfile?.profileType === "local") {
35+
ideMessenger.post("config/openProfile", {
36+
profileId: undefined,
37+
});
38+
} else if (block && isUsesBlock(block)) {
39+
openUrl(`${block.uses}/new-version`);
40+
} else if (selectedProfile?.fullSlug) {
41+
const slug = `${selectedProfile.fullSlug.ownerSlug}/${selectedProfile.fullSlug.packageSlug}`;
42+
openUrl(`${slug}/new-version`);
43+
}
44+
};
45+
46+
return (
47+
<PencilIcon
48+
className={`h-3 w-3 cursor-pointer text-gray-400 hover:brightness-125 ${className}`}
49+
onClick={handleEdit}
50+
/>
51+
);
52+
}

gui/src/components/mainInput/Lump/sections/docs/DocsIndexingStatus.tsx

+4-35
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
import { ConfigYaml } from "@continuedev/config-yaml";
2-
import {
3-
ArrowPathIcon,
4-
PencilIcon,
5-
StopIcon,
6-
} from "@heroicons/react/24/outline";
2+
import { ArrowPathIcon, StopIcon } from "@heroicons/react/24/outline";
73
import { SiteIndexingConfig } from "core";
84
import { useContext, useMemo, useState } from "react";
9-
import { useAuth } from "../../../../../context/Auth";
105
import { IdeMessengerContext } from "../../../../../context/IdeMessenger";
116
import { useAppDispatch, useAppSelector } from "../../../../../redux/hooks";
127
import { updateIndexingStatus } from "../../../../../redux/slices/indexingSlice";
@@ -16,22 +11,18 @@ import {
1611
} from "../../../../../redux/slices/uiSlice";
1712
import { fontSize } from "../../../../../util";
1813
import ConfirmationDialog from "../../../../dialogs/ConfirmationDialog";
14+
import EditBlockButton from "../../EditBlockButton";
1915
import { StatusIndicator } from "./StatusIndicator";
2016
interface IndexingStatusViewerProps {
2117
docConfig: SiteIndexingConfig;
2218
docFromYaml?: NonNullable<ConfigYaml["docs"]>[number];
2319
}
2420

25-
function isUsesBlock(block: any): block is { uses: string } {
26-
return typeof block !== "string" && "uses" in block;
27-
}
28-
2921
function DocsIndexingStatus({
3022
docConfig,
3123
docFromYaml,
3224
}: IndexingStatusViewerProps) {
3325
const ideMessenger = useContext(IdeMessengerContext);
34-
const { selectedProfile } = useAuth();
3526
const dispatch = useAppDispatch();
3627

3728
const status = useAppSelector(
@@ -77,25 +68,6 @@ function DocsIndexingStatus({
7768
dispatch(setShowDialog(true));
7869
};
7970

80-
const openUrl = (path: string) =>
81-
ideMessenger.request("controlPlane/openUrl", {
82-
path,
83-
orgSlug: undefined,
84-
});
85-
86-
const handleEdit = () => {
87-
console.log("edit", docFromYaml);
88-
if (selectedProfile?.profileType === "local") {
89-
ideMessenger.post("config/openProfile", {
90-
profileId: undefined,
91-
});
92-
} else if (docFromYaml && isUsesBlock(docFromYaml)) {
93-
openUrl(`${docFromYaml.uses}/new-version`);
94-
} else if (selectedProfile?.fullSlug) {
95-
const slug = `${selectedProfile.fullSlug.ownerSlug}/${selectedProfile.fullSlug.packageSlug}`;
96-
openUrl(`${slug}/new-version`);
97-
}
98-
};
9971
const progressPercentage = useMemo(() => {
10072
if (!status) {
10173
return 0;
@@ -161,6 +133,8 @@ function DocsIndexingStatus({
161133
/>
162134
)}
163135

136+
<EditBlockButton blockType="docs" block={docFromYaml} />
137+
164138
{["aborted", "complete", "failed"].includes(
165139
status?.status ?? "",
166140
) && (
@@ -170,11 +144,6 @@ function DocsIndexingStatus({
170144
/>
171145
)}
172146

173-
<PencilIcon
174-
className="h-3 w-3 cursor-pointer text-gray-400 hover:brightness-125"
175-
onClick={handleEdit}
176-
/>
177-
178147
{/* Removed StatusIndicator from here */}
179148
</div>
180149
</div>

gui/src/pages/config/MCPServersPreview.tsx

+24-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ConfigYaml, parseConfigYaml } from "@continuedev/config-yaml";
12
import {
23
ArrowPathIcon,
34
CircleStackIcon,
@@ -6,18 +7,21 @@ import {
67
WrenchScrewdriverIcon,
78
} from "@heroicons/react/24/outline";
89
import { MCPServerStatus } from "core";
9-
import { useContext } from "react";
10+
import { useContext, useMemo } from "react";
1011
import { ToolTip } from "../../components/gui/Tooltip";
12+
import EditBlockButton from "../../components/mainInput/Lump/EditBlockButton";
1113
import { ExploreBlocksButton } from "../../components/mainInput/Lump/sections/ExploreBlocksButton";
14+
import { useAuth } from "../../context/Auth";
1215
import { IdeMessengerContext } from "../../context/IdeMessenger";
1316
import { useAppDispatch, useAppSelector } from "../../redux/hooks";
1417
import { updateConfig } from "../../redux/slices/configSlice";
1518
import { fontSize } from "../../util";
1619

1720
interface MCPServerStatusProps {
1821
server: MCPServerStatus;
22+
serverFromYaml?: NonNullable<ConfigYaml["mcpServers"]>[number];
1923
}
20-
function MCPServerPreview({ server }: MCPServerStatusProps) {
24+
function MCPServerPreview({ server, serverFromYaml }: MCPServerStatusProps) {
2125
const ideMessenger = useContext(IdeMessengerContext);
2226
const config = useAppSelector((store) => store.config.config);
2327
const dispatch = useAppDispatch();
@@ -124,6 +128,7 @@ function MCPServerPreview({ server }: MCPServerStatusProps) {
124128

125129
{/* Refresh button */}
126130
<div className="flex items-center gap-2">
131+
<EditBlockButton blockType={"mcpServers"} block={serverFromYaml} />
127132
<div
128133
className="text-lightgray flex cursor-pointer items-center hover:opacity-80"
129134
onClick={onRefresh}
@@ -153,12 +158,27 @@ function MCPServersPreview() {
153158
const servers = useAppSelector(
154159
(store) => store.config.config.mcpServerStatuses,
155160
);
161+
const { selectedProfile } = useAuth();
162+
163+
const mergedBlocks = useMemo(() => {
164+
const parsed = selectedProfile?.rawYaml
165+
? parseConfigYaml(selectedProfile?.rawYaml ?? "")
166+
: undefined;
167+
return (servers ?? []).map((doc, index) => ({
168+
block: doc,
169+
blockFromYaml: parsed?.mcpServers?.[index],
170+
}));
171+
}, [servers, selectedProfile]);
156172

157173
return (
158174
<div className="flex flex-col gap-1">
159175
<div className="flex max-h-[170px] flex-col gap-1 overflow-y-auto overflow-x-hidden pr-2">
160-
{servers.map((server, idx) => (
161-
<MCPServerPreview key={idx} server={server} />
176+
{mergedBlocks.map(({ block, blockFromYaml }, idx) => (
177+
<MCPServerPreview
178+
key={idx}
179+
server={block}
180+
serverFromYaml={blockFromYaml}
181+
/>
162182
))}
163183
</div>
164184
<ExploreBlocksButton blockType="mcpServers" />

packages/config-yaml/src/load/unroll.ts

+6
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,12 @@ export async function resolveBlock(
346346
// Retrieve block raw yaml
347347
const rawYaml = await registry.getContent(fullSlug);
348348

349+
if (rawYaml === undefined) {
350+
throw new Error(
351+
`Block ${fullSlug.ownerSlug}/${fullSlug.packageSlug} not found`,
352+
);
353+
}
354+
349355
// Convert any input secrets to FQSNs (they get FQSNs as if they are in the block. This is so that we know when to use models add-on / free trial secrets)
350356
const renderedInputs = inputsToFQSNs(inputs || {}, fullSlug);
351357

0 commit comments

Comments
 (0)