Compare commits

...
13 changed files with 544 additions and 65 deletions
+18
View File
@@ -0,0 +1,18 @@
---
"claude-dev": minor
---
## Checkpoints 2.0: **User-Configurable Checkpoints** Settings
### New Features
- **Global File Exclusions List:** Comprehensive list of files to exclude (build outputs, media files, logs, etc.)
- **Checkpoint Cleanup:** New command to delete all checkpoints and reclaim storage space
### Quality of Life
- **Settings Panel:** Manage all checkpoint settings in one convenient location, providing space for easily adding future checkpoint settings
- **Space Management:** More control over what gets saved in your checkpoints
- **Smart Defaults:** Out-of-the-box checkpoint settings that work for most projects
Settings are preserved when updating, your existing configuration will work as expected.
---
-5
View File
@@ -157,11 +157,6 @@
"default": "full",
"description": "Controls MCP inclusion in prompts, reduces token usage if you only need access to certain functionality."
},
"cline.enableCheckpoints": {
"type": "boolean",
"default": true,
"description": "Enables extension to save checkpoints of workspace throughout the task."
},
"cline.disableBrowserTool": {
"type": "boolean",
"default": false,
+44
View File
@@ -9,6 +9,8 @@ import * as path from "path"
import * as vscode from "vscode"
import { buildApiHandler } from "../../api"
import CheckpointTracker from "../../integrations/checkpoints/CheckpointTracker"
import { CheckpointSettingsManager } from "../../integrations/checkpoints/CheckpointSettings"
import { deleteAllCheckpoints } from "../../integrations/checkpoints/CheckpointUtils"
import { downloadTask } from "../../integrations/misc/export-markdown"
import { openFile, openImage } from "../../integrations/misc/open-file"
import { fetchOpenGraphData, isImageUrl } from "../../integrations/misc/link-preview"
@@ -794,6 +796,48 @@ export class ClineProvider implements vscode.WebviewViewProvider {
}
break
}
case "getCheckpointSettings": {
const settings = await CheckpointSettingsManager.getInstance().getSettings()
await this.postMessageToWebview({
type: "setCheckpointSettings",
checkpointSettings: settings,
})
break
}
case "updateCheckpointSettings": {
if (message.checkpointSettings) {
await CheckpointSettingsManager.getInstance().saveSettings(message.checkpointSettings)
await this.postMessageToWebview({
type: "setCheckpointSettings",
checkpointSettings: message.checkpointSettings,
})
}
break
}
case "openCheckpointsIgnore": {
await vscode.commands.executeCommand("cline.openCheckpointsIgnore")
break
}
case "confirmDeleteAllCheckpoints": {
vscode.window
.showWarningMessage(
"This action will delete all checkpoints that have been created by Cline! Deleting all checkpoints will remove the ability to use these features for all historical tasks.",
"Delete All",
"Cancel",
)
.then(async (selection) => {
if (selection === "Delete All") {
try {
await deleteAllCheckpoints(this.context.globalStoragePath)
vscode.window.showInformationMessage("All checkpoints have been deleted")
} catch (error) {
vscode.window.showErrorMessage("Failed to delete checkpoints")
console.error("Failed to delete all checkpoints:", error)
}
}
})
break
}
case "taskCompletionViewChanges": {
if (message.number) {
await this.cline?.presentMultifileDiff(message.number, true)
+12
View File
@@ -9,6 +9,7 @@ import "./utils/path" // necessary to have access to String.prototype.toPosix
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
import assert from "node:assert"
import { telemetryService } from "./services/telemetry/TelemetryService"
import { CheckpointSettingsManager } from "./integrations/checkpoints/CheckpointSettings"
/*
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
@@ -32,6 +33,9 @@ export function activate(context: vscode.ExtensionContext) {
const sidebarProvider = new ClineProvider(context, outputChannel)
// Initialize CheckpointSettingsManager
CheckpointSettingsManager.initialize(context.globalStorageUri.fsPath)
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(ClineProvider.sideBarId, sidebarProvider, {
webviewOptions: { retainContextWhenHidden: true },
@@ -123,6 +127,14 @@ export function activate(context: vscode.ExtensionContext) {
}),
)
context.subscriptions.push(
vscode.commands.registerCommand("cline.openCheckpointsIgnore", async () => {
const settingsManager = CheckpointSettingsManager.getInstance()
const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(settingsManager.checkpointsIgnorePath))
await vscode.window.showTextDocument(doc)
}),
)
/*
We use the text document content provider API to show the left side for diff view by creating a virtual document for the original content. This makes it readonly so users know to edit the right side if they want to keep their changes.
@@ -2,6 +2,7 @@ import fs from "fs/promises"
import { join } from "path"
import { fileExistsAtPath } from "../../utils/fs"
import { GIT_DISABLED_SUFFIX } from "./CheckpointGitOperations"
import { CheckpointSettingsManager } from "./CheckpointSettings"
/**
* CheckpointExclusions Module
@@ -42,13 +43,9 @@ interface ExclusionResult {
/**
* Returns the default list of file and directory patterns to exclude from checkpoints.
* Combines built-in patterns with workspace-specific LFS patterns.
*
* @param lfsPatterns - Optional array of Git LFS patterns from workspace
* @returns Array of glob patterns to exclude
* @todo Make this configurable by the user
* These patterns will be written to .checkpointsignore when it's created.
*/
export const getDefaultExclusions = (lfsPatterns: string[] = []): string[] => [
export const getDefaultExclusions = (): string[] => [
// Build and Development Artifacts
".git/",
`.git${GIT_DISABLED_SUFFIX}/`,
@@ -74,10 +71,51 @@ export const getDefaultExclusions = (lfsPatterns: string[] = []): string[] => [
// Log Files
...getLogFilePatterns(),
...lfsPatterns,
]
/**
* Writes the combined exclusion patterns to Git's exclude file.
* Creates the info directory if it doesn't exist.
*
* @param gitPath - Path to the .git directory
* @param lfsPatterns - Optional array of Git LFS patterns to include
*/
export const writeExcludesFile = async (gitPath: string, lfsPatterns: string[] = []): Promise<void> => {
const excludesPath = join(gitPath, "info", "exclude")
await fs.mkdir(join(gitPath, "info"), { recursive: true })
const settingsManager = CheckpointSettingsManager.getInstance()
// Ensure .checkpointsignore exists and load its patterns
const ignorePatterns = await settingsManager.getIgnorePatterns()
// Combine patterns and write to git exclude file
const patterns = [...ignorePatterns, ...lfsPatterns]
await fs.writeFile(excludesPath, patterns.join("\n"))
}
/**
* Retrieves Git LFS patterns from the workspace's .gitattributes file.
* Returns an empty array if no patterns found or file doesn't exist.
*
* @param workspacePath - Path to the workspace root
* @returns Array of Git LFS patterns found in .gitattributes
*/
export const getLfsPatterns = async (workspacePath: string): Promise<string[]> => {
try {
const attributesPath = join(workspacePath, ".gitattributes")
if (await fileExistsAtPath(attributesPath)) {
const attributesContent = await fs.readFile(attributesPath, "utf8")
return attributesContent
.split("\n")
.filter((line) => line.includes("filter=lfs"))
.map((line) => line.split(" ")[0].trim())
}
} catch (error) {
console.warn("Failed to read .gitattributes:", error)
}
return []
}
/**
* Returns patterns for common build and development artifact directories
* @returns Array of glob patterns for build artifacts
@@ -295,41 +333,3 @@ function getGeospatialPatterns(): string[] {
function getLogFilePatterns(): string[] {
return ["*.error", "*.log", "*.logs", "*.npm-debug.log*", "*.out", "*.stdout", "yarn-debug.log*", "yarn-error.log*"]
}
/**
* Writes the combined exclusion patterns to Git's exclude file.
* Creates the info directory if it doesn't exist.
*
* @param gitPath - Path to the .git directory
* @param lfsPatterns - Optional array of Git LFS patterns to include
*/
export const writeExcludesFile = async (gitPath: string, lfsPatterns: string[] = []): Promise<void> => {
const excludesPath = join(gitPath, "info", "exclude")
await fs.mkdir(join(gitPath, "info"), { recursive: true })
const patterns = getDefaultExclusions(lfsPatterns)
await fs.writeFile(excludesPath, patterns.join("\n"))
}
/**
* Retrieves Git LFS patterns from the workspace's .gitattributes file.
* Returns an empty array if no patterns found or file doesn't exist.
*
* @param workspacePath - Path to the workspace root
* @returns Array of Git LFS patterns found in .gitattributes
*/
export const getLfsPatterns = async (workspacePath: string): Promise<string[]> => {
try {
const attributesPath = join(workspacePath, ".gitattributes")
if (await fileExistsAtPath(attributesPath)) {
const attributesContent = await fs.readFile(attributesPath, "utf8")
return attributesContent
.split("\n")
.filter((line) => line.includes("filter=lfs"))
.map((line) => line.split(" ")[0].trim())
}
} catch (error) {
console.warn("Failed to read .gitattributes:", error)
}
return []
}
@@ -0,0 +1,203 @@
import fs from "fs/promises"
import * as path from "path"
import * as vscode from "vscode"
import { fileExistsAtPath } from "../../utils/fs"
import { getDefaultExclusions } from "./CheckpointExclusions"
import { CheckpointSettings } from "../../shared/Checkpoints"
/**
* CheckpointSettings Module
*
* Manages user-configurable settings for the Checkpoints system. Key features:
*
* Settings Management:
* - Enable/disable checkpoints functionality
*
* File Exclusions Management:
* - .checkpointsignore file for exclusion patterns
* - Default patterns for common file types
* - User-customizable pattern list
*
* Storage Structure:
* - Settings stored in globalStorage/settings/cline_checkpoints_settings.json
* - Ignore patterns stored in globalStorage/settings/.checkpointsignore
*
* Integration Points:
* - Used by CheckpointTracker for file filtering
* - Consumed by CheckpointExclusions for pattern management
*/
/**
* Default settings values.
* These are used when no settings file exists or when reading fails.
*/
const DEFAULT_SETTINGS: CheckpointSettings = {
enableCheckpoints: true, // Enabled by default
}
/**
* CheckpointSettingsManager Class
*
* Handles all checkpoint settings operations including:
* - Reading and writing settings to disk
* - Managing .checkpointsignore patterns
* - Providing default values when needed
*
* File Structure:
* globalStorage/
* settings/
* cline_checkpoints_settings.json - Contains enable flag
* .checkpointsignore - Contains file exclusion patterns
*/
export class CheckpointSettingsManager {
public readonly settingsDir: string
public readonly checkpointSettingsPath: string
public readonly checkpointsIgnorePath: string
private settings: CheckpointSettings = DEFAULT_SETTINGS
private static instance: CheckpointSettingsManager | null = null
/**
* Creates a new CheckpointSettingsManager instance.
* Initializes paths for settings and ignore files.
*
* @param globalStoragePath - VS Code's global storage path for the extension
*/
private constructor(globalStoragePath: string) {
this.settingsDir = path.join(globalStoragePath, "settings")
this.checkpointSettingsPath = path.join(this.settingsDir, "cline_checkpoints_settings.json")
this.checkpointsIgnorePath = path.join(this.settingsDir, ".checkpointsignore")
this.readSettings().then((settings) => {
this.settings = settings
this.migrateEnableCheckpointsSetting()
})
this.ensureIgnoreFileExists()
}
/**
* Initialize the singleton instance
*/
public static initialize(globalStoragePath: string): void {
if (!CheckpointSettingsManager.instance) {
CheckpointSettingsManager.instance = new CheckpointSettingsManager(globalStoragePath)
}
}
/**
* Get the singleton instance
*/
public static getInstance(): CheckpointSettingsManager {
if (!CheckpointSettingsManager.instance) {
throw new Error("CheckpointSettingsManager not initialized")
}
return CheckpointSettingsManager.instance
}
/**
* Retrieves current checkpoint settings from memory.
*
* @returns CheckpointSettings Current settings
*/
getSettings(): CheckpointSettings {
return this.settings
}
/**
* Reads checkpoint settings from disk.
* Merges stored settings with defaults to ensure all fields are present.
*
* @returns Promise<CheckpointSettings> Settings read from disk, with defaults for any missing values
*/
private async readSettings(): Promise<CheckpointSettings> {
try {
if (await fileExistsAtPath(this.checkpointSettingsPath)) {
const settingsContent = await fs.readFile(this.checkpointSettingsPath, "utf8")
return { ...DEFAULT_SETTINGS, ...JSON.parse(settingsContent) }
}
// If file doesn't exist, create it with default settings
await this.saveSettings(DEFAULT_SETTINGS)
} catch (error) {
console.error("Error reading checkpoint settings:", error)
}
return DEFAULT_SETTINGS
}
/**
* Saves checkpoint settings to disk and updates in-memory settings.
* Creates settings directory if it doesn't exist.
* Merges new settings with existing ones.
*
* @param settings - Partial settings to update
*/
async saveSettings(settings: Partial<CheckpointSettings>): Promise<void> {
// Ensure settings directory exists
await fs.mkdir(this.settingsDir, { recursive: true })
// Merge with current settings
const updatedSettings = { ...this.settings, ...settings }
this.settings = updatedSettings
// Save to disk
await fs.writeFile(this.checkpointSettingsPath, JSON.stringify(updatedSettings, null, 2))
}
/**
* Retrieves patterns from .checkpointsignore file.
* Filters out empty lines and comments.
*
* @returns Promise<string[]> Array of active ignore patterns
*/
async getIgnorePatterns(): Promise<string[]> {
try {
if (await fileExistsAtPath(this.checkpointsIgnorePath)) {
const content = await fs.readFile(this.checkpointsIgnorePath, "utf8")
return content.split("\n").filter((line) => line.trim() && !line.startsWith("#"))
}
} catch (error) {
console.error("Error loading .checkpointsignore:", error)
}
return []
}
/**
* Ensures .checkpointsignore file exists.
* Creates it with default patterns if it doesn't exist.
*/
private async ensureIgnoreFileExists(): Promise<void> {
try {
await fs.mkdir(this.settingsDir, { recursive: true })
if (!(await fileExistsAtPath(this.checkpointsIgnorePath))) {
await fs.writeFile(this.checkpointsIgnorePath, getDefaultExclusions().join("\n"))
}
} catch (error) {
console.error("Error creating .checkpointsignore:", error)
}
}
/**
* Migrates the enableCheckpoints setting from VSCode configuration to settings file
* All checkpoints settings will be kept in the CheckpointSettingsView from now on
*/
private async migrateEnableCheckpointsSetting(): Promise<void> {
const config = vscode.workspace.getConfiguration("cline")
const enableCheckpoints = config.get<boolean>("enableCheckpoints")
if (enableCheckpoints !== undefined) {
// Save to settings file
await this.saveSettings({
enableCheckpoints,
})
// Remove from VSCode configuration
await config.update("enableCheckpoints", undefined, true)
}
}
/**
* Reinitializes the settings manager by reading settings from disk.
* This should be called when a new CheckpointTracker is created.
*/
async reinitialize(): Promise<void> {
this.settings = await this.readSettings()
}
}
@@ -1,10 +1,11 @@
import fs from "fs/promises"
import * as path from "path"
import simpleGit, { SimpleGit } from "simple-git"
import simpleGit, { type SimpleGit } from "simple-git"
import * as vscode from "vscode"
import { HistoryItem } from "../../shared/HistoryItem"
import { GitOperations } from "./CheckpointGitOperations"
import { getShadowGitPath, hashWorkingDir, getWorkingDirectory, detectLegacyCheckpoint } from "./CheckpointUtils"
import { CheckpointSettingsManager } from "./CheckpointSettings"
/**
* CheckpointTracker Module
@@ -85,7 +86,7 @@ class CheckpointTracker {
* - Sets up task-specific branch for new checkpoints
*
* Configuration:
* - Respects 'cline.enableCheckpoints' VS Code setting
* - Uses settings from CheckpointSettingsManager
* - Uses branch-per-task architecture for new checkpoints
* - Maintains backwards compatibility with legacy structure
*/
@@ -96,9 +97,13 @@ class CheckpointTracker {
try {
console.info(`Creating new CheckpointTracker for task ${taskId}`)
// Check if checkpoints are disabled in VS Code settings
const enableCheckpoints = vscode.workspace.getConfiguration("cline").get<boolean>("enableCheckpoints") ?? true
if (!enableCheckpoints) {
// Get settings manager instance and reinitialize
const settingsManager = CheckpointSettingsManager.getInstance()
await settingsManager.reinitialize()
// Check if checkpoints are enabled in settings
const settings = settingsManager.getSettings()
if (!settings.enableCheckpoints) {
return undefined // Don't create tracker when disabled
}
@@ -291,7 +296,6 @@ class CheckpointTracker {
* - the current working directory (including uncommitted changes).
*
* If `rhsHash` is omitted, compares `lhsHash` to the working directory.
* If you want truly untracked files to appear, `git add` them first.
*
* @param lhsHash - The commit to compare from (older commit)
* @param rhsHash - The commit to compare to (newer commit).
@@ -339,7 +343,7 @@ class CheckpointTracker {
const batchSize = 50
// Get list of files that exist in base commit
const existingFiles = await this.getExistingFiles(git, baseHash, files)
const existingFiles = await this.getExistingFiles(git, baseHash)
// Process files in batches
for (let i = 0; i < files.length; i += batchSize) {
@@ -373,7 +377,7 @@ class CheckpointTracker {
let afterContents: string[] = []
if (rhsHash) {
// Split after files into existing and new in target commit
const afterExistingFiles = await this.getExistingFiles(git, rhsHash, batch)
const afterExistingFiles = await this.getExistingFiles(git, rhsHash)
const afterExistingBatch = batch.filter((file) => afterExistingFiles.has(file))
if (afterExistingBatch.length > 0) {
@@ -438,7 +442,7 @@ class CheckpointTracker {
/**
* Helper function to get a set of files that exist in a given commit
*/
private async getExistingFiles(git: SimpleGit, commitHash: string, files: string[]): Promise<Set<string>> {
private async getExistingFiles(git: SimpleGit, commitHash: string): Promise<Set<string>> {
try {
const result = await git.raw(["ls-tree", "-r", "--name-only", commitHash])
const existingFiles = new Set<string>(result.split("\n"))
@@ -1,6 +1,6 @@
import { mkdir } from "fs/promises"
import * as path from "path"
import * as vscode from "vscode"
import fs from "fs/promises"
import os from "os"
import { fileExistsAtPath } from "../../utils/fs"
@@ -26,9 +26,10 @@ export async function getLegacyShadowGitPath(globalStoragePath: string, taskId:
throw new Error("Global storage uri is invalid")
}
const checkpointsDir = path.join(globalStoragePath, "tasks", taskId, "checkpoints")
await mkdir(checkpointsDir, { recursive: true })
await fs.mkdir(checkpointsDir, { recursive: true })
const gitPath = path.join(checkpointsDir, ".git")
console.info(`Legacy shadow git path: ${gitPath}`)
console.log(`Legacy shadow git path: ${gitPath}`)
return gitPath
}
@@ -63,7 +64,8 @@ export async function getShadowGitPath(
throw new Error("Global storage uri is invalid")
}
const checkpointsDir = path.join(globalStoragePath, "checkpoints", cwdHash)
await mkdir(checkpointsDir, { recursive: true })
await fs.mkdir(checkpointsDir, { recursive: true })
const gitPath = path.join(checkpointsDir, ".git")
return gitPath
}
@@ -154,6 +156,40 @@ export async function detectLegacyCheckpoint(globalStoragePath: string | undefin
}
const legacyGitPath = path.join(globalStoragePath, "tasks", taskId, "checkpoints", ".git")
const isLegacy = await fileExistsAtPath(legacyGitPath)
console.info(`Legacy checkpoint detection result: ${isLegacy}`)
return isLegacy
}
/**
* Deletes all checkpoint data across all tasks.
* This is a destructive operation that removes all checkpoint history.
* Handles both legacy checkpoints (under tasks/{taskId}/checkpoints/.git/)
* and branch-per-task checkpoints (under checkpoints/{workspaceHash}/.git/).
*
* @param globalStoragePath - The VS Code global storage path
* @throws Error if deletion fails or if global storage path is invalid
*/
export async function deleteAllCheckpoints(globalStoragePath: string): Promise<void> {
if (!globalStoragePath) {
throw new Error("Global storage path is invalid")
}
// Delete legacy checkpoints
const tasksDir = path.join(globalStoragePath, "tasks")
if (await fileExistsAtPath(tasksDir)) {
const taskDirs = await fs.readdir(tasksDir)
for (const taskId of taskDirs) {
const checkpointsDir = path.join(tasksDir, taskId, "checkpoints")
if (await fileExistsAtPath(checkpointsDir)) {
await fs.rm(checkpointsDir, { recursive: true, force: true })
}
}
}
// Delete branch-per-task checkpoints
const checkpointsDir = path.join(globalStoragePath, "checkpoints")
if (await fileExistsAtPath(checkpointsDir)) {
await fs.rm(checkpointsDir, { recursive: true, force: true })
}
}
+4
View File
@@ -0,0 +1,4 @@
export interface CheckpointSettings {
/** Whether checkpoints are enabled */
enableCheckpoints: boolean
}
+3
View File
@@ -8,6 +8,7 @@ import { ChatSettings } from "./ChatSettings"
import { HistoryItem } from "./HistoryItem"
import { McpServer, McpMarketplaceCatalog, McpMarketplaceItem, McpDownloadResponse } from "./mcp"
import { TelemetrySetting } from "./TelemetrySetting"
import { CheckpointSettings } from "./Checkpoints"
// webview will hold state
export interface ExtensionMessage {
@@ -33,6 +34,7 @@ export interface ExtensionMessage {
| "commitSearchResults"
| "openGraphData"
| "isImageUrlResult"
| "setCheckpointSettings"
text?: string
action?:
| "chatButtonClicked"
@@ -56,6 +58,7 @@ export interface ExtensionMessage {
mcpMarketplaceCatalog?: McpMarketplaceCatalog
error?: string
mcpDownloadDetails?: McpDownloadResponse
checkpointSettings?: CheckpointSettings
commits?: GitCommit[]
openGraphData?: {
title?: string
+6
View File
@@ -3,6 +3,7 @@ import { AutoApprovalSettings } from "./AutoApprovalSettings"
import { BrowserSettings } from "./BrowserSettings"
import { ChatSettings } from "./ChatSettings"
import { ChatContent } from "./ChatContent"
import { CheckpointSettings } from "./Checkpoints"
export interface WebviewMessage {
type:
@@ -36,6 +37,10 @@ export interface WebviewMessage {
| "togglePlanActMode"
| "checkpointDiff"
| "checkpointRestore"
| "getCheckpointSettings"
| "updateCheckpointSettings"
| "openCheckpointsIgnore"
| "confirmDeleteAllCheckpoints"
| "taskCompletionViewChanges"
| "openExtensionSettings"
| "requestVsCodeLmModels"
@@ -67,6 +72,7 @@ export interface WebviewMessage {
number?: number
autoApprovalSettings?: AutoApprovalSettings
browserSettings?: BrowserSettings
checkpointSettings?: CheckpointSettings
chatSettings?: ChatSettings
chatContent?: ChatContent
mcpId?: string
@@ -0,0 +1,102 @@
import { VSCodeButton, VSCodeLink, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { memo, useEffect, useState } from "react"
import { vscode } from "../../utils/vscode"
import { CheckpointSettings } from "../../../../src/shared/Checkpoints"
const CheckpointsSettingsView = () => {
const [settings, setSettings] = useState<CheckpointSettings>({
enableCheckpoints: true,
})
useEffect(() => {
// Message extension to get initial settings
vscode.postMessage({
type: "getCheckpointSettings",
})
}, [])
useEffect(() => {
const messageHandler = (event: MessageEvent) => {
const message = event.data
switch (message.type) {
case "setCheckpointSettings": {
const newSettings = message.checkpointSettings
setSettings(newSettings)
}
}
}
window.addEventListener("message", messageHandler)
return () => window.removeEventListener("message", messageHandler)
}, [])
const handleEnableChange = (e: any) => {
const checkbox = e.target as HTMLInputElement
const newSettings = {
...settings,
enableCheckpoints: checkbox.checked,
}
setSettings(newSettings)
vscode.postMessage({
type: "updateCheckpointSettings",
checkpointSettings: newSettings,
})
}
const handleDeleteCheckpoints = () => {
vscode.postMessage({
type: "confirmDeleteAllCheckpoints",
})
}
return (
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
<div>
<div style={{ fontWeight: "500", marginBottom: "8px" }}>Enable Checkpoints</div>
<VSCodeCheckbox checked={settings.enableCheckpoints} onChange={handleEnableChange}>
Enable checkpoints
</VSCodeCheckbox>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
Automatically create checkpoints when Cline makes changes to your files
</p>
</div>
<div>
<div style={{ fontWeight: "500", marginBottom: "8px" }}>Checkpoints File Exclusions</div>
<VSCodeLink onClick={() => vscode.postMessage({ type: "openCheckpointsIgnore" })}>
Edit .checkpointsignore
</VSCodeLink>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
Configure which files and directories to exclude from checkpoint creation globally
</p>
</div>
<div>
<div style={{ fontWeight: "500", marginBottom: "8px" }}>Delete All Checkpoints</div>
<VSCodeButton onClick={handleDeleteCheckpoints}>
<i className="codicon codicon-trash" style={{ marginRight: "6px" }} />
Delete All
</VSCodeButton>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
Permanently remove all saved checkpoints from your system
</p>
</div>
</div>
)
}
export default memo(CheckpointsSettingsView)
@@ -3,14 +3,18 @@ import { memo, useEffect, useState } from "react"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { validateApiConfiguration, validateModelId } from "../../utils/validate"
import { vscode } from "../../utils/vscode"
import SettingsButton from "../common/SettingsButton"
import ApiOptions from "./ApiOptions"
import SettingsButton from "../common/SettingsButton"
import CheckpointsSettingsView from "./CheckpointSettingsView"
const { IS_DEV } = process.env
type SettingsViewProps = {
onDone: () => void
}
type View = "main" | "checkpoints"
const SettingsView = ({ onDone }: SettingsViewProps) => {
const {
apiConfiguration,
@@ -23,6 +27,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
} = useExtensionState()
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
const [modelIdErrorMessage, setModelIdErrorMessage] = useState<string | undefined>(undefined)
const [currentView, setCurrentView] = useState<View>("main")
const handleSubmit = () => {
const apiValidationResult = validateApiConfiguration(apiConfiguration)
@@ -66,6 +71,45 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
vscode.postMessage({ type: "resetState" })
}
if (currentView === "checkpoints") {
return (
<div
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
padding: "10px 0px 0px 20px",
display: "flex",
flexDirection: "column",
overflow: "hidden",
}}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "17px",
paddingRight: 17,
}}>
<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>Checkpoint Settings</h3>
<VSCodeButton onClick={() => setCurrentView("main")}>Done</VSCodeButton>
</div>
<div
style={{
flexGrow: 1,
overflowY: "scroll",
paddingRight: 8,
display: "flex",
flexDirection: "column",
}}>
<CheckpointsSettingsView />
</div>
</div>
)
}
return (
<div
style={{
@@ -126,6 +170,14 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
</p>
</div>
<div style={{ marginBottom: 5 }}>
<div style={{ fontWeight: "500", marginBottom: "8px" }}>Checkpoints</div>
<SettingsButton onClick={() => setCurrentView("checkpoints")}>
<i className="codicon codicon-bookmark" />
Configure Checkpoints
</SettingsButton>
</div>
<div style={{ marginBottom: 5 }}>
<VSCodeCheckbox
style={{ marginBottom: "5px" }}