From 7a24c10188f709508d3a8a0fd3875600221e1de3 Mon Sep 17 00:00:00 2001 From: Evan <58194240+celestial-vault@users.noreply.github.com> Date: Tue, 20 May 2025 12:31:45 -0700 Subject: [PATCH] Migrate toggleClineRule protobus (#3638) * migrate toggleClineRule * changeset --------- Co-authored-by: Elephant Lumps --- .changeset/hungry-worms-draw.md | 5 + proto/file.proto | 22 + src/core/controller/file/methods.ts | 2 + src/core/controller/file/toggleClineRule.ts | 43 ++ src/core/controller/index.ts | 24 -- src/shared/WebviewMessage.ts | 2 - src/shared/proto/file.ts | 390 ++++++++++++++++++ src/standalone/server-setup.ts | 2 + .../cline-rules/ClineRulesToggleModal.tsx | 20 +- .../src/context/ExtensionStateContext.tsx | 12 + 10 files changed, 493 insertions(+), 29 deletions(-) create mode 100644 .changeset/hungry-worms-draw.md create mode 100644 src/core/controller/file/toggleClineRule.ts diff --git a/.changeset/hungry-worms-draw.md b/.changeset/hungry-worms-draw.md new file mode 100644 index 000000000..6112af0b1 --- /dev/null +++ b/.changeset/hungry-worms-draw.md @@ -0,0 +1,5 @@ +--- +"claude-dev": minor +--- + +Migrate toggleClineRule to protobus diff --git a/proto/file.proto b/proto/file.proto index c0779808e..16b83585f 100644 --- a/proto/file.proto +++ b/proto/file.proto @@ -34,6 +34,9 @@ service FileService { // Search for files in the workspace with fuzzy matching rpc searchFiles(FileSearchRequest) returns (FileSearchResults); + + // Toggle a Cline rule (enable or disable) + rpc toggleClineRule(ToggleClineRuleRequest) returns (ToggleClineRules); } // Request to convert a list of URIs to relative paths @@ -97,3 +100,22 @@ message RuleFile { string display_name = 2; // Filename for display purposes bool already_exists = 3; // For createRuleFile, indicates if file already existed } + +// Request to toggle a Cline rule +message ToggleClineRuleRequest { + Metadata metadata = 1; + bool is_global = 2; // Whether this is a global rule or workspace rule + string rule_path = 3; // Path to the rule file + bool enabled = 4; // Whether to enable or disable the rule +} + +// Maps from filepath to enabled/disabled status, matching app's ClineRulesToggles type +message ClineRulesToggles { + map toggles = 1; +} + +// Response for toggleClineRule operation +message ToggleClineRules { + ClineRulesToggles global_cline_rules_toggles = 1; + ClineRulesToggles local_cline_rules_toggles = 2; +} diff --git a/src/core/controller/file/methods.ts b/src/core/controller/file/methods.ts index 20dc5aa46..8c1f60c44 100644 --- a/src/core/controller/file/methods.ts +++ b/src/core/controller/file/methods.ts @@ -12,6 +12,7 @@ import { openImage } from "./openImage" import { searchCommits } from "./searchCommits" import { searchFiles } from "./searchFiles" import { selectImages } from "./selectImages" +import { toggleClineRule } from "./toggleClineRule" // Register all file service methods export function registerAllMethods(): void { @@ -25,4 +26,5 @@ export function registerAllMethods(): void { registerMethod("searchCommits", searchCommits) registerMethod("searchFiles", searchFiles) registerMethod("selectImages", selectImages) + registerMethod("toggleClineRule", toggleClineRule) } diff --git a/src/core/controller/file/toggleClineRule.ts b/src/core/controller/file/toggleClineRule.ts new file mode 100644 index 000000000..3f1237fc4 --- /dev/null +++ b/src/core/controller/file/toggleClineRule.ts @@ -0,0 +1,43 @@ +import type { ToggleClineRuleRequest, ClineRulesToggles, ToggleClineRules } from "../../../shared/proto/file" +import type { Controller } from "../index" +import { getGlobalState, getWorkspaceState, updateGlobalState, updateWorkspaceState } from "../../../core/storage/state" +import { ClineRulesToggles as AppClineRulesToggles } from "@shared/cline-rules" + +/** + * Toggles a Cline rule (enable or disable) + * @param controller The controller instance + * @param request The toggle request + * @returns The updated Cline rule toggles + */ +export async function toggleClineRule(controller: Controller, request: ToggleClineRuleRequest): Promise { + const { isGlobal, rulePath, enabled } = request + + if (!rulePath || typeof enabled !== "boolean" || typeof isGlobal !== "boolean") { + console.error("toggleClineRule: Missing or invalid parameters", { + rulePath, + isGlobal: typeof isGlobal === "boolean" ? isGlobal : `Invalid: ${typeof isGlobal}`, + enabled: typeof enabled === "boolean" ? enabled : `Invalid: ${typeof enabled}`, + }) + throw new Error("Missing or invalid parameters for toggleClineRule") + } + + // This is the same core logic as in the original handler + if (isGlobal) { + const toggles = ((await getGlobalState(controller.context, "globalClineRulesToggles")) as AppClineRulesToggles) || {} + toggles[rulePath] = enabled + await updateGlobalState(controller.context, "globalClineRulesToggles", toggles) + } else { + const toggles = ((await getWorkspaceState(controller.context, "localClineRulesToggles")) as AppClineRulesToggles) || {} + toggles[rulePath] = enabled + await updateWorkspaceState(controller.context, "localClineRulesToggles", toggles) + } + + // Get the current state to return in the response + const globalToggles = ((await getGlobalState(controller.context, "globalClineRulesToggles")) as AppClineRulesToggles) || {} + const localToggles = ((await getWorkspaceState(controller.context, "localClineRulesToggles")) as AppClineRulesToggles) || {} + + return { + globalClineRulesToggles: { toggles: globalToggles }, + localClineRulesToggles: { toggles: localToggles }, + } +} diff --git a/src/core/controller/index.ts b/src/core/controller/index.ts index b840ff6c1..46bbce607 100644 --- a/src/core/controller/index.ts +++ b/src/core/controller/index.ts @@ -409,30 +409,6 @@ export class Controller { } break } - case "toggleClineRule": { - const { isGlobal, rulePath, enabled } = message - if (rulePath && typeof enabled === "boolean" && typeof isGlobal === "boolean") { - if (isGlobal) { - const toggles = - ((await getGlobalState(this.context, "globalClineRulesToggles")) as ClineRulesToggles) || {} - toggles[rulePath] = enabled - await updateGlobalState(this.context, "globalClineRulesToggles", toggles) - } else { - const toggles = - ((await getWorkspaceState(this.context, "localClineRulesToggles")) as ClineRulesToggles) || {} - toggles[rulePath] = enabled - await updateWorkspaceState(this.context, "localClineRulesToggles", toggles) - } - await this.postStateToWebview() - } else { - console.error("toggleClineRule: Missing or invalid parameters", { - rulePath, - isGlobal: typeof isGlobal === "boolean" ? isGlobal : `Invalid: ${typeof isGlobal}`, - enabled: typeof enabled === "boolean" ? enabled : `Invalid: ${typeof enabled}`, - }) - } - break - } case "toggleWindsurfRule": { const { rulePath, enabled } = message if (rulePath && typeof enabled === "boolean") { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 0b16ff7f7..77d387a9e 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -45,11 +45,9 @@ export interface WebviewMessage { | "searchFiles" | "grpc_request" | "grpc_request_cancel" - | "toggleClineRule" | "toggleCursorRule" | "toggleWindsurfRule" | "toggleWorkflow" - | "deleteClineRule" | "updateTerminalConnectionTimeout" | "setActiveQuote" diff --git a/src/shared/proto/file.ts b/src/shared/proto/file.ts index e24b38c9b..733ed1556 100644 --- a/src/shared/proto/file.ts +++ b/src/shared/proto/file.ts @@ -87,6 +87,33 @@ export interface RuleFile { alreadyExists: boolean } +/** Request to toggle a Cline rule */ +export interface ToggleClineRuleRequest { + metadata?: Metadata | undefined + /** Whether this is a global rule or workspace rule */ + isGlobal: boolean + /** Path to the rule file */ + rulePath: string + /** Whether to enable or disable the rule */ + enabled: boolean +} + +/** Maps from filepath to enabled/disabled status, matching app's ClineRulesToggles type */ +export interface ClineRulesToggles { + toggles: { [key: string]: boolean } +} + +export interface ClineRulesToggles_TogglesEntry { + key: string + value: boolean +} + +/** Response for toggleClineRule operation */ +export interface ToggleClineRules { + globalClineRulesToggles?: ClineRulesToggles | undefined + localClineRulesToggles?: ClineRulesToggles | undefined +} + function createBaseRelativePathsRequest(): RelativePathsRequest { return { metadata: undefined, uris: [] } } @@ -900,6 +927,356 @@ export const RuleFile: MessageFns = { }, } +function createBaseToggleClineRuleRequest(): ToggleClineRuleRequest { + return { metadata: undefined, isGlobal: false, rulePath: "", enabled: false } +} + +export const ToggleClineRuleRequest: MessageFns = { + encode(message: ToggleClineRuleRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.metadata !== undefined) { + Metadata.encode(message.metadata, writer.uint32(10).fork()).join() + } + if (message.isGlobal !== false) { + writer.uint32(16).bool(message.isGlobal) + } + if (message.rulePath !== "") { + writer.uint32(26).string(message.rulePath) + } + if (message.enabled !== false) { + writer.uint32(32).bool(message.enabled) + } + return writer + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ToggleClineRuleRequest { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseToggleClineRuleRequest() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break + } + + message.metadata = Metadata.decode(reader, reader.uint32()) + continue + } + case 2: { + if (tag !== 16) { + break + } + + message.isGlobal = reader.bool() + continue + } + case 3: { + if (tag !== 26) { + break + } + + message.rulePath = reader.string() + continue + } + case 4: { + if (tag !== 32) { + break + } + + message.enabled = reader.bool() + continue + } + } + if ((tag & 7) === 4 || tag === 0) { + break + } + reader.skip(tag & 7) + } + return message + }, + + fromJSON(object: any): ToggleClineRuleRequest { + return { + metadata: isSet(object.metadata) ? Metadata.fromJSON(object.metadata) : undefined, + isGlobal: isSet(object.isGlobal) ? globalThis.Boolean(object.isGlobal) : false, + rulePath: isSet(object.rulePath) ? globalThis.String(object.rulePath) : "", + enabled: isSet(object.enabled) ? globalThis.Boolean(object.enabled) : false, + } + }, + + toJSON(message: ToggleClineRuleRequest): unknown { + const obj: any = {} + if (message.metadata !== undefined) { + obj.metadata = Metadata.toJSON(message.metadata) + } + if (message.isGlobal !== false) { + obj.isGlobal = message.isGlobal + } + if (message.rulePath !== "") { + obj.rulePath = message.rulePath + } + if (message.enabled !== false) { + obj.enabled = message.enabled + } + return obj + }, + + create, I>>(base?: I): ToggleClineRuleRequest { + return ToggleClineRuleRequest.fromPartial(base ?? ({} as any)) + }, + fromPartial, I>>(object: I): ToggleClineRuleRequest { + const message = createBaseToggleClineRuleRequest() + message.metadata = + object.metadata !== undefined && object.metadata !== null ? Metadata.fromPartial(object.metadata) : undefined + message.isGlobal = object.isGlobal ?? false + message.rulePath = object.rulePath ?? "" + message.enabled = object.enabled ?? false + return message + }, +} + +function createBaseClineRulesToggles(): ClineRulesToggles { + return { toggles: {} } +} + +export const ClineRulesToggles: MessageFns = { + encode(message: ClineRulesToggles, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + Object.entries(message.toggles).forEach(([key, value]) => { + ClineRulesToggles_TogglesEntry.encode({ key: key as any, value }, writer.uint32(10).fork()).join() + }) + return writer + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ClineRulesToggles { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseClineRulesToggles() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break + } + + const entry1 = ClineRulesToggles_TogglesEntry.decode(reader, reader.uint32()) + if (entry1.value !== undefined) { + message.toggles[entry1.key] = entry1.value + } + continue + } + } + if ((tag & 7) === 4 || tag === 0) { + break + } + reader.skip(tag & 7) + } + return message + }, + + fromJSON(object: any): ClineRulesToggles { + return { + toggles: isObject(object.toggles) + ? Object.entries(object.toggles).reduce<{ [key: string]: boolean }>((acc, [key, value]) => { + acc[key] = Boolean(value) + return acc + }, {}) + : {}, + } + }, + + toJSON(message: ClineRulesToggles): unknown { + const obj: any = {} + if (message.toggles) { + const entries = Object.entries(message.toggles) + if (entries.length > 0) { + obj.toggles = {} + entries.forEach(([k, v]) => { + obj.toggles[k] = v + }) + } + } + return obj + }, + + create, I>>(base?: I): ClineRulesToggles { + return ClineRulesToggles.fromPartial(base ?? ({} as any)) + }, + fromPartial, I>>(object: I): ClineRulesToggles { + const message = createBaseClineRulesToggles() + message.toggles = Object.entries(object.toggles ?? {}).reduce<{ [key: string]: boolean }>((acc, [key, value]) => { + if (value !== undefined) { + acc[key] = globalThis.Boolean(value) + } + return acc + }, {}) + return message + }, +} + +function createBaseClineRulesToggles_TogglesEntry(): ClineRulesToggles_TogglesEntry { + return { key: "", value: false } +} + +export const ClineRulesToggles_TogglesEntry: MessageFns = { + encode(message: ClineRulesToggles_TogglesEntry, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.key !== "") { + writer.uint32(10).string(message.key) + } + if (message.value !== false) { + writer.uint32(16).bool(message.value) + } + return writer + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ClineRulesToggles_TogglesEntry { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseClineRulesToggles_TogglesEntry() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break + } + + message.key = reader.string() + continue + } + case 2: { + if (tag !== 16) { + break + } + + message.value = reader.bool() + continue + } + } + if ((tag & 7) === 4 || tag === 0) { + break + } + reader.skip(tag & 7) + } + return message + }, + + fromJSON(object: any): ClineRulesToggles_TogglesEntry { + return { + key: isSet(object.key) ? globalThis.String(object.key) : "", + value: isSet(object.value) ? globalThis.Boolean(object.value) : false, + } + }, + + toJSON(message: ClineRulesToggles_TogglesEntry): unknown { + const obj: any = {} + if (message.key !== "") { + obj.key = message.key + } + if (message.value !== false) { + obj.value = message.value + } + return obj + }, + + create, I>>(base?: I): ClineRulesToggles_TogglesEntry { + return ClineRulesToggles_TogglesEntry.fromPartial(base ?? ({} as any)) + }, + fromPartial, I>>(object: I): ClineRulesToggles_TogglesEntry { + const message = createBaseClineRulesToggles_TogglesEntry() + message.key = object.key ?? "" + message.value = object.value ?? false + return message + }, +} + +function createBaseToggleClineRules(): ToggleClineRules { + return { globalClineRulesToggles: undefined, localClineRulesToggles: undefined } +} + +export const ToggleClineRules: MessageFns = { + encode(message: ToggleClineRules, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.globalClineRulesToggles !== undefined) { + ClineRulesToggles.encode(message.globalClineRulesToggles, writer.uint32(10).fork()).join() + } + if (message.localClineRulesToggles !== undefined) { + ClineRulesToggles.encode(message.localClineRulesToggles, writer.uint32(18).fork()).join() + } + return writer + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ToggleClineRules { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseToggleClineRules() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break + } + + message.globalClineRulesToggles = ClineRulesToggles.decode(reader, reader.uint32()) + continue + } + case 2: { + if (tag !== 18) { + break + } + + message.localClineRulesToggles = ClineRulesToggles.decode(reader, reader.uint32()) + continue + } + } + if ((tag & 7) === 4 || tag === 0) { + break + } + reader.skip(tag & 7) + } + return message + }, + + fromJSON(object: any): ToggleClineRules { + return { + globalClineRulesToggles: isSet(object.globalClineRulesToggles) + ? ClineRulesToggles.fromJSON(object.globalClineRulesToggles) + : undefined, + localClineRulesToggles: isSet(object.localClineRulesToggles) + ? ClineRulesToggles.fromJSON(object.localClineRulesToggles) + : undefined, + } + }, + + toJSON(message: ToggleClineRules): unknown { + const obj: any = {} + if (message.globalClineRulesToggles !== undefined) { + obj.globalClineRulesToggles = ClineRulesToggles.toJSON(message.globalClineRulesToggles) + } + if (message.localClineRulesToggles !== undefined) { + obj.localClineRulesToggles = ClineRulesToggles.toJSON(message.localClineRulesToggles) + } + return obj + }, + + create, I>>(base?: I): ToggleClineRules { + return ToggleClineRules.fromPartial(base ?? ({} as any)) + }, + fromPartial, I>>(object: I): ToggleClineRules { + const message = createBaseToggleClineRules() + message.globalClineRulesToggles = + object.globalClineRulesToggles !== undefined && object.globalClineRulesToggles !== null + ? ClineRulesToggles.fromPartial(object.globalClineRulesToggles) + : undefined + message.localClineRulesToggles = + object.localClineRulesToggles !== undefined && object.localClineRulesToggles !== null + ? ClineRulesToggles.fromPartial(object.localClineRulesToggles) + : undefined + return message + }, +} + /** Service for file-related operations */ export type FileServiceDefinition = typeof FileServiceDefinition export const FileServiceDefinition = { @@ -987,6 +1364,15 @@ export const FileServiceDefinition = { responseStream: false, options: {}, }, + /** Toggle a Cline rule (enable or disable) */ + toggleClineRule: { + name: "toggleClineRule", + requestType: ToggleClineRuleRequest, + requestStream: false, + responseType: ToggleClineRules, + responseStream: false, + options: {}, + }, }, } as const @@ -1007,6 +1393,10 @@ export type Exact = P extends Builtin ? P : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never } +function isObject(value: any): boolean { + return typeof value === "object" && value !== null +} + function isSet(value: any): boolean { return value !== null && value !== undefined } diff --git a/src/standalone/server-setup.ts b/src/standalone/server-setup.ts index b499cecd3..b9896e353 100644 --- a/src/standalone/server-setup.ts +++ b/src/standalone/server-setup.ts @@ -29,6 +29,7 @@ import { searchCommits } from "../core/controller/file/searchCommits" import { selectImages } from "../core/controller/file/selectImages" import { getRelativePaths } from "../core/controller/file/getRelativePaths" import { searchFiles } from "../core/controller/file/searchFiles" +import { toggleClineRule } from "../core/controller/file/toggleClineRule" // Mcp Service import { toggleMcpServer } from "../core/controller/mcp/toggleMcpServer" @@ -114,6 +115,7 @@ export function addServices( selectImages: wrapper(selectImages, controller), getRelativePaths: wrapper(getRelativePaths, controller), searchFiles: wrapper(searchFiles, controller), + toggleClineRule: wrapper(toggleClineRule, controller), }) // Mcp Service diff --git a/webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx b/webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx index 9ef7038ba..73e433eef 100644 --- a/webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx +++ b/webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx @@ -3,6 +3,7 @@ import { useClickAway, useWindowSize } from "react-use" import { useExtensionState } from "@/context/ExtensionStateContext" import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock" import { vscode } from "@/utils/vscode" +import { FileServiceClient } from "@/services/grpc-client" import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" import RulesToggleList from "./RulesToggleList" import Tooltip from "@/components/common/Tooltip" @@ -15,6 +16,8 @@ const ClineRulesToggleModal: React.FC = () => { localCursorRulesToggles = {}, localWindsurfRulesToggles = {}, workflowToggles = {}, + setGlobalClineRulesToggles, + setLocalClineRulesToggles, } = useExtensionState() const [isVisible, setIsVisible] = useState(false) const buttonRef = useRef(null) @@ -52,14 +55,25 @@ const ClineRulesToggleModal: React.FC = () => { .map(([path, enabled]): [string, boolean] => [path, enabled as boolean]) .sort(([a], [b]) => a.localeCompare(b)) - // Handle toggle rule + // Handle toggle rule using gRPC const toggleRule = (isGlobal: boolean, rulePath: string, enabled: boolean) => { - vscode.postMessage({ - type: "toggleClineRule", + FileServiceClient.toggleClineRule({ isGlobal, rulePath, enabled, }) + .then((response) => { + // Update the local state with the response + if (response.globalClineRulesToggles?.toggles) { + setGlobalClineRulesToggles(response.globalClineRulesToggles.toggles) + } + if (response.localClineRulesToggles?.toggles) { + setLocalClineRulesToggles(response.localClineRulesToggles.toggles) + } + }) + .catch((error) => { + console.error("Error toggling Cline rule:", error) + }) } const toggleCursorRule = (rulePath: string, enabled: boolean) => { diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 27ea1c62b..c96e7a8da 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -51,6 +51,8 @@ interface ExtensionStateContextType extends ExtensionState { setShellIntegrationTimeout: (value: number) => void setChatSettings: (value: ChatSettings) => void setMcpServers: (value: McpServer[]) => void + setGlobalClineRulesToggles: (toggles: Record) => void + setLocalClineRulesToggles: (toggles: Record) => void // Navigation state setters setShowMcp: (value: boolean) => void @@ -514,6 +516,16 @@ export const ExtensionStateContextProvider: React.FC<{ mcpMarketplaceEnabled: state.mcpMarketplaceEnabled, }) }, + setGlobalClineRulesToggles: (toggles) => + setState((prevState) => ({ + ...prevState, + globalClineRulesToggles: toggles, + })), + setLocalClineRulesToggles: (toggles) => + setState((prevState) => ({ + ...prevState, + localClineRulesToggles: toggles, + })), setMcpTab, }