Skip to content

Commit d152835

Browse files
committed
fix: merge conflicts
2 parents a6b2b6b + b461fdc commit d152835

File tree

9 files changed

+230
-16
lines changed

9 files changed

+230
-16
lines changed

core/config/workspace/workspaceBlocks.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from "@continuedev/config-yaml";
88
import * as YAML from "yaml";
99
import { IDE } from "../..";
10+
import { getContinueGlobalPath } from "../../util/paths";
1011
import { joinPathsToUri } from "../../util/uri";
1112

1213
const BLOCK_TYPE_CONFIG: Record<
@@ -114,8 +115,13 @@ export async function findAvailableFilename(
114115
blockType: BlockType,
115116
fileExists: (uri: string) => Promise<boolean>,
116117
extension?: string,
118+
isGlobal?: boolean,
117119
): Promise<string> {
118-
const baseFilename = `new-${BLOCK_TYPE_CONFIG[blockType]?.filename}`;
120+
// Differentiate filename based on whether its a global rule or a workspace rule
121+
const baseFilename =
122+
blockType === "rules" && isGlobal
123+
? "global-rule"
124+
: `new-${BLOCK_TYPE_CONFIG[blockType]?.filename}`;
119125
const fileExtension = extension ?? getFileExtension(blockType);
120126
let counter = 0;
121127
let fileUri: string;
@@ -156,3 +162,28 @@ export async function createNewWorkspaceBlockFile(
156162
await ide.writeFile(fileUri, fileContent);
157163
await ide.openFile(fileUri);
158164
}
165+
166+
export async function createNewGlobalRuleFile(ide: IDE): Promise<void> {
167+
try {
168+
const globalDir = getContinueGlobalPath();
169+
170+
// Create the rules subdirectory within the global directory
171+
const rulesDir = joinPathsToUri(globalDir, "rules");
172+
173+
const fileUri = await findAvailableFilename(
174+
rulesDir,
175+
"rules",
176+
ide.fileExists.bind(ide),
177+
undefined,
178+
true, // isGlobal = true for global rules
179+
);
180+
181+
const fileContent = getFileContent("rules");
182+
183+
await ide.writeFile(fileUri, fileContent);
184+
185+
await ide.openFile(fileUri);
186+
} catch (error) {
187+
throw error;
188+
}
189+
}

core/core.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ import {
6363
setupProviderConfig,
6464
setupQuickstartConfig,
6565
} from "./config/onboarding";
66-
import { createNewWorkspaceBlockFile } from "./config/workspace/workspaceBlocks";
66+
import {
67+
createNewGlobalRuleFile,
68+
createNewWorkspaceBlockFile,
69+
} from "./config/workspace/workspaceBlocks";
6770
import { MCPManagerSingleton } from "./context/mcp/MCPManagerSingleton";
6871
import { performAuth, removeMCPAuth } from "./context/mcp/MCPOauth";
6972
import { setMdmLicenseKey } from "./control-plane/mdm/mdm";
@@ -410,6 +413,17 @@ export class Core {
410413
);
411414
});
412415

416+
on("config/addGlobalRule", async (msg) => {
417+
try {
418+
await createNewGlobalRuleFile(this.ide);
419+
await this.configHandler.reloadConfig(
420+
"Global rule created (config/addGlobalRule message)",
421+
);
422+
} catch (error) {
423+
throw error;
424+
}
425+
});
426+
413427
on("config/openProfile", async (msg) => {
414428
await this.configHandler.openConfigProfile(msg.data.profileId);
415429
});
@@ -812,7 +826,6 @@ export class Core {
812826
if (data?.shouldClearIndexes) {
813827
await this.codeBaseIndexer.clearIndexes();
814828
}
815-
816829
const dirs = data?.dirs ?? (await this.ide.getWorkspaceDirs());
817830
await this.codeBaseIndexer.refreshCodebaseIndex(dirs);
818831
});

core/protocol/core.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export type ToCoreFromIdeOrWebviewProtocol = {
9393
void,
9494
];
9595
"config/addLocalWorkspaceBlock": [{ blockType: BlockType }, void];
96+
"config/addGlobalRule": [undefined, void];
9697
"config/newPromptFile": [undefined, void];
9798
"config/newAssistantFile": [undefined, void];
9899
"config/ideSettingsUpdate": [IdeSettings, void];

