mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-22 05:11:06 -05:00
Add a Git section to the context mentions
This commit is contained in:
@@ -12,7 +12,7 @@ import { ApiHandler, SingleCompletionHandler, buildApiHandler } from "../api"
|
||||
import { ApiStream } from "../api/transform/stream"
|
||||
import { DiffViewProvider } from "../integrations/editor/DiffViewProvider"
|
||||
import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown"
|
||||
import { extractTextFromFile, addLineNumbers, stripLineNumbers, everyLineHasLineNumbers } from "../integrations/misc/extract-text"
|
||||
import { extractTextFromFile, addLineNumbers, stripLineNumbers, everyLineHasLineNumbers, truncateOutput } from "../integrations/misc/extract-text"
|
||||
import { TerminalManager } from "../integrations/terminal/TerminalManager"
|
||||
import { UrlContentFetcher } from "../services/browser/UrlContentFetcher"
|
||||
import { listFiles } from "../services/glob/list-files"
|
||||
@@ -716,22 +716,6 @@ export class Cline {
|
||||
}
|
||||
})
|
||||
|
||||
const getFormattedOutput = async () => {
|
||||
const { terminalOutputLineLimit } = await this.providerRef.deref()?.getState() ?? {}
|
||||
const limit = terminalOutputLineLimit ?? 0
|
||||
|
||||
if (limit > 0 && lines.length > limit) {
|
||||
const beforeLimit = Math.floor(limit * 0.2) // 20% of lines before
|
||||
const afterLimit = limit - beforeLimit // remaining 80% after
|
||||
return [
|
||||
...lines.slice(0, beforeLimit),
|
||||
`\n[...${lines.length - limit} lines omitted...]\n`,
|
||||
...lines.slice(-afterLimit)
|
||||
].join('\n')
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
let completed = false
|
||||
process.once("completed", () => {
|
||||
completed = true
|
||||
@@ -750,7 +734,8 @@ export class Cline {
|
||||
// grouping command_output messages despite any gaps anyways)
|
||||
await delay(50)
|
||||
|
||||
const output = await getFormattedOutput()
|
||||
const { terminalOutputLineLimit } = await this.providerRef.deref()?.getState() ?? {}
|
||||
const output = truncateOutput(lines.join('\n'), terminalOutputLineLimit)
|
||||
const result = output.trim()
|
||||
|
||||
if (userFeedback) {
|
||||
|
||||
155
src/core/mentions/__tests__/index.test.ts
Normal file
155
src/core/mentions/__tests__/index.test.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
// Create mock vscode module before importing anything
|
||||
const createMockUri = (scheme: string, path: string) => ({
|
||||
scheme,
|
||||
authority: '',
|
||||
path,
|
||||
query: '',
|
||||
fragment: '',
|
||||
fsPath: path,
|
||||
with: jest.fn(),
|
||||
toString: () => path,
|
||||
toJSON: () => ({
|
||||
scheme,
|
||||
authority: '',
|
||||
path,
|
||||
query: '',
|
||||
fragment: ''
|
||||
})
|
||||
})
|
||||
|
||||
const mockExecuteCommand = jest.fn()
|
||||
const mockOpenExternal = jest.fn()
|
||||
const mockShowErrorMessage = jest.fn()
|
||||
|
||||
const mockVscode = {
|
||||
workspace: {
|
||||
workspaceFolders: [{
|
||||
uri: { fsPath: "/test/workspace" }
|
||||
}]
|
||||
},
|
||||
window: {
|
||||
showErrorMessage: mockShowErrorMessage,
|
||||
showInformationMessage: jest.fn(),
|
||||
showWarningMessage: jest.fn(),
|
||||
createTextEditorDecorationType: jest.fn(),
|
||||
createOutputChannel: jest.fn(),
|
||||
createWebviewPanel: jest.fn(),
|
||||
activeTextEditor: undefined
|
||||
},
|
||||
commands: {
|
||||
executeCommand: mockExecuteCommand
|
||||
},
|
||||
env: {
|
||||
openExternal: mockOpenExternal
|
||||
},
|
||||
Uri: {
|
||||
parse: jest.fn((url: string) => createMockUri('https', url)),
|
||||
file: jest.fn((path: string) => createMockUri('file', path))
|
||||
},
|
||||
Position: jest.fn(),
|
||||
Range: jest.fn(),
|
||||
TextEdit: jest.fn(),
|
||||
WorkspaceEdit: jest.fn(),
|
||||
DiagnosticSeverity: {
|
||||
Error: 0,
|
||||
Warning: 1,
|
||||
Information: 2,
|
||||
Hint: 3
|
||||
}
|
||||
}
|
||||
|
||||
// Mock modules
|
||||
jest.mock('vscode', () => mockVscode)
|
||||
jest.mock("../../../services/browser/UrlContentFetcher")
|
||||
jest.mock("../../../utils/git")
|
||||
|
||||
// Now import the modules that use the mocks
|
||||
import { parseMentions, openMention } from "../index"
|
||||
import { UrlContentFetcher } from "../../../services/browser/UrlContentFetcher"
|
||||
import * as git from "../../../utils/git"
|
||||
|
||||
describe("mentions", () => {
|
||||
const mockCwd = "/test/workspace"
|
||||
let mockUrlContentFetcher: UrlContentFetcher
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
// Create a mock instance with just the methods we need
|
||||
mockUrlContentFetcher = {
|
||||
launchBrowser: jest.fn().mockResolvedValue(undefined),
|
||||
closeBrowser: jest.fn().mockResolvedValue(undefined),
|
||||
urlToMarkdown: jest.fn().mockResolvedValue(""),
|
||||
} as unknown as UrlContentFetcher
|
||||
})
|
||||
|
||||
describe("parseMentions", () => {
|
||||
it("should parse git commit mentions", async () => {
|
||||
const commitHash = "abc1234"
|
||||
const commitInfo = `abc1234 Fix bug in parser
|
||||
|
||||
Author: John Doe
|
||||
Date: Mon Jan 5 23:50:06 2025 -0500
|
||||
|
||||
Detailed commit message with multiple lines
|
||||
- Fixed parsing issue
|
||||
- Added tests`
|
||||
|
||||
jest.mocked(git.getCommitInfo).mockResolvedValue(commitInfo)
|
||||
|
||||
const result = await parseMentions(
|
||||
`Check out this commit @${commitHash}`,
|
||||
mockCwd,
|
||||
mockUrlContentFetcher
|
||||
)
|
||||
|
||||
expect(result).toContain(`'${commitHash}' (see below for commit info)`)
|
||||
expect(result).toContain(`<git_commit hash="${commitHash}">`)
|
||||
expect(result).toContain(commitInfo)
|
||||
})
|
||||
|
||||
it("should handle errors fetching git info", async () => {
|
||||
const commitHash = "abc1234"
|
||||
const errorMessage = "Failed to get commit info"
|
||||
|
||||
jest.mocked(git.getCommitInfo).mockRejectedValue(new Error(errorMessage))
|
||||
|
||||
const result = await parseMentions(
|
||||
`Check out this commit @${commitHash}`,
|
||||
mockCwd,
|
||||
mockUrlContentFetcher
|
||||
)
|
||||
|
||||
expect(result).toContain(`'${commitHash}' (see below for commit info)`)
|
||||
expect(result).toContain(`<git_commit hash="${commitHash}">`)
|
||||
expect(result).toContain(`Error fetching commit info: ${errorMessage}`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("openMention", () => {
|
||||
it("should handle file paths and problems", async () => {
|
||||
await openMention("/path/to/file")
|
||||
expect(mockExecuteCommand).not.toHaveBeenCalled()
|
||||
expect(mockOpenExternal).not.toHaveBeenCalled()
|
||||
expect(mockShowErrorMessage).toHaveBeenCalledWith("Could not open file!")
|
||||
|
||||
await openMention("problems")
|
||||
expect(mockExecuteCommand).toHaveBeenCalledWith("workbench.actions.view.problems")
|
||||
})
|
||||
|
||||
it("should handle URLs", async () => {
|
||||
const url = "https://example.com"
|
||||
await openMention(url)
|
||||
const mockUri = mockVscode.Uri.parse(url)
|
||||
expect(mockOpenExternal).toHaveBeenCalled()
|
||||
const calledArg = mockOpenExternal.mock.calls[0][0]
|
||||
expect(calledArg).toEqual(expect.objectContaining({
|
||||
scheme: mockUri.scheme,
|
||||
authority: mockUri.authority,
|
||||
path: mockUri.path,
|
||||
query: mockUri.query,
|
||||
fragment: mockUri.fragment
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2,27 +2,28 @@ import * as vscode from "vscode"
|
||||
import * as path from "path"
|
||||
import { openFile } from "../../integrations/misc/open-file"
|
||||
import { UrlContentFetcher } from "../../services/browser/UrlContentFetcher"
|
||||
import { mentionRegexGlobal } from "../../shared/context-mentions"
|
||||
import { mentionRegexGlobal, formatGitSuggestion, type MentionSuggestion } from "../../shared/context-mentions"
|
||||
import fs from "fs/promises"
|
||||
import { extractTextFromFile } from "../../integrations/misc/extract-text"
|
||||
import { isBinaryFile } from "isbinaryfile"
|
||||
import { diagnosticsToProblemsString } from "../../integrations/diagnostics"
|
||||
import { getCommitInfo, getWorkingState } from "../../utils/git"
|
||||
|
||||
export function openMention(mention?: string): void {
|
||||
export async function openMention(mention?: string): Promise<void> {
|
||||
if (!mention) {
|
||||
return
|
||||
}
|
||||
|
||||
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
|
||||
if (!cwd) {
|
||||
return
|
||||
}
|
||||
|
||||
if (mention.startsWith("/")) {
|
||||
const relPath = mention.slice(1)
|
||||
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
|
||||
if (!cwd) {
|
||||
return
|
||||
}
|
||||
const absPath = path.resolve(cwd, relPath)
|
||||
if (mention.endsWith("/")) {
|
||||
vscode.commands.executeCommand("revealInExplorer", vscode.Uri.file(absPath))
|
||||
// vscode.commands.executeCommand("vscode.openFolder", , { forceNewWindow: false }) opens in new window
|
||||
} else {
|
||||
openFile(absPath)
|
||||
}
|
||||
@@ -40,12 +41,16 @@ export async function parseMentions(text: string, cwd: string, urlContentFetcher
|
||||
if (mention.startsWith("http")) {
|
||||
return `'${mention}' (see below for site content)`
|
||||
} else if (mention.startsWith("/")) {
|
||||
const mentionPath = mention.slice(1) // Remove the leading '/'
|
||||
const mentionPath = mention.slice(1)
|
||||
return mentionPath.endsWith("/")
|
||||
? `'${mentionPath}' (see below for folder content)`
|
||||
: `'${mentionPath}' (see below for file content)`
|
||||
} else if (mention === "problems") {
|
||||
return `Workspace Problems (see below for diagnostics)`
|
||||
} else if (mention === "git-changes") {
|
||||
return `Working directory changes (see below for details)`
|
||||
} else if (/^[a-f0-9]{7,40}$/.test(mention)) {
|
||||
return `Git commit '${mention}' (see below for commit info)`
|
||||
}
|
||||
return match
|
||||
})
|
||||
@@ -99,6 +104,20 @@ export async function parseMentions(text: string, cwd: string, urlContentFetcher
|
||||
} catch (error) {
|
||||
parsedText += `\n\n<workspace_diagnostics>\nError fetching diagnostics: ${error.message}\n</workspace_diagnostics>`
|
||||
}
|
||||
} else if (mention === "git-changes") {
|
||||
try {
|
||||
const workingState = await getWorkingState(cwd)
|
||||
parsedText += `\n\n<git_working_state>\n${workingState}\n</git_working_state>`
|
||||
} catch (error) {
|
||||
parsedText += `\n\n<git_working_state>\nError fetching working state: ${error.message}\n</git_working_state>`
|
||||
}
|
||||
} else if (/^[a-f0-9]{7,40}$/.test(mention)) {
|
||||
try {
|
||||
const commitInfo = await getCommitInfo(mention, cwd)
|
||||
parsedText += `\n\n<git_commit hash="${mention}">\n${commitInfo}\n</git_commit>`
|
||||
} catch (error) {
|
||||
parsedText += `\n\n<git_commit hash="${mention}">\nError fetching commit info: ${error.message}\n</git_commit>`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +156,6 @@ async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise
|
||||
folderContent += `${linePrefix}${entry.name}\n`
|
||||
const filePath = path.join(mentionPath, entry.name)
|
||||
const absoluteFilePath = path.resolve(absPath, entry.name)
|
||||
// const relativeFilePath = path.relative(cwd, absoluteFilePath);
|
||||
fileContentPromises.push(
|
||||
(async () => {
|
||||
try {
|
||||
@@ -154,7 +172,6 @@ async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise
|
||||
)
|
||||
} else if (entry.isDirectory()) {
|
||||
folderContent += `${linePrefix}${entry.name}/\n`
|
||||
// not recursively getting folder contents
|
||||
} else {
|
||||
folderContent += `${linePrefix}${entry.name}\n`
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { getNonce } from "./getNonce"
|
||||
import { getUri } from "./getUri"
|
||||
import { playSound, setSoundEnabled, setSoundVolume } from "../../utils/sound"
|
||||
import { enhancePrompt } from "../../utils/enhance-prompt"
|
||||
import { getCommitInfo, searchCommits, getWorkingState } from "../../utils/git"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
@@ -732,6 +733,24 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
|
||||
case "searchCommits": {
|
||||
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
|
||||
if (cwd) {
|
||||
try {
|
||||
const commits = await searchCommits(message.query || "", cwd)
|
||||
await this.postMessageToWebview({
|
||||
type: "commitSearchResults",
|
||||
commits
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Error searching commits:", error)
|
||||
vscode.window.showErrorMessage("Failed to search commits")
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
null,
|
||||
|
||||
Reference in New Issue
Block a user