Compare commits

...

4 Commits

Author SHA1 Message Date
0xtoshii e8f46f4b1b changeset 2025-05-04 11:25:45 -07:00
0xtoshii 976d4f3153 delete refresh 2025-05-04 11:25:17 -07:00
0xtoshii 8591854a80 svg 2025-05-04 10:56:30 -07:00
0xtoshii 88a0cf775d base 2025-05-04 00:28:06 -07:00
9 changed files with 164 additions and 8 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
add ui for windsurf and cursor rules
@@ -5,6 +5,7 @@ import {
deleteRuleFile as deleteRuleFileImpl,
refreshClineRulesToggles,
} from "@core/context/instructions/user-instructions/cline-rules"
import { refreshExternalRulesToggles } from "@core/context/instructions/user-instructions/external-rules"
import * as vscode from "vscode"
import * as path from "path"
import { cwd } from "@core/task"
@@ -32,6 +33,7 @@ export const deleteRuleFile: FileMethodHandler = async (controller: Controller,
}
await refreshClineRulesToggles(controller.context, cwd)
await refreshExternalRulesToggles(controller.context, cwd)
await controller.postStateToWebview()
const fileName = path.basename(request.rulePath)
+37 -1
View File
@@ -48,7 +48,8 @@ import {
} from "../storage/state"
import { Task, cwd } from "../task"
import { ClineRulesToggles } from "@shared/cline-rules"
import { createRuleFile, refreshClineRulesToggles } from "../context/instructions/user-instructions/cline-rules"
import { refreshClineRulesToggles } from "@core/context/instructions/user-instructions/cline-rules"
import { refreshExternalRulesToggles } from "@core/context/instructions/user-instructions/external-rules"
/*
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -375,6 +376,7 @@ export class Controller {
break
case "refreshClineRules":
await refreshClineRulesToggles(this.context, cwd)
await refreshExternalRulesToggles(this.context, cwd)
await this.postStateToWebview()
break
case "openInBrowser":
@@ -516,6 +518,32 @@ export class Controller {
}
break
}
case "toggleWindsurfRule": {
const { rulePath, enabled } = message
if (rulePath && typeof enabled === "boolean") {
const toggles =
((await getWorkspaceState(this.context, "localWindsurfRulesToggles")) as ClineRulesToggles) || {}
toggles[rulePath] = enabled
await updateWorkspaceState(this.context, "localWindsurfRulesToggles", toggles)
await this.postStateToWebview()
} else {
console.error("toggleWindsurfRule: Missing or invalid parameters")
}
break
}
case "toggleCursorRule": {
const { rulePath, enabled } = message
if (rulePath && typeof enabled === "boolean") {
const toggles =
((await getWorkspaceState(this.context, "localCursorRulesToggles")) as ClineRulesToggles) || {}
toggles[rulePath] = enabled
await updateWorkspaceState(this.context, "localCursorRulesToggles", toggles)
await this.postStateToWebview()
} else {
console.error("toggleCursorRule: Missing or invalid parameters")
}
break
}
case "requestTotalTasksSize": {
this.refreshTotalTasksSize()
break
@@ -1755,6 +1783,12 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
const localClineRulesToggles =
((await getWorkspaceState(this.context, "localClineRulesToggles")) as ClineRulesToggles) || {}
const localWindsurfRulesToggles =
((await getWorkspaceState(this.context, "localWindsurfRulesToggles")) as ClineRulesToggles) || {}
const localCursorRulesToggles =
((await getWorkspaceState(this.context, "localCursorRulesToggles")) as ClineRulesToggles) || {}
return {
version: this.context.extension?.packageJSON?.version ?? "",
apiConfiguration,
@@ -1779,6 +1813,8 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
vscMachineId: vscode.env.machineId,
globalClineRulesToggles: globalClineRulesToggles || {},
localClineRulesToggles: localClineRulesToggles || {},
localWindsurfRulesToggles: localWindsurfRulesToggles || {},
localCursorRulesToggles: localCursorRulesToggles || {},
shellIntegrationTimeout,
}
}
+2
View File
@@ -142,6 +142,8 @@ export interface ExtensionState {
vscMachineId: string
globalClineRulesToggles: ClineRulesToggles
localClineRulesToggles: ClineRulesToggles
localCursorRulesToggles: ClineRulesToggles
localWindsurfRulesToggles: ClineRulesToggles
}
export interface ClineMessage {
+2
View File
@@ -67,6 +67,8 @@ export interface WebviewMessage {
| "toggleFavoriteModel"
| "grpc_request"
| "toggleClineRule"
| "toggleCursorRule"
| "toggleWindsurfRule"
| "deleteClineRule"
| "copyToClipboard"
| "updateTerminalConnectionTimeout"
@@ -8,7 +8,12 @@ import RulesToggleList from "./RulesToggleList"
import Tooltip from "@/components/common/Tooltip"
const ClineRulesToggleModal: React.FC = () => {
const { globalClineRulesToggles = {}, localClineRulesToggles = {} } = useExtensionState()
const {
globalClineRulesToggles = {},
localClineRulesToggles = {},
localCursorRulesToggles = {},
localWindsurfRulesToggles = {},
} = useExtensionState()
const [isVisible, setIsVisible] = useState(false)
const buttonRef = useRef<HTMLDivElement>(null)
const modalRef = useRef<HTMLDivElement>(null)
@@ -32,6 +37,14 @@ const ClineRulesToggleModal: React.FC = () => {
.map(([path, enabled]): [string, boolean] => [path, enabled as boolean])
.sort(([a], [b]) => a.localeCompare(b))
const cursorRules = Object.entries(localCursorRulesToggles || {})
.map(([path, enabled]): [string, boolean] => [path, enabled as boolean])
.sort(([a], [b]) => a.localeCompare(b))
const windsurfRules = Object.entries(localWindsurfRulesToggles || {})
.map(([path, enabled]): [string, boolean] => [path, enabled as boolean])
.sort(([a], [b]) => a.localeCompare(b))
// Handle toggle rule
const toggleRule = (isGlobal: boolean, rulePath: string, enabled: boolean) => {
vscode.postMessage({
@@ -42,6 +55,22 @@ const ClineRulesToggleModal: React.FC = () => {
})
}
const toggleCursorRule = (rulePath: string, enabled: boolean) => {
vscode.postMessage({
type: "toggleCursorRule",
rulePath,
enabled,
})
}
const toggleWindsurfRule = (rulePath: string, enabled: boolean) => {
vscode.postMessage({
type: "toggleWindsurfRule",
rulePath,
enabled,
})
}
// Close modal when clicking outside
useClickAway(modalRef, () => {
setIsVisible(false)
@@ -117,6 +146,9 @@ const ClineRulesToggleModal: React.FC = () => {
toggleRule={(rulePath, enabled) => toggleRule(true, rulePath, enabled)}
listGap="small"
isGlobal={true}
ruleType={"cline"}
showNewRule={true}
showNoRules={true}
/>
</div>
@@ -128,6 +160,27 @@ const ClineRulesToggleModal: React.FC = () => {
toggleRule={(rulePath, enabled) => toggleRule(false, rulePath, enabled)}
listGap="small"
isGlobal={false}
ruleType={"cline"}
showNewRule={false}
showNoRules={false}
/>
<RulesToggleList
rules={cursorRules}
toggleRule={toggleCursorRule}
listGap="small"
isGlobal={false}
ruleType={"cursor"}
showNewRule={false}
showNoRules={false}
/>
<RulesToggleList
rules={windsurfRules}
toggleRule={toggleWindsurfRule}
listGap="small"
isGlobal={false}
ruleType={"windsurf"}
showNewRule={true}
showNoRules={localRules.length === 0 && cursorRules.length === 0 && windsurfRules.length === 0}
/>
</div>
</div>
@@ -6,11 +6,53 @@ const RuleRow: React.FC<{
rulePath: string
enabled: boolean
isGlobal: boolean
ruleType: string
toggleRule: (rulePath: string, enabled: boolean) => void
}> = ({ rulePath, enabled, isGlobal, toggleRule }) => {
}> = ({ rulePath, enabled, isGlobal, toggleRule, ruleType }) => {
// Get the filename from the path for display
const displayName = rulePath.split("/").pop() || rulePath
const getRuleTypeIcon = () => {
switch (ruleType) {
case "cursor":
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
style={{ verticalAlign: "middle" }}>
<g fill="none" stroke="currentColor" strokeWidth="1.2">
<path d="M12 4L5 8l7 4 7-4-7-4z" fill="rgba(255,255,255,0.2)" />
<path d="M5 8v8l7 4v-8L5 8z" fill="rgba(255,255,255,0.1)" />
<path d="M19 8v8l-7 4v-8l7-4z" fill="rgba(255,255,255,0.15)" />
<line x1="5" y1="8" x2="12" y2="12" />
<line x1="12" y1="12" x2="19" y2="8" />
<line x1="12" y1="12" x2="12" y2="20" />
</g>
</svg>
)
case "windsurf":
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
style={{ verticalAlign: "middle" }}>
<g fill="currentColor" stroke="currentColor" strokeWidth="1">
<path d="M6 18L16 5L14 18H6z" fill="currentColor" />
<line x1="14" y1="18" x2="16" y2="5" strokeWidth="1.5" />
<path d="M4 19h12c0.5 0 1-0.3 1-1s-0.3-1-1-1H4c-0.5 0-1 0.3-1 1s0.3 1 1 1z" fill="currentColor" />
<line x1="14" y1="13" x2="16" y2="9" strokeWidth="1" />
</g>
</svg>
)
default:
return null
}
}
const handleEditClick = () => {
FileServiceClient.openFile({ value: rulePath }).catch((err) => console.error("Failed to open file:", err))
}
@@ -31,6 +73,7 @@ const RuleRow: React.FC<{
enabled ? "opacity-100" : "opacity-60"
}`}>
<span className="flex-1 overflow-hidden break-all whitespace-normal flex items-center mr-1" title={rulePath}>
{getRuleTypeIcon() && <span className="mr-1.5">{getRuleTypeIcon()}</span>}
{displayName}
</span>
@@ -6,11 +6,17 @@ const RulesToggleList = ({
toggleRule,
listGap = "medium",
isGlobal,
ruleType,
showNewRule,
showNoRules,
}: {
rules: [string, boolean][]
toggleRule: (rulePath: string, enabled: boolean) => void
listGap?: "small" | "medium" | "large"
isGlobal: boolean
ruleType: string
showNewRule: boolean
showNoRules: boolean
}) => {
const gapClasses = {
small: "gap-0",
@@ -31,16 +37,19 @@ const RulesToggleList = ({
enabled={enabled}
isGlobal={isGlobal}
toggleRule={toggleRule}
ruleType={ruleType}
/>
))}
<NewRuleRow isGlobal={isGlobal} />
{showNewRule && <NewRuleRow isGlobal={isGlobal} />}
</>
) : (
<>
<div className="flex flex-col items-center gap-3 my-3 text-[var(--vscode-descriptionForeground)]">
No rules found
</div>
<NewRuleRow isGlobal={isGlobal} />
{showNoRules && (
<div className="flex flex-col items-center gap-3 my-3 text-[var(--vscode-descriptionForeground)]">
No rules found
</div>
)}
{showNewRule && <NewRuleRow isGlobal={isGlobal} />}
</>
)}
</div>
@@ -70,6 +70,8 @@ export const ExtensionStateContextProvider: React.FC<{
planActSeparateModelsSetting: true,
globalClineRulesToggles: {},
localClineRulesToggles: {},
localCursorRulesToggles: {},
localWindsurfRulesToggles: {},
shellIntegrationTimeout: 4000, // default timeout for shell integration
})
const [didHydrateState, setDidHydrateState] = useState(false)
@@ -219,6 +221,8 @@ export const ExtensionStateContextProvider: React.FC<{
mcpTab,
globalClineRulesToggles: state.globalClineRulesToggles || {},
localClineRulesToggles: state.localClineRulesToggles || {},
localCursorRulesToggles: state.localCursorRulesToggles || {},
localWindsurfRulesToggles: state.localWindsurfRulesToggles || {},
setApiConfiguration: (value) =>
setState((prevState) => ({
...prevState,