core/protocol/passThrough.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const WEBVIEW_TO_CORE_PASS_THROUGH: (keyof ToCoreFromWebviewProtocol)[] =
2222
"config/newAssistantFile",
2323
"config/ideSettingsUpdate",
2424
"config/addLocalWorkspaceBlock",
25+
"config/addGlobalRule",
2526
"config/getSerializedProfileInfo",
2627
"config/deleteModel",
2728
"config/refreshProfiles",

extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/activities/ContinuePluginStartupActivity.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ import java.nio.charset.StandardCharsets
2929
import java.nio.file.Paths
3030
import javax.swing.*
3131
import com.intellij.openapi.components.service
32+
import com.intellij.openapi.module.Module
3233
import com.intellij.openapi.module.ModuleManager
34+
import com.intellij.openapi.project.ModuleListener
3335
import com.intellij.openapi.roots.ModuleRootManager
3436
import com.intellij.openapi.vfs.VirtualFileManager
3537
import com.intellij.openapi.vfs.newvfs.BulkFileListener
@@ -39,6 +41,7 @@ import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent
3941
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent
4042
import com.intellij.ide.ui.LafManagerListener
4143
import com.intellij.openapi.vfs.VirtualFile
44+
import com.intellij.util.Function
4245

4346
fun showTutorial(project: Project) {
4447
val tutorialFileName = getTutorialFileName()
@@ -193,6 +196,41 @@ class ContinuePluginStartupActivity : StartupActivity, DumbAware {
193196
}
194197
})
195198

199+
// Handle workspace directories changes
200+
connection.subscribe(
201+
ModuleListener.TOPIC,
202+
object : ModuleListener {
203+
override fun modulesAdded(project: Project, modules: MutableList<out Module>) {
204+
205+
val allModulePaths = ModuleManager.getInstance(project).modules
206+
.flatMap { module -> ModuleRootManager.getInstance(module).contentRoots.mapNotNull { it.toUriOrNull() } }
207+
208+
val topLevelModulePaths = allModulePaths
209+
.filter { modulePath -> allModulePaths.none { it != modulePath && modulePath.startsWith(it) } }
210+
211+
continuePluginService.workspacePaths = topLevelModulePaths.toTypedArray();
212+
}
213+
214+
override fun moduleRemoved(project: Project, module: Module) {
215+
val removedPaths = ModuleRootManager.getInstance(module).contentRoots.mapNotNull { it.toUriOrNull() } ;
216+
continuePluginService.workspacePaths = continuePluginService.workspacePaths?.toList()?.filter { path -> removedPaths.none {removedPath -> path == removedPath }}?.toTypedArray();
217+
}
218+
219+
override fun modulesRenamed(
220+
project: Project,
221+
modules: MutableList<out Module>,
222+
oldNameProvider: Function<in Module, String>
223+
) {
224+
val allModulePaths = ModuleManager.getInstance(project).modules
225+
.flatMap { module -> ModuleRootManager.getInstance(module).contentRoots.mapNotNull { it.toUriOrNull() } }
226+
227+
val topLevelModulePaths = allModulePaths
228+
.filter { modulePath -> allModulePaths.none { it != modulePath && modulePath.startsWith(it) } }
229+
230+
continuePluginService.workspacePaths = topLevelModulePaths.toTypedArray()
231+
}
232+
}
233+
)
196234

197235
connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, object : FileEditorManagerListener {
198236
override fun fileClosed(source: FileEditorManager, file: VirtualFile) {

extensions/vscode/src/extension/VsCodeExtension.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from "fs";
2+
import path from "path";
23

34
import { IContextProvider } from "core";
45
import { ConfigHandler } from "core/config/ConfigHandler";
@@ -10,6 +11,7 @@ import {
1011
getConfigJsonPath,
1112
getConfigTsPath,
1213
getConfigYamlPath,
14+
getContinueGlobalPath,
1315
} from "core/util/paths";
1416
import { v4 as uuidv4 } from "uuid";
1517
import * as vscode from "vscode";
@@ -463,6 +465,18 @@ export class VsCodeExtension {
463465
void this.configHandler.reloadConfig("config.ts updated - fs file watch");
464466
});
465467

468+
// watch global rules directory for changes
469+
const globalRulesDir = path.join(getContinueGlobalPath(), "rules");
470+
if (fs.existsSync(globalRulesDir)) {
471+
fs.watch(globalRulesDir, { recursive: true }, (eventType, filename) => {
472+
if (filename && filename.endsWith(".md")) {
473+
void this.configHandler.reloadConfig(
474+
"Global rules directory updated - fs file watch",
475+
);
476+
}
477+
});
478+
}
479+
466480
vscode.workspace.onDidChangeTextDocument(async (event) => {
467481
if (event.contentChanges.length > 0) {
468482
selectionManager.documentChanged();
@@ -503,6 +517,21 @@ export class VsCodeExtension {
503517
});
504518
});
505519

520+
vscode.workspace.onDidChangeWorkspaceFolders(async (event) => {
521+
const dirs = vscode.workspace.workspaceFolders?.map(
522+
(folder) => folder.uri,
523+
);
524+
525+
this.ideUtils.setWokspaceDirectories(dirs);
526+
527+
this.core.invoke("index/forceReIndex", {
528+
dirs: [
529+
...event.added.map((folder) => folder.uri.toString()),
530+
...event.removed.map((folder) => folder.uri.toString()),
531+
],
532+
});
533+
});
534+
506535
vscode.workspace.onDidOpenTextDocument(async (event) => {
507536
console.log("onDidOpenTextDocument");
508537
const ast = await getAst(event.fileName, event.getText());

extensions/vscode/src/util/ideUtils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ export class VsCodeIdeUtils {
7474
return this._workspaceDirectories;
7575
}
7676

77+
setWokspaceDirectories(dirs: vscode.Uri[] | undefined): void {
78+
this._workspaceDirectories = dirs;
79+
}
80+
7781
getUniqueId() {
7882
return getUniqueId();
7983
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { ChevronDownIcon, PlusIcon } from "@heroicons/react/24/outline";
2+
import { ToolTip } from "./gui/Tooltip";
3+
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from "./ui";
4+
5+
interface DropdownOption {
6+
value: string;
7+
label: string;
8+
}
9+
10+
interface DropdownButtonProps {
11+
title: string;
12+
options: DropdownOption[];
13+
onOptionClick: (value: string) => void;
14+
addButtonTooltip?: string;
15+
className?: string;
16+
variant?: "default" | "sm";
17+
}
18+
19+
export function DropdownButton({
20+
title,
21+
options,
22+
onOptionClick,
23+
addButtonTooltip,
24+
className = "",
25+
variant = "default",
26+
}: DropdownButtonProps) {
27+
const isSmall = variant === "sm";
28+
const titleSize = isSmall ? "text-sm font-semibold" : "text-xl font-semibold";
29+
const marginBottom = isSmall ? "mb-2" : "mb-4";
30+
31+
return (
32+
<div
33+
className={`${marginBottom} flex items-center justify-between ${className}`}
34+
>
35+
<h3 className={`my-0 ${titleSize}`}>{title}</h3>
36+
<Listbox value={null} onChange={() => {}}>
37+
<div className="relative">
38+
<ToolTip content={addButtonTooltip}>
39+
<ListboxButton
40+
className={`ring-offset-background focus-visible:ring-ring border-description hover:enabled:bg-input hover:enabled:text-foreground text-description inline-flex h-7 items-center justify-center gap-1 whitespace-nowrap rounded-md border border-solid bg-transparent px-1.5 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50`}
41+
aria-label={addButtonTooltip}
42+
>
43+
<PlusIcon className="h-3 w-3" />
44+
<ChevronDownIcon className="h-3 w-3" />
45+
</ListboxButton>
46+
</ToolTip>
47+
<ListboxOptions className="min-w-32 max-w-36" anchor="bottom end">
48+
{options.map((option) => (
49+
<ListboxOption
50+
key={option.value}
51+
value={option.value}
52+
className={({ active }: { active: boolean }) =>
53+
`relative flex cursor-default select-none items-center gap-3 py-2 pl-4 pr-4 ${
54+
active
55+
? "bg-list-active text-list-active-foreground"
56+
: "text-foreground"
57+
}`
58+
}
59+
onClick={() => onOptionClick(option.value)}
60+
>
61+
<span className="block truncate">{option.label}</span>
62+
</ListboxOption>
63+
))}
64+
</ListboxOptions>
65+
</div>
66+
</Listbox>
67+
</div>
68+
);
69+
}

gui/src/pages/config/sections/RulesSection.tsx

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import {
1919
DEFAULT_SYSTEM_MESSAGES_URL,
2020
} from "core/llm/defaultSystemMessages";
2121
import { getRuleDisplayName } from "core/llm/rules/rules-utils";
22-
import { useContext, useMemo } from "react";
22+
import { useContext, useMemo, useState } from "react";
23+
import { DropdownButton } from "../../../components/DropdownButton";
2324
import HeaderButtonWithToolTip from "../../../components/gui/HeaderButtonWithToolTip";
2425
import Switch from "../../../components/gui/Switch";
2526
import { useEditBlock } from "../../../components/mainInput/Lump/useEditBlock";
@@ -173,8 +174,8 @@ const RuleCard: React.FC<RuleCardProps> = ({ rule }) => {
173174
);
174175
}
175176

176-
const smallFont = useFontSize(-2);
177-
const tinyFont = useFontSize(-3);
177+
const smallFont = fontSize(-2);
178+
const tinyFont = fontSize(-3);
178179
return (
179180
<div
180181
className={`border-border flex flex-col rounded-sm px-2 py-1.5 transition-colors ${isDisabled ? "opacity-50" : ""}`}
@@ -386,18 +387,30 @@ function addDefaultSystemMessage(
386387
}
387388
}
388389

390+
// Define dropdown options for global rules
391+
const globalRulesOptions = [
392+
{ value: "workspace", label: "Current workspace" },
393+
{ value: "global", label: "Global" },
394+
];
395+
389396
function RulesSubSection() {
390397
const { selectedProfile } = useAuth();
391398
const config = useAppSelector((store) => store.config.config);
392399
const mode = useAppSelector((store) => store.session.mode);
393400
const ideMessenger = useContext(IdeMessengerContext);
394401
const isLocal = selectedProfile?.profileType === "local";
402+
const [globalRulesMode, setGlobalRulesMode] = useState<string>("workspace");
395403

396-
const handleAddRule = () => {
404+
const handleAddRule = (mode?: string) => {
405+
const currentMode = mode || globalRulesMode;
397406
if (isLocal) {
398-
void ideMessenger.request("config/addLocalWorkspaceBlock", {
399-
blockType: "rules",
400-
});
407+
if (currentMode === "global") {
408+
void ideMessenger.request("config/addGlobalRule", undefined);
409+
} else {
410+
void ideMessenger.request("config/addLocalWorkspaceBlock", {
411+
blockType: "rules",
412+
});
413+
}
401414
} else {
402415
void ideMessenger.request("controlPlane/openUrl", {
403416
path: "?type=rules",
@@ -406,6 +419,11 @@ function RulesSubSection() {
406419
}
407420
};
408421

422+
const handleOptionClick = (value: string) => {
423+
setGlobalRulesMode(value);
424+
handleAddRule(value);
425+
};
426+
409427
const sortedRules: RuleWithSource[] = useMemo(() => {
410428
const rules = [...config.rules.map((rule) => ({ ...rule }))];
411429

@@ -448,12 +466,22 @@ function RulesSubSection() {
448466

449467
return (
450468
<div>
451-
<ConfigHeader
452-
title="Rules"
453-
variant="sm"
454-
onAddClick={handleAddRule}
455-
addButtonTooltip="Add rule"
456-
/>
469+
{isLocal ? (
470+
<DropdownButton
471+
title="Rules"
472+
variant="sm"
473+
options={globalRulesOptions}
474+
onOptionClick={handleOptionClick}
475+
addButtonTooltip="Add rules"
476+
/>
477+
) : (
478+
<ConfigHeader
479+
title="Rules"
480+
variant="sm"
481+
onAddClick={() => handleAddRule()}
482+
addButtonTooltip="Add rules"
483+
/>
484+
)}
457485

458486
<Card>
459487
{sortedRules.length > 0 ? (

0 commit comments

Comments
 (0)