VSCode.ts 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111
  1. import * as fs from "fs"
  2. import * as path from "path"
  3. import { logs } from "../services/logs.js"
  4. import { KiloCodePaths } from "../utils/paths.js"
  5. import { Package } from "../constants/package.js"
  6. // Identity information for VSCode environment
  7. export interface IdentityInfo {
  8. machineId: string
  9. sessionId: string
  10. cliUserId?: string
  11. }
  12. // Basic VSCode API types and enums
  13. export type Thenable<T> = Promise<T>
  14. // VSCode EventEmitter implementation
  15. export interface Disposable {
  16. dispose(): void
  17. }
  18. type Listener<T> = (e: T) => any
  19. export class EventEmitter<T> {
  20. readonly #listeners = new Set<Listener<T>>()
  21. /**
  22. * The event listeners can subscribe to.
  23. */
  24. event = (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]): Disposable => {
  25. const fn = thisArgs ? listener.bind(thisArgs) : listener
  26. this.#listeners.add(fn)
  27. const disposable = {
  28. dispose: () => {
  29. this.#listeners.delete(fn)
  30. },
  31. }
  32. if (disposables) {
  33. disposables.push(disposable)
  34. }
  35. return disposable
  36. }
  37. /**
  38. * Notify all subscribers of the event. Failure
  39. * of one or more listener will not fail this function call.
  40. *
  41. * @param data The event object.
  42. */
  43. fire = (data: T): void => {
  44. for (const listener of this.#listeners) {
  45. try {
  46. listener(data)
  47. } catch {
  48. // ignore
  49. }
  50. }
  51. }
  52. /**
  53. * Dispose this object and free resources.
  54. */
  55. dispose = (): void => {
  56. this.#listeners.clear()
  57. }
  58. }
  59. export enum ConfigurationTarget {
  60. Global = 1,
  61. Workspace = 2,
  62. WorkspaceFolder = 3,
  63. }
  64. export enum ViewColumn {
  65. Active = -1,
  66. Beside = -2,
  67. One = 1,
  68. Two = 2,
  69. Three = 3,
  70. }
  71. export enum TextEditorRevealType {
  72. Default = 0,
  73. InCenter = 1,
  74. InCenterIfOutsideViewport = 2,
  75. AtTop = 3,
  76. }
  77. export enum StatusBarAlignment {
  78. Left = 1,
  79. Right = 2,
  80. }
  81. export enum DiagnosticSeverity {
  82. Error = 0,
  83. Warning = 1,
  84. Information = 2,
  85. Hint = 3,
  86. }
  87. // Position class
  88. export class Position {
  89. constructor(
  90. public line: number,
  91. public character: number,
  92. ) {}
  93. isEqual(other: Position): boolean {
  94. return this.line === other.line && this.character === other.character
  95. }
  96. isBefore(other: Position): boolean {
  97. if (this.line < other.line) {
  98. return true
  99. }
  100. if (this.line === other.line) {
  101. return this.character < other.character
  102. }
  103. return false
  104. }
  105. isBeforeOrEqual(other: Position): boolean {
  106. return this.isBefore(other) || this.isEqual(other)
  107. }
  108. isAfter(other: Position): boolean {
  109. return !this.isBeforeOrEqual(other)
  110. }
  111. isAfterOrEqual(other: Position): boolean {
  112. return !this.isBefore(other)
  113. }
  114. compareTo(other: Position): number {
  115. if (this.line < other.line) {
  116. return -1
  117. }
  118. if (this.line > other.line) {
  119. return 1
  120. }
  121. if (this.character < other.character) {
  122. return -1
  123. }
  124. if (this.character > other.character) {
  125. return 1
  126. }
  127. return 0
  128. }
  129. translate(lineDelta?: number, characterDelta?: number): Position
  130. translate(change: { lineDelta?: number; characterDelta?: number }): Position
  131. translate(
  132. lineDeltaOrChange?: number | { lineDelta?: number; characterDelta?: number },
  133. characterDelta?: number,
  134. ): Position {
  135. if (typeof lineDeltaOrChange === "object") {
  136. return new Position(
  137. this.line + (lineDeltaOrChange.lineDelta || 0),
  138. this.character + (lineDeltaOrChange.characterDelta || 0),
  139. )
  140. }
  141. return new Position(this.line + (lineDeltaOrChange || 0), this.character + (characterDelta || 0))
  142. }
  143. with(line?: number, character?: number): Position
  144. with(change: { line?: number; character?: number }): Position
  145. with(lineOrChange?: number | { line?: number; character?: number }, character?: number): Position {
  146. if (typeof lineOrChange === "object") {
  147. return new Position(
  148. lineOrChange.line !== undefined ? lineOrChange.line : this.line,
  149. lineOrChange.character !== undefined ? lineOrChange.character : this.character,
  150. )
  151. }
  152. return new Position(
  153. lineOrChange !== undefined ? lineOrChange : this.line,
  154. character !== undefined ? character : this.character,
  155. )
  156. }
  157. }
  158. // Range class
  159. export class Range {
  160. public start: Position
  161. public end: Position
  162. constructor(start: Position, end: Position)
  163. constructor(startLine: number, startCharacter: number, endLine: number, endCharacter: number)
  164. constructor(
  165. startOrStartLine: Position | number,
  166. endOrStartCharacter: Position | number,
  167. endLine?: number,
  168. endCharacter?: number,
  169. ) {
  170. if (typeof startOrStartLine === "number") {
  171. this.start = new Position(startOrStartLine, endOrStartCharacter as number)
  172. this.end = new Position(endLine!, endCharacter!)
  173. } else {
  174. this.start = startOrStartLine
  175. this.end = endOrStartCharacter as Position
  176. }
  177. }
  178. get isEmpty(): boolean {
  179. return this.start.isEqual(this.end)
  180. }
  181. get isSingleLine(): boolean {
  182. return this.start.line === this.end.line
  183. }
  184. contains(positionOrRange: Position | Range): boolean {
  185. if (positionOrRange instanceof Range) {
  186. return this.contains(positionOrRange.start) && this.contains(positionOrRange.end)
  187. }
  188. return positionOrRange.isAfterOrEqual(this.start) && positionOrRange.isBeforeOrEqual(this.end)
  189. }
  190. isEqual(other: Range): boolean {
  191. return this.start.isEqual(other.start) && this.end.isEqual(other.end)
  192. }
  193. intersection(other: Range): Range | undefined {
  194. const start = this.start.isAfter(other.start) ? this.start : other.start
  195. const end = this.end.isBefore(other.end) ? this.end : other.end
  196. if (start.isAfter(end)) {
  197. return undefined
  198. }
  199. return new Range(start, end)
  200. }
  201. union(other: Range): Range {
  202. const start = this.start.isBefore(other.start) ? this.start : other.start
  203. const end = this.end.isAfter(other.end) ? this.end : other.end
  204. return new Range(start, end)
  205. }
  206. with(start?: Position, end?: Position): Range
  207. with(change: { start?: Position; end?: Position }): Range
  208. with(startOrChange?: Position | { start?: Position; end?: Position }, end?: Position): Range {
  209. if (startOrChange instanceof Position) {
  210. return new Range(startOrChange, end || this.end)
  211. }
  212. if (typeof startOrChange === "object") {
  213. return new Range(startOrChange.start || this.start, startOrChange.end || this.end)
  214. }
  215. return new Range(this.start, this.end)
  216. }
  217. }
  218. // Selection class (extends Range)
  219. export class Selection extends Range {
  220. public anchor: Position
  221. public active: Position
  222. constructor(anchor: Position, active: Position)
  223. constructor(anchorLine: number, anchorCharacter: number, activeLine: number, activeCharacter: number)
  224. constructor(
  225. anchorOrAnchorLine: Position | number,
  226. activeOrAnchorCharacter: Position | number,
  227. activeLine?: number,
  228. activeCharacter?: number,
  229. ) {
  230. let anchor: Position
  231. let active: Position
  232. if (typeof anchorOrAnchorLine === "number") {
  233. anchor = new Position(anchorOrAnchorLine, activeOrAnchorCharacter as number)
  234. active = new Position(activeLine!, activeCharacter!)
  235. } else {
  236. anchor = anchorOrAnchorLine
  237. active = activeOrAnchorCharacter as Position
  238. }
  239. super(anchor, active)
  240. this.anchor = anchor
  241. this.active = active
  242. }
  243. get isReversed(): boolean {
  244. return this.anchor.isAfter(this.active)
  245. }
  246. }
  247. // Location class
  248. export class Location {
  249. constructor(
  250. public uri: Uri,
  251. public range: Range | Position,
  252. ) {}
  253. }
  254. // Diagnostic-related classes
  255. export enum DiagnosticTag {
  256. Unnecessary = 1,
  257. Deprecated = 2,
  258. }
  259. export class DiagnosticRelatedInformation {
  260. constructor(
  261. public location: Location,
  262. public message: string,
  263. ) {}
  264. }
  265. export class Diagnostic {
  266. range: Range
  267. message: string
  268. severity: DiagnosticSeverity
  269. source?: string
  270. code?: string | number | { value: string | number; target: Uri }
  271. relatedInformation?: DiagnosticRelatedInformation[]
  272. tags?: DiagnosticTag[]
  273. constructor(range: Range, message: string, severity?: DiagnosticSeverity) {
  274. this.range = range
  275. this.message = message
  276. this.severity = severity !== undefined ? severity : DiagnosticSeverity.Error
  277. }
  278. }
  279. // DiagnosticCollection interface
  280. export interface DiagnosticCollection extends Disposable {
  281. name: string
  282. set(uri: Uri, diagnostics: Diagnostic[] | undefined): void
  283. set(entries: [Uri, Diagnostic[] | undefined][]): void
  284. delete(uri: Uri): void
  285. clear(): void
  286. forEach(
  287. callback: (uri: Uri, diagnostics: Diagnostic[], collection: DiagnosticCollection) => any,
  288. thisArg?: any,
  289. ): void
  290. get(uri: Uri): Diagnostic[] | undefined
  291. has(uri: Uri): boolean
  292. }
  293. // TextEdit class
  294. export class TextEdit {
  295. range: Range
  296. newText: string
  297. constructor(range: Range, newText: string) {
  298. this.range = range
  299. this.newText = newText
  300. }
  301. static replace(range: Range, newText: string): TextEdit {
  302. return new TextEdit(range, newText)
  303. }
  304. static insert(position: Position, newText: string): TextEdit {
  305. return new TextEdit(new Range(position, position), newText)
  306. }
  307. static delete(range: Range): TextEdit {
  308. return new TextEdit(range, "")
  309. }
  310. static setEndOfLine(): TextEdit {
  311. // Simplified implementation
  312. return new TextEdit(new Range(new Position(0, 0), new Position(0, 0)), "")
  313. }
  314. }
  315. // EndOfLine enum
  316. export enum EndOfLine {
  317. LF = 1,
  318. CRLF = 2,
  319. }
  320. // WorkspaceEdit class
  321. export class WorkspaceEdit {
  322. private _edits: Map<string, TextEdit[]> = new Map()
  323. set(uri: Uri, edits: TextEdit[]): void {
  324. this._edits.set(uri.toString(), edits)
  325. }
  326. get(uri: Uri): TextEdit[] {
  327. return this._edits.get(uri.toString()) || []
  328. }
  329. has(uri: Uri): boolean {
  330. return this._edits.has(uri.toString())
  331. }
  332. delete(uri: Uri, range: Range): void {
  333. const key = uri.toString()
  334. if (!this._edits.has(key)) {
  335. this._edits.set(key, [])
  336. }
  337. this._edits.get(key)!.push(TextEdit.delete(range))
  338. }
  339. insert(uri: Uri, position: Position, newText: string): void {
  340. const key = uri.toString()
  341. if (!this._edits.has(key)) {
  342. this._edits.set(key, [])
  343. }
  344. this._edits.get(key)!.push(TextEdit.insert(position, newText))
  345. }
  346. replace(uri: Uri, range: Range, newText: string): void {
  347. const key = uri.toString()
  348. if (!this._edits.has(key)) {
  349. this._edits.set(key, [])
  350. }
  351. this._edits.get(key)!.push(TextEdit.replace(range, newText))
  352. }
  353. get size(): number {
  354. return this._edits.size
  355. }
  356. entries(): [Uri, TextEdit[]][] {
  357. return Array.from(this._edits.entries()).map(([uriString, edits]) => [Uri.parse(uriString), edits])
  358. }
  359. }
  360. // UI Kind enum
  361. export enum UIKind {
  362. Desktop = 1,
  363. Web = 2,
  364. }
  365. // Extension Mode enum
  366. export enum ExtensionMode {
  367. Production = 1,
  368. Development = 2,
  369. Test = 3,
  370. }
  371. // Code Action Kind mock
  372. export class CodeActionKind {
  373. static readonly Empty = new CodeActionKind("")
  374. static readonly QuickFix = new CodeActionKind("quickfix")
  375. static readonly Refactor = new CodeActionKind("refactor")
  376. static readonly RefactorExtract = new CodeActionKind("refactor.extract")
  377. static readonly RefactorInline = new CodeActionKind("refactor.inline")
  378. static readonly RefactorRewrite = new CodeActionKind("refactor.rewrite")
  379. static readonly Source = new CodeActionKind("source")
  380. static readonly SourceOrganizeImports = new CodeActionKind("source.organizeImports")
  381. constructor(public value: string) {}
  382. append(parts: string): CodeActionKind {
  383. return new CodeActionKind(this.value ? `${this.value}.${parts}` : parts)
  384. }
  385. intersects(other: CodeActionKind): boolean {
  386. return this.contains(other) || other.contains(this)
  387. }
  388. contains(other: CodeActionKind): boolean {
  389. return this.value === other.value || other.value.startsWith(this.value + ".")
  390. }
  391. }
  392. // Theme Color mock
  393. export class ThemeColor {
  394. constructor(public id: string) {}
  395. }
  396. // Theme Icon mock
  397. export class ThemeIcon {
  398. constructor(
  399. public id: string,
  400. public color?: ThemeColor,
  401. ) {}
  402. }
  403. // Cancellation Token mock
  404. export interface CancellationToken {
  405. isCancellationRequested: boolean
  406. onCancellationRequested: (listener: (e: any) => any) => Disposable
  407. }
  408. export class CancellationTokenSource {
  409. private _token: CancellationToken
  410. private _isCancelled = false
  411. private _onCancellationRequestedEmitter = new EventEmitter<any>()
  412. constructor() {
  413. this._token = {
  414. isCancellationRequested: false,
  415. onCancellationRequested: this._onCancellationRequestedEmitter.event,
  416. }
  417. }
  418. get token(): CancellationToken {
  419. return this._token
  420. }
  421. cancel(): void {
  422. if (!this._isCancelled) {
  423. this._isCancelled = true
  424. ;(this._token as any).isCancellationRequested = true
  425. this._onCancellationRequestedEmitter.fire(undefined)
  426. }
  427. }
  428. dispose(): void {
  429. this.cancel()
  430. this._onCancellationRequestedEmitter.dispose()
  431. }
  432. }
  433. // CodeLens mock
  434. export class CodeLens {
  435. public range: Range
  436. public command?: { command: string; title: string; arguments?: any[] } | undefined
  437. public isResolved: boolean = false
  438. constructor(range: Range, command?: { command: string; title: string; arguments?: any[] } | undefined) {
  439. this.range = range
  440. this.command = command
  441. }
  442. }
  443. // Language Model API mocks (for VSCode LM API)
  444. export class LanguageModelTextPart {
  445. constructor(public value: string) {}
  446. }
  447. export class LanguageModelToolCallPart {
  448. constructor(
  449. public callId: string,
  450. public name: string,
  451. public input: any,
  452. ) {}
  453. }
  454. export class LanguageModelToolResultPart {
  455. constructor(
  456. public callId: string,
  457. public content: any[],
  458. ) {}
  459. }
  460. // Decoration Range Behavior mock
  461. export enum DecorationRangeBehavior {
  462. OpenOpen = 0,
  463. ClosedClosed = 1,
  464. OpenClosed = 2,
  465. ClosedOpen = 3,
  466. }
  467. // Overview Ruler Lane mock
  468. export enum OverviewRulerLane {
  469. Left = 1,
  470. Center = 2,
  471. Right = 4,
  472. Full = 7,
  473. }
  474. // URI class mock
  475. export class Uri {
  476. public scheme: string
  477. public authority: string
  478. public path: string
  479. public query: string
  480. public fragment: string
  481. constructor(scheme: string, authority: string, path: string, query: string, fragment: string) {
  482. this.scheme = scheme
  483. this.authority = authority
  484. this.path = path
  485. this.query = query
  486. this.fragment = fragment
  487. }
  488. static file(path: string): Uri {
  489. return new Uri("file", "", path, "", "")
  490. }
  491. static parse(value: string): Uri {
  492. const url = new URL(value)
  493. return new Uri(url.protocol.slice(0, -1), url.hostname, url.pathname, url.search.slice(1), url.hash.slice(1))
  494. }
  495. static joinPath(base: Uri, ...pathSegments: string[]): Uri {
  496. const joinedPath = path.join(base.path, ...pathSegments)
  497. return new Uri(base.scheme, base.authority, joinedPath, base.query, base.fragment)
  498. }
  499. with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): Uri {
  500. return new Uri(
  501. change.scheme !== undefined ? change.scheme : this.scheme,
  502. change.authority !== undefined ? change.authority : this.authority,
  503. change.path !== undefined ? change.path : this.path,
  504. change.query !== undefined ? change.query : this.query,
  505. change.fragment !== undefined ? change.fragment : this.fragment,
  506. )
  507. }
  508. get fsPath(): string {
  509. return this.path
  510. }
  511. toString(): string {
  512. return `${this.scheme}://${this.authority}${this.path}${this.query ? "?" + this.query : ""}${this.fragment ? "#" + this.fragment : ""}`
  513. }
  514. }
  515. // Output Channel mock
  516. export class OutputChannel implements Disposable {
  517. private _name: string
  518. constructor(name: string) {
  519. this._name = name
  520. }
  521. get name(): string {
  522. return this._name
  523. }
  524. append(value: string): void {
  525. logs.info(`[${this._name}] ${value}`, "VSCode.OutputChannel")
  526. }
  527. appendLine(value: string): void {
  528. logs.info(`[${this._name}] ${value}`, "VSCode.OutputChannel")
  529. }
  530. clear(): void {
  531. // No-op for CLI
  532. }
  533. show(): void {
  534. // No-op for CLI
  535. }
  536. hide(): void {
  537. // No-op for CLI
  538. }
  539. dispose(): void {
  540. // No-op for CLI
  541. }
  542. }
  543. // Extension Context mock
  544. export class ExtensionContext {
  545. public subscriptions: Disposable[] = []
  546. public workspaceState: Memento
  547. public globalState: Memento & { setKeysForSync(keys: readonly string[]): void }
  548. public secrets: SecretStorage
  549. public extensionUri: Uri
  550. public extensionPath: string
  551. public environmentVariableCollection: any
  552. public storageUri: Uri | undefined
  553. public storagePath: string | undefined
  554. public globalStorageUri: Uri
  555. public globalStoragePath: string
  556. public logUri: Uri
  557. public logPath: string
  558. public extensionMode: ExtensionMode = ExtensionMode.Production
  559. constructor(extensionPath: string, workspacePath: string) {
  560. this.extensionPath = extensionPath
  561. this.extensionUri = Uri.file(extensionPath)
  562. // Setup storage paths using centralized path utility
  563. // Initialize workspace to ensure all directories exist
  564. KiloCodePaths.initializeWorkspace(workspacePath)
  565. const globalStoragePath = KiloCodePaths.getGlobalStorageDir()
  566. const workspaceStoragePath = KiloCodePaths.getWorkspaceStorageDir(workspacePath)
  567. const logPath = KiloCodePaths.getLogsDir()
  568. this.globalStoragePath = globalStoragePath
  569. this.globalStorageUri = Uri.file(globalStoragePath)
  570. this.storagePath = workspaceStoragePath
  571. this.storageUri = Uri.file(workspaceStoragePath)
  572. this.logPath = logPath
  573. this.logUri = Uri.file(logPath)
  574. // Ensure directories exist
  575. this.ensureDirectoryExists(globalStoragePath)
  576. this.ensureDirectoryExists(workspaceStoragePath)
  577. this.ensureDirectoryExists(logPath)
  578. // Initialize state storage
  579. this.workspaceState = new MemoryMemento(path.join(workspaceStoragePath, "workspace-state.json"))
  580. this.globalState = new MemoryMemento(path.join(globalStoragePath, "global-state.json")) as any
  581. this.globalState.setKeysForSync = () => {} // No-op for CLI
  582. this.secrets = new MockSecretStorage(globalStoragePath)
  583. }
  584. private ensureDirectoryExists(dirPath: string): void {
  585. try {
  586. if (!fs.existsSync(dirPath)) {
  587. fs.mkdirSync(dirPath, { recursive: true })
  588. }
  589. } catch (error) {
  590. logs.warn(`Failed to create directory ${dirPath}`, "VSCode.ExtensionContext", { error })
  591. }
  592. }
  593. }
  594. // Memento (state storage) implementation
  595. export interface Memento {
  596. get<T>(key: string): T | undefined
  597. get<T>(key: string, defaultValue: T): T
  598. update(key: string, value: any): Thenable<void>
  599. keys(): readonly string[]
  600. }
  601. class MemoryMemento implements Memento {
  602. private data: Record<string, any> = {}
  603. private filePath: string
  604. constructor(filePath: string) {
  605. this.filePath = filePath
  606. this.loadFromFile()
  607. }
  608. private loadFromFile(): void {
  609. try {
  610. if (fs.existsSync(this.filePath)) {
  611. const content = fs.readFileSync(this.filePath, "utf-8")
  612. this.data = JSON.parse(content)
  613. }
  614. } catch (error) {
  615. logs.warn(`Failed to load state from ${this.filePath}`, "VSCode.Memento", { error })
  616. this.data = {}
  617. }
  618. }
  619. private saveToFile(): void {
  620. try {
  621. // Ensure directory exists
  622. const dir = path.dirname(this.filePath)
  623. if (!fs.existsSync(dir)) {
  624. fs.mkdirSync(dir, { recursive: true })
  625. }
  626. fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2))
  627. } catch (error) {
  628. logs.warn(`Failed to save state to ${this.filePath}`, "VSCode.Memento", { error })
  629. }
  630. }
  631. get<T>(key: string, defaultValue?: T): T | undefined {
  632. return this.data[key] !== undefined ? this.data[key] : defaultValue
  633. }
  634. async update(key: string, value: any): Promise<void> {
  635. if (value === undefined) {
  636. delete this.data[key]
  637. } else {
  638. this.data[key] = value
  639. }
  640. this.saveToFile()
  641. }
  642. keys(): readonly string[] {
  643. return Object.keys(this.data)
  644. }
  645. }
  646. // Secret Storage mock
  647. export interface SecretStorage {
  648. get(key: string): Thenable<string | undefined>
  649. store(key: string, value: string): Thenable<void>
  650. delete(key: string): Thenable<void>
  651. }
  652. class MockSecretStorage implements SecretStorage {
  653. private secrets: Record<string, string> = {}
  654. private _onDidChange = new EventEmitter<any>()
  655. private filePath: string
  656. constructor(storagePath: string) {
  657. this.filePath = path.join(storagePath, "secrets.json")
  658. this.loadFromFile()
  659. }
  660. private loadFromFile(): void {
  661. try {
  662. if (fs.existsSync(this.filePath)) {
  663. const content = fs.readFileSync(this.filePath, "utf-8")
  664. this.secrets = JSON.parse(content)
  665. }
  666. } catch (error) {
  667. logs.warn(`Failed to load secrets from ${this.filePath}`, "VSCode.MockSecretStorage", { error })
  668. this.secrets = {}
  669. }
  670. }
  671. private saveToFile(): void {
  672. try {
  673. // Ensure directory exists
  674. const dir = path.dirname(this.filePath)
  675. if (!fs.existsSync(dir)) {
  676. fs.mkdirSync(dir, { recursive: true })
  677. }
  678. fs.writeFileSync(this.filePath, JSON.stringify(this.secrets, null, 2))
  679. } catch (error) {
  680. logs.warn(`Failed to save secrets to ${this.filePath}`, "VSCode.MockSecretStorage", { error })
  681. }
  682. }
  683. async get(key: string): Promise<string | undefined> {
  684. return this.secrets[key]
  685. }
  686. async store(key: string, value: string): Promise<void> {
  687. this.secrets[key] = value
  688. this.saveToFile()
  689. this._onDidChange.fire({ key })
  690. }
  691. async delete(key: string): Promise<void> {
  692. delete this.secrets[key]
  693. this.saveToFile()
  694. this._onDidChange.fire({ key })
  695. }
  696. get onDidChange() {
  697. return this._onDidChange.event
  698. }
  699. }
  700. // FileSystem API mock
  701. export enum FileType {
  702. Unknown = 0,
  703. File = 1,
  704. Directory = 2,
  705. SymbolicLink = 64,
  706. }
  707. // FileSystemError class mock
  708. export class FileSystemError extends Error {
  709. public code: string
  710. constructor(message: string, code: string = "Unknown") {
  711. super(message)
  712. this.name = "FileSystemError"
  713. this.code = code
  714. }
  715. static FileNotFound(messageOrUri?: string | Uri): FileSystemError {
  716. const message =
  717. typeof messageOrUri === "string" ? messageOrUri : `File not found: ${messageOrUri?.fsPath || "unknown"}`
  718. return new FileSystemError(message, "FileNotFound")
  719. }
  720. static FileExists(messageOrUri?: string | Uri): FileSystemError {
  721. const message =
  722. typeof messageOrUri === "string" ? messageOrUri : `File exists: ${messageOrUri?.fsPath || "unknown"}`
  723. return new FileSystemError(message, "FileExists")
  724. }
  725. static FileNotADirectory(messageOrUri?: string | Uri): FileSystemError {
  726. const message =
  727. typeof messageOrUri === "string"
  728. ? messageOrUri
  729. : `File is not a directory: ${messageOrUri?.fsPath || "unknown"}`
  730. return new FileSystemError(message, "FileNotADirectory")
  731. }
  732. static FileIsADirectory(messageOrUri?: string | Uri): FileSystemError {
  733. const message =
  734. typeof messageOrUri === "string"
  735. ? messageOrUri
  736. : `File is a directory: ${messageOrUri?.fsPath || "unknown"}`
  737. return new FileSystemError(message, "FileIsADirectory")
  738. }
  739. static NoPermissions(messageOrUri?: string | Uri): FileSystemError {
  740. const message =
  741. typeof messageOrUri === "string" ? messageOrUri : `No permissions: ${messageOrUri?.fsPath || "unknown"}`
  742. return new FileSystemError(message, "NoPermissions")
  743. }
  744. static Unavailable(messageOrUri?: string | Uri): FileSystemError {
  745. const message =
  746. typeof messageOrUri === "string" ? messageOrUri : `Unavailable: ${messageOrUri?.fsPath || "unknown"}`
  747. return new FileSystemError(message, "Unavailable")
  748. }
  749. }
  750. export interface FileStat {
  751. type: FileType
  752. ctime: number
  753. mtime: number
  754. size: number
  755. }
  756. export class FileSystemAPI {
  757. async stat(uri: Uri): Promise<FileStat> {
  758. try {
  759. const stats = fs.statSync(uri.fsPath)
  760. return {
  761. type: stats.isDirectory() ? FileType.Directory : FileType.File,
  762. ctime: stats.ctimeMs,
  763. mtime: stats.mtimeMs,
  764. size: stats.size,
  765. }
  766. } catch {
  767. // If file doesn't exist, assume it's a file for CLI purposes
  768. return {
  769. type: FileType.File,
  770. ctime: Date.now(),
  771. mtime: Date.now(),
  772. size: 0,
  773. }
  774. }
  775. }
  776. async readFile(uri: Uri): Promise<Uint8Array> {
  777. try {
  778. const content = fs.readFileSync(uri.fsPath)
  779. return new Uint8Array(content)
  780. } catch {
  781. throw new Error(`Failed to read file: ${uri.fsPath}`)
  782. }
  783. }
  784. async writeFile(uri: Uri, content: Uint8Array): Promise<void> {
  785. try {
  786. fs.writeFileSync(uri.fsPath, content)
  787. } catch {
  788. throw new Error(`Failed to write file: ${uri.fsPath}`)
  789. }
  790. }
  791. async delete(uri: Uri): Promise<void> {
  792. try {
  793. fs.unlinkSync(uri.fsPath)
  794. } catch {
  795. throw new Error(`Failed to delete file: ${uri.fsPath}`)
  796. }
  797. }
  798. async createDirectory(uri: Uri): Promise<void> {
  799. try {
  800. fs.mkdirSync(uri.fsPath, { recursive: true })
  801. } catch {
  802. throw new Error(`Failed to create directory: ${uri.fsPath}`)
  803. }
  804. }
  805. }
  806. // Workspace API mock
  807. export class WorkspaceAPI {
  808. public workspaceFolders: WorkspaceFolder[] | undefined
  809. public name: string | undefined
  810. public workspaceFile: Uri | undefined
  811. public fs: FileSystemAPI
  812. public textDocuments: any[] = []
  813. private _onDidChangeWorkspaceFolders = new EventEmitter<any>()
  814. private _onDidOpenTextDocument = new EventEmitter<any>()
  815. private _onDidChangeTextDocument = new EventEmitter<any>()
  816. private _onDidCloseTextDocument = new EventEmitter<any>()
  817. private workspacePath: string
  818. private context: ExtensionContext
  819. constructor(workspacePath: string, context: ExtensionContext) {
  820. this.workspacePath = workspacePath
  821. this.context = context
  822. this.workspaceFolders = [
  823. {
  824. uri: Uri.file(workspacePath),
  825. name: path.basename(workspacePath),
  826. index: 0,
  827. },
  828. ]
  829. this.name = path.basename(workspacePath)
  830. this.fs = new FileSystemAPI()
  831. }
  832. onDidChangeWorkspaceFolders(listener: (event: any) => void): Disposable {
  833. return this._onDidChangeWorkspaceFolders.event(listener)
  834. }
  835. onDidChangeConfiguration(listener: (event: any) => void): Disposable {
  836. // Create a mock configuration change event emitter
  837. const emitter = new EventEmitter<any>()
  838. return emitter.event(listener)
  839. }
  840. onDidChangeTextDocument(listener: (event: any) => void): Disposable {
  841. return this._onDidChangeTextDocument.event(listener)
  842. }
  843. onDidOpenTextDocument(listener: (event: any) => void): Disposable {
  844. logs.debug("Registering onDidOpenTextDocument listener", "VSCode.Workspace")
  845. return this._onDidOpenTextDocument.event(listener)
  846. }
  847. onDidCloseTextDocument(listener: (event: any) => void): Disposable {
  848. return this._onDidCloseTextDocument.event(listener)
  849. }
  850. getConfiguration(section?: string): WorkspaceConfiguration {
  851. return new MockWorkspaceConfiguration(section, this.context)
  852. }
  853. findFiles(_include: string, _exclude?: string): Thenable<Uri[]> {
  854. // Basic implementation - could be enhanced with glob patterns
  855. return Promise.resolve([])
  856. }
  857. async openTextDocument(uri: Uri): Promise<any> {
  858. logs.debug(`openTextDocument called for: ${uri.fsPath}`, "VSCode.Workspace")
  859. // Read file content
  860. let content = ""
  861. try {
  862. content = fs.readFileSync(uri.fsPath, "utf-8")
  863. logs.debug(`File content read successfully, length: ${content.length}`, "VSCode.Workspace")
  864. } catch (error) {
  865. logs.warn(`Failed to read file: ${uri.fsPath}`, "VSCode.Workspace", { error })
  866. }
  867. const lines = content.split("\n")
  868. const document = {
  869. uri,
  870. fileName: uri.fsPath,
  871. languageId: "plaintext",
  872. version: 1,
  873. isDirty: false,
  874. isClosed: false,
  875. lineCount: lines.length,
  876. getText: (range?: Range) => {
  877. if (!range) {
  878. return content
  879. }
  880. return lines.slice(range.start.line, range.end.line + 1).join("\n")
  881. },
  882. lineAt: (line: number) => {
  883. const text = lines[line] || ""
  884. return {
  885. text,
  886. range: new Range(new Position(line, 0), new Position(line, text.length)),
  887. rangeIncludingLineBreak: new Range(new Position(line, 0), new Position(line + 1, 0)),
  888. firstNonWhitespaceCharacterIndex: text.search(/\S/),
  889. isEmptyOrWhitespace: text.trim().length === 0,
  890. }
  891. },
  892. offsetAt: (position: Position) => {
  893. let offset = 0
  894. for (let i = 0; i < position.line && i < lines.length; i++) {
  895. offset += (lines[i]?.length || 0) + 1 // +1 for newline
  896. }
  897. offset += position.character
  898. return offset
  899. },
  900. positionAt: (offset: number) => {
  901. let currentOffset = 0
  902. for (let i = 0; i < lines.length; i++) {
  903. const lineLength = (lines[i]?.length || 0) + 1 // +1 for newline
  904. if (currentOffset + lineLength > offset) {
  905. return new Position(i, offset - currentOffset)
  906. }
  907. currentOffset += lineLength
  908. }
  909. return new Position(lines.length - 1, lines[lines.length - 1]?.length || 0)
  910. },
  911. save: () => Promise.resolve(true),
  912. validateRange: (range: Range) => range,
  913. validatePosition: (position: Position) => position,
  914. }
  915. // Add to textDocuments array
  916. this.textDocuments.push(document)
  917. logs.debug(`Document added to textDocuments array, total: ${this.textDocuments.length}`, "VSCode.Workspace")
  918. // Fire the event after a small delay to ensure listeners are fully registered
  919. logs.debug("Waiting before firing onDidOpenTextDocument", "VSCode.Workspace")
  920. await new Promise((resolve) => setTimeout(resolve, 10))
  921. logs.debug("Firing onDidOpenTextDocument event", "VSCode.Workspace")
  922. this._onDidOpenTextDocument.fire(document)
  923. logs.debug("onDidOpenTextDocument event fired", "VSCode.Workspace")
  924. return document
  925. }
  926. async applyEdit(edit: WorkspaceEdit): Promise<boolean> {
  927. // In CLI mode, we need to apply the edits to the actual files
  928. try {
  929. for (const [uri, edits] of edit.entries()) {
  930. const filePath = uri.fsPath
  931. let content = ""
  932. // Read existing content if file exists
  933. try {
  934. content = fs.readFileSync(filePath, "utf-8")
  935. } catch {
  936. // File doesn't exist, start with empty content
  937. }
  938. // Apply edits in reverse order to maintain correct positions
  939. const sortedEdits = edits.sort((a, b) => {
  940. const lineDiff = b.range.start.line - a.range.start.line
  941. if (lineDiff !== 0) return lineDiff
  942. return b.range.start.character - a.range.start.character
  943. })
  944. const lines = content.split("\n")
  945. for (const textEdit of sortedEdits) {
  946. const startLine = textEdit.range.start.line
  947. const startChar = textEdit.range.start.character
  948. const endLine = textEdit.range.end.line
  949. const endChar = textEdit.range.end.character
  950. if (startLine === endLine) {
  951. // Single line edit
  952. const line = lines[startLine] || ""
  953. lines[startLine] = line.substring(0, startChar) + textEdit.newText + line.substring(endChar)
  954. } else {
  955. // Multi-line edit
  956. const firstLine = lines[startLine] || ""
  957. const lastLine = lines[endLine] || ""
  958. const newContent =
  959. firstLine.substring(0, startChar) + textEdit.newText + lastLine.substring(endChar)
  960. lines.splice(startLine, endLine - startLine + 1, newContent)
  961. }
  962. }
  963. // Write back to file
  964. const newContent = lines.join("\n")
  965. fs.writeFileSync(filePath, newContent, "utf-8")
  966. }
  967. return true
  968. } catch (error) {
  969. logs.error("Failed to apply workspace edit", "VSCode.Workspace", { error })
  970. return false
  971. }
  972. }
  973. createFileSystemWatcher(
  974. _globPattern: any,
  975. _ignoreCreateEvents?: boolean,
  976. _ignoreChangeEvents?: boolean,
  977. _ignoreDeleteEvents?: boolean,
  978. ): any {
  979. return {
  980. onDidChange: () => ({ dispose: () => {} }),
  981. onDidCreate: () => ({ dispose: () => {} }),
  982. onDidDelete: () => ({ dispose: () => {} }),
  983. dispose: () => {},
  984. }
  985. }
  986. registerTextDocumentContentProvider(_scheme: string, _provider: any): Disposable {
  987. return { dispose: () => {} }
  988. }
  989. }
  990. export interface WorkspaceFolder {
  991. uri: Uri
  992. name: string
  993. index: number
  994. }
  995. export interface WorkspaceConfiguration {
  996. get<T>(section: string): T | undefined
  997. get<T>(section: string, defaultValue: T): T
  998. has(section: string): boolean
  999. inspect(section: string): any
  1000. update(section: string, value: any, configurationTarget?: ConfigurationTarget): Thenable<void>
  1001. }
  1002. export class MockWorkspaceConfiguration implements WorkspaceConfiguration {
  1003. private section: string | undefined
  1004. private globalMemento: MemoryMemento
  1005. private workspaceMemento: MemoryMemento
  1006. constructor(section?: string, context?: ExtensionContext) {
  1007. this.section = section
  1008. if (context) {
  1009. // Use the extension context's mementos
  1010. this.globalMemento = context.globalState as unknown as MemoryMemento
  1011. this.workspaceMemento = context.workspaceState as unknown as MemoryMemento
  1012. } else {
  1013. // Fallback: create our own mementos (shouldn't happen in normal usage)
  1014. const globalStoragePath = KiloCodePaths.getGlobalStorageDir()
  1015. const workspaceStoragePath = KiloCodePaths.getWorkspaceStorageDir(process.cwd())
  1016. this.ensureDirectoryExists(globalStoragePath)
  1017. this.ensureDirectoryExists(workspaceStoragePath)
  1018. this.globalMemento = new MemoryMemento(path.join(globalStoragePath, "configuration.json"))
  1019. this.workspaceMemento = new MemoryMemento(path.join(workspaceStoragePath, "configuration.json"))
  1020. }
  1021. }
  1022. private ensureDirectoryExists(dirPath: string): void {
  1023. try {
  1024. if (!fs.existsSync(dirPath)) {
  1025. fs.mkdirSync(dirPath, { recursive: true })
  1026. }
  1027. } catch (error) {
  1028. logs.warn(`Failed to create directory ${dirPath}`, "VSCode.MockWorkspaceConfiguration", { error })
  1029. }
  1030. }
  1031. get<T>(section: string, defaultValue?: T): T | undefined {
  1032. const fullSection = this.section ? `${this.section}.${section}` : section
  1033. // Check workspace configuration first (higher priority)
  1034. const workspaceValue = this.workspaceMemento.get(fullSection)
  1035. if (workspaceValue !== undefined && workspaceValue !== null) {
  1036. return workspaceValue as T
  1037. }
  1038. // Check global configuration
  1039. const globalValue = this.globalMemento.get(fullSection)
  1040. if (globalValue !== undefined && globalValue !== null) {
  1041. return globalValue as T
  1042. }
  1043. // Return default value
  1044. return defaultValue
  1045. }
  1046. has(section: string): boolean {
  1047. const fullSection = this.section ? `${this.section}.${section}` : section
  1048. return this.workspaceMemento.get(fullSection) !== undefined || this.globalMemento.get(fullSection) !== undefined
  1049. }
  1050. inspect(section: string): any {
  1051. const fullSection = this.section ? `${this.section}.${section}` : section
  1052. const workspaceValue = this.workspaceMemento.get(fullSection)
  1053. const globalValue = this.globalMemento.get(fullSection)
  1054. if (workspaceValue !== undefined || globalValue !== undefined) {
  1055. return {
  1056. key: fullSection,
  1057. defaultValue: undefined,
  1058. globalValue: globalValue,
  1059. workspaceValue: workspaceValue,
  1060. workspaceFolderValue: undefined,
  1061. }
  1062. }
  1063. return undefined
  1064. }
  1065. async update(section: string, value: any, configurationTarget?: ConfigurationTarget): Promise<void> {
  1066. const fullSection = this.section ? `${this.section}.${section}` : section
  1067. try {
  1068. // Determine which memento to use based on configuration target
  1069. const memento =
  1070. configurationTarget === ConfigurationTarget.Workspace ? this.workspaceMemento : this.globalMemento
  1071. const scope = configurationTarget === ConfigurationTarget.Workspace ? "workspace" : "global"
  1072. // Update the memento (this automatically persists to disk)
  1073. await memento.update(fullSection, value)
  1074. logs.debug(
  1075. `Configuration updated: ${fullSection} = ${JSON.stringify(value)} (${scope})`,
  1076. "VSCode.MockWorkspaceConfiguration",
  1077. )
  1078. } catch (error) {
  1079. logs.error(`Failed to update configuration: ${fullSection}`, "VSCode.MockWorkspaceConfiguration", {
  1080. error,
  1081. })
  1082. throw error
  1083. }
  1084. }
  1085. // Additional method to reload configuration from disk
  1086. public reload(): void {
  1087. // MemoryMemento automatically loads from disk, so we don't need to do anything special
  1088. logs.debug("Configuration reload requested", "VSCode.MockWorkspaceConfiguration")
  1089. }
  1090. // Method to get all configuration data (useful for debugging and generic config loading)
  1091. public getAllConfig(): Record<string, any> {
  1092. const globalKeys = this.globalMemento.keys()
  1093. const workspaceKeys = this.workspaceMemento.keys()
  1094. const allConfig: Record<string, any> = {}
  1095. // Add global settings first
  1096. for (const key of globalKeys) {
  1097. const value = this.globalMemento.get(key)
  1098. if (value !== undefined && value !== null) {
  1099. allConfig[key] = value
  1100. }
  1101. }
  1102. // Add workspace settings (these override global)
  1103. for (const key of workspaceKeys) {
  1104. const value = this.workspaceMemento.get(key)
  1105. if (value !== undefined && value !== null) {
  1106. allConfig[key] = value
  1107. }
  1108. }
  1109. return allConfig
  1110. }
  1111. }
  1112. // Text Editor Decoration Type mock
  1113. export class TextEditorDecorationType implements Disposable {
  1114. public key: string
  1115. constructor(key: string) {
  1116. this.key = key
  1117. }
  1118. dispose(): void {
  1119. // No-op for CLI
  1120. }
  1121. }
  1122. // StatusBarItem mock
  1123. export class StatusBarItem implements Disposable {
  1124. private _text: string = ""
  1125. private _tooltip: string | undefined
  1126. private _command: string | undefined
  1127. private _color: string | undefined
  1128. private _backgroundColor: string | undefined
  1129. private _isVisible: boolean = false
  1130. constructor(
  1131. public readonly alignment: StatusBarAlignment,
  1132. public readonly priority?: number,
  1133. ) {}
  1134. get text(): string {
  1135. return this._text
  1136. }
  1137. set text(value: string) {
  1138. this._text = value
  1139. }
  1140. get tooltip(): string | undefined {
  1141. return this._tooltip
  1142. }
  1143. set tooltip(value: string | undefined) {
  1144. this._tooltip = value
  1145. }
  1146. get command(): string | undefined {
  1147. return this._command
  1148. }
  1149. set command(value: string | undefined) {
  1150. this._command = value
  1151. }
  1152. get color(): string | undefined {
  1153. return this._color
  1154. }
  1155. set color(value: string | undefined) {
  1156. this._color = value
  1157. }
  1158. get backgroundColor(): string | undefined {
  1159. return this._backgroundColor
  1160. }
  1161. set backgroundColor(value: string | undefined) {
  1162. this._backgroundColor = value
  1163. }
  1164. show(): void {
  1165. this._isVisible = true
  1166. }
  1167. hide(): void {
  1168. this._isVisible = false
  1169. }
  1170. dispose(): void {
  1171. this._isVisible = false
  1172. }
  1173. }
  1174. // Tab and TabGroup interfaces for VSCode API
  1175. export interface Tab {
  1176. input: TabInputText | any
  1177. label: string
  1178. isActive: boolean
  1179. isDirty: boolean
  1180. }
  1181. export interface TabInputText {
  1182. uri: Uri
  1183. }
  1184. export interface TabGroup {
  1185. tabs: Tab[]
  1186. }
  1187. // TabGroups API mock
  1188. export class TabGroupsAPI {
  1189. private _onDidChangeTabs = new EventEmitter<void>()
  1190. private _tabGroups: TabGroup[] = []
  1191. get all(): TabGroup[] {
  1192. return this._tabGroups
  1193. }
  1194. onDidChangeTabs(listener: () => void): Disposable {
  1195. return this._onDidChangeTabs.event(listener)
  1196. }
  1197. async close(tab: Tab): Promise<boolean> {
  1198. // Find and remove the tab from all groups
  1199. for (const group of this._tabGroups) {
  1200. const index = group.tabs.indexOf(tab)
  1201. if (index !== -1) {
  1202. group.tabs.splice(index, 1)
  1203. this._onDidChangeTabs.fire()
  1204. return true
  1205. }
  1206. }
  1207. return false
  1208. }
  1209. // Internal method to simulate tab changes for CLI
  1210. _simulateTabChange(): void {
  1211. this._onDidChangeTabs.fire()
  1212. }
  1213. dispose(): void {
  1214. this._onDidChangeTabs.dispose()
  1215. }
  1216. }
  1217. // Window API mock
  1218. export class WindowAPI {
  1219. public tabGroups: TabGroupsAPI
  1220. public visibleTextEditors: any[] = []
  1221. public _onDidChangeVisibleTextEditors = new EventEmitter<any[]>()
  1222. private _workspace?: WorkspaceAPI
  1223. constructor() {
  1224. this.tabGroups = new TabGroupsAPI()
  1225. }
  1226. setWorkspace(workspace: WorkspaceAPI) {
  1227. this._workspace = workspace
  1228. }
  1229. createOutputChannel(name: string): OutputChannel {
  1230. return new OutputChannel(name)
  1231. }
  1232. createStatusBarItem(alignment?: StatusBarAlignment, priority?: number): StatusBarItem
  1233. createStatusBarItem(id?: string, alignment?: StatusBarAlignment, priority?: number): StatusBarItem
  1234. createStatusBarItem(
  1235. idOrAlignment?: string | StatusBarAlignment,
  1236. alignmentOrPriority?: StatusBarAlignment | number,
  1237. priority?: number,
  1238. ): StatusBarItem {
  1239. // Handle overloaded signatures
  1240. let actualAlignment: StatusBarAlignment
  1241. let actualPriority: number | undefined
  1242. if (typeof idOrAlignment === "string") {
  1243. // Called with id, alignment, priority
  1244. actualAlignment = (alignmentOrPriority as StatusBarAlignment) ?? StatusBarAlignment.Left
  1245. actualPriority = priority
  1246. } else {
  1247. // Called with alignment, priority
  1248. actualAlignment = (idOrAlignment as StatusBarAlignment) ?? StatusBarAlignment.Left
  1249. actualPriority = alignmentOrPriority as number | undefined
  1250. }
  1251. return new StatusBarItem(actualAlignment, actualPriority)
  1252. }
  1253. createTextEditorDecorationType(_options: any): TextEditorDecorationType {
  1254. return new TextEditorDecorationType(`decoration-${Date.now()}`)
  1255. }
  1256. createTerminal(options?: {
  1257. name?: string
  1258. shellPath?: string
  1259. shellArgs?: string[]
  1260. cwd?: string
  1261. env?: { [key: string]: string | null | undefined }
  1262. iconPath?: ThemeIcon
  1263. hideFromUser?: boolean
  1264. message?: string
  1265. strictEnv?: boolean
  1266. }): any {
  1267. // Return a mock terminal object
  1268. return {
  1269. name: options?.name || "Terminal",
  1270. processId: Promise.resolve(undefined),
  1271. creationOptions: options || {},
  1272. exitStatus: undefined,
  1273. state: { isInteractedWith: false },
  1274. sendText: (text: string, _addNewLine?: boolean) => {
  1275. logs.debug(`Terminal sendText: ${text}`, "VSCode.Terminal")
  1276. },
  1277. show: (_preserveFocus?: boolean) => {
  1278. logs.debug("Terminal show called", "VSCode.Terminal")
  1279. },
  1280. hide: () => {
  1281. logs.debug("Terminal hide called", "VSCode.Terminal")
  1282. },
  1283. dispose: () => {
  1284. logs.debug("Terminal disposed", "VSCode.Terminal")
  1285. },
  1286. }
  1287. }
  1288. showInformationMessage(message: string, ..._items: string[]): Thenable<string | undefined> {
  1289. logs.info(message, "VSCode.Window")
  1290. return Promise.resolve(undefined)
  1291. }
  1292. showWarningMessage(message: string, ..._items: string[]): Thenable<string | undefined> {
  1293. logs.warn(message, "VSCode.Window")
  1294. return Promise.resolve(undefined)
  1295. }
  1296. showErrorMessage(message: string, ..._items: string[]): Thenable<string | undefined> {
  1297. logs.error(message, "VSCode.Window")
  1298. return Promise.resolve(undefined)
  1299. }
  1300. showQuickPick(items: string[], _options?: any): Thenable<string | undefined> {
  1301. // Return first item for CLI
  1302. return Promise.resolve(items[0])
  1303. }
  1304. showInputBox(_options?: any): Thenable<string | undefined> {
  1305. // Return empty string for CLI
  1306. return Promise.resolve("")
  1307. }
  1308. showOpenDialog(_options?: any): Thenable<Uri[] | undefined> {
  1309. // Return empty array for CLI
  1310. return Promise.resolve([])
  1311. }
  1312. async showTextDocument(
  1313. documentOrUri: any | Uri,
  1314. columnOrOptions?: ViewColumn | any,
  1315. _preserveFocus?: boolean,
  1316. ): Promise<any> {
  1317. // Mock implementation for CLI
  1318. // In a real VSCode environment, this would open the document in an editor
  1319. const uri = documentOrUri instanceof Uri ? documentOrUri : documentOrUri.uri
  1320. logs.debug(`showTextDocument called for: ${uri?.toString() || "unknown"}`, "VSCode.Window")
  1321. // Create a placeholder editor first so it's in visibleTextEditors when onDidOpenTextDocument fires
  1322. const placeholderEditor = {
  1323. document: { uri },
  1324. selection: new Selection(new Position(0, 0), new Position(0, 0)),
  1325. selections: [new Selection(new Position(0, 0), new Position(0, 0))],
  1326. visibleRanges: [new Range(new Position(0, 0), new Position(0, 0))],
  1327. options: {},
  1328. viewColumn: typeof columnOrOptions === "number" ? columnOrOptions : ViewColumn.One,
  1329. edit: () => Promise.resolve(true),
  1330. insertSnippet: () => Promise.resolve(true),
  1331. setDecorations: () => {},
  1332. revealRange: () => {},
  1333. show: () => {},
  1334. hide: () => {},
  1335. }
  1336. // Add placeholder to visible editors BEFORE opening document
  1337. this.visibleTextEditors.push(placeholderEditor)
  1338. logs.debug(
  1339. `Placeholder editor added to visibleTextEditors, total: ${this.visibleTextEditors.length}`,
  1340. "VSCode.Window",
  1341. )
  1342. // If we have a URI, open the document (this will fire onDidOpenTextDocument)
  1343. let document = documentOrUri
  1344. if (documentOrUri instanceof Uri && this._workspace) {
  1345. logs.debug("Opening document via workspace.openTextDocument", "VSCode.Window")
  1346. document = await this._workspace.openTextDocument(uri)
  1347. logs.debug("Document opened successfully", "VSCode.Window")
  1348. // Update the placeholder editor with the real document
  1349. placeholderEditor.document = document
  1350. }
  1351. // Fire events immediately using setImmediate
  1352. setImmediate(() => {
  1353. logs.debug("Firing onDidChangeVisibleTextEditors event", "VSCode.Window")
  1354. this._onDidChangeVisibleTextEditors.fire(this.visibleTextEditors)
  1355. logs.debug("onDidChangeVisibleTextEditors event fired", "VSCode.Window")
  1356. })
  1357. logs.debug("Returning editor from showTextDocument", "VSCode.Window")
  1358. return placeholderEditor
  1359. }
  1360. registerWebviewViewProvider(viewId: string, provider: any, _options?: any): Disposable {
  1361. // Store the provider for later use by ExtensionHost
  1362. if ((global as any).__extensionHost) {
  1363. ;(global as any).__extensionHost.registerWebviewProvider(viewId, provider)
  1364. // Set up webview mock that captures messages from the extension
  1365. const mockWebview = {
  1366. postMessage: (message: any) => {
  1367. // Forward extension messages to ExtensionHost for CLI consumption
  1368. if ((global as any).__extensionHost) {
  1369. ;(global as any).__extensionHost.emit("extensionWebviewMessage", message)
  1370. }
  1371. },
  1372. onDidReceiveMessage: (listener: (message: any) => void) => {
  1373. // This is how the extension listens for messages from the webview
  1374. // We need to connect this to our message bridge
  1375. if ((global as any).__extensionHost) {
  1376. ;(global as any).__extensionHost.on("webviewMessage", listener)
  1377. }
  1378. return { dispose: () => {} }
  1379. },
  1380. asWebviewUri: (uri: Uri) => {
  1381. // Convert file URIs to webview-compatible URIs
  1382. // For CLI, we can just return a mock webview URI
  1383. return Uri.parse(`vscode-webview://webview/${uri.path}`)
  1384. },
  1385. html: "",
  1386. options: {},
  1387. cspSource: "vscode-webview:",
  1388. }
  1389. // Provide the mock webview to the provider
  1390. if (provider.resolveWebviewView) {
  1391. const mockWebviewView = {
  1392. webview: mockWebview,
  1393. viewType: viewId,
  1394. title: viewId,
  1395. description: undefined,
  1396. badge: undefined,
  1397. show: () => {},
  1398. onDidChangeVisibility: () => ({ dispose: () => {} }),
  1399. onDidDispose: () => ({ dispose: () => {} }),
  1400. visible: true,
  1401. }
  1402. // Call the provider's resolveWebviewView method
  1403. setTimeout(() => {
  1404. try {
  1405. provider.resolveWebviewView(mockWebviewView, { preserveFocus: false }, {})
  1406. } catch (error) {
  1407. logs.error("Error resolving webview view", "VSCode.Window", { error })
  1408. }
  1409. }, 100)
  1410. }
  1411. }
  1412. return {
  1413. dispose: () => {
  1414. if ((global as any).__extensionHost) {
  1415. ;(global as any).__extensionHost.unregisterWebviewProvider(viewId)
  1416. }
  1417. },
  1418. }
  1419. }
  1420. registerUriHandler(_handler: any): Disposable {
  1421. // Store the URI handler for later use
  1422. return {
  1423. dispose: () => {},
  1424. }
  1425. }
  1426. onDidChangeTextEditorSelection(listener: (event: any) => void): Disposable {
  1427. const emitter = new EventEmitter<any>()
  1428. return emitter.event(listener)
  1429. }
  1430. onDidChangeActiveTextEditor(listener: (event: any) => void): Disposable {
  1431. const emitter = new EventEmitter<any>()
  1432. return emitter.event(listener)
  1433. }
  1434. onDidChangeVisibleTextEditors(listener: (editors: any[]) => void): Disposable {
  1435. return this._onDidChangeVisibleTextEditors.event(listener)
  1436. }
  1437. // Terminal event handlers
  1438. onDidCloseTerminal(_listener: (terminal: any) => void): Disposable {
  1439. return { dispose: () => {} }
  1440. }
  1441. onDidOpenTerminal(_listener: (terminal: any) => void): Disposable {
  1442. return { dispose: () => {} }
  1443. }
  1444. onDidChangeActiveTerminal(_listener: (terminal: any) => void): Disposable {
  1445. return { dispose: () => {} }
  1446. }
  1447. onDidChangeTerminalDimensions(_listener: (event: any) => void): Disposable {
  1448. return { dispose: () => {} }
  1449. }
  1450. onDidWriteTerminalData(_listener: (event: any) => void): Disposable {
  1451. return { dispose: () => {} }
  1452. }
  1453. get activeTerminal(): any {
  1454. return undefined
  1455. }
  1456. get terminals(): any[] {
  1457. return []
  1458. }
  1459. }
  1460. export interface WorkspaceFolder {
  1461. uri: Uri
  1462. name: string
  1463. index: number
  1464. }
  1465. export interface WorkspaceConfiguration {
  1466. get<T>(section: string): T | undefined
  1467. get<T>(section: string, defaultValue: T): T
  1468. has(section: string): boolean
  1469. inspect(_section: string): any
  1470. update(section: string, value: any, configurationTarget?: ConfigurationTarget): Thenable<void>
  1471. }
  1472. // Commands API mock
  1473. // Commands API mock
  1474. export class CommandsAPI {
  1475. private commands: Map<string, (...args: any[]) => any> = new Map()
  1476. registerCommand(command: string, callback: (...args: any[]) => any): Disposable {
  1477. this.commands.set(command, callback)
  1478. return {
  1479. dispose: () => {
  1480. this.commands.delete(command)
  1481. },
  1482. }
  1483. }
  1484. executeCommand<T = unknown>(command: string, ...rest: any[]): Thenable<T> {
  1485. const handler = this.commands.get(command)
  1486. if (handler) {
  1487. try {
  1488. const result = handler(...rest)
  1489. return Promise.resolve(result)
  1490. } catch (error) {
  1491. return Promise.reject(error)
  1492. }
  1493. }
  1494. // Handle built-in commands
  1495. switch (command) {
  1496. case "workbench.action.files.saveFiles":
  1497. case "workbench.action.closeWindow":
  1498. case "workbench.action.reloadWindow":
  1499. return Promise.resolve(undefined as T)
  1500. case "vscode.diff":
  1501. // Simulate opening a diff view for the CLI
  1502. // The extension's DiffViewProvider expects this to create a diff editor
  1503. return this.handleDiffCommand(rest[0], rest[1], rest[2], rest[3]) as Thenable<T>
  1504. default:
  1505. logs.warn(`Unknown command: ${command}`, "VSCode.Commands")
  1506. return Promise.resolve(undefined as T)
  1507. }
  1508. }
  1509. private async handleDiffCommand(originalUri: Uri, modifiedUri: Uri, title?: string, _options?: any): Promise<void> {
  1510. // The DiffViewProvider is waiting for the modified document to appear in visibleTextEditors
  1511. // We need to simulate this by opening the document and adding it to visible editors
  1512. logs.info(`[DIFF] Handling vscode.diff command`, "VSCode.Commands", {
  1513. originalUri: originalUri?.toString(),
  1514. modifiedUri: modifiedUri?.toString(),
  1515. title,
  1516. })
  1517. if (!modifiedUri) {
  1518. logs.warn("[DIFF] vscode.diff called without modified URI", "VSCode.Commands")
  1519. return
  1520. }
  1521. // Get the workspace API to open the document
  1522. const workspace = (global as any).vscode?.workspace
  1523. const window = (global as any).vscode?.window
  1524. if (!workspace || !window) {
  1525. logs.warn("[DIFF] VSCode APIs not available for diff command", "VSCode.Commands")
  1526. return
  1527. }
  1528. logs.info(
  1529. `[DIFF] Current visibleTextEditors count: ${window.visibleTextEditors?.length || 0}`,
  1530. "VSCode.Commands",
  1531. )
  1532. try {
  1533. // The document should already be open from the showTextDocument call
  1534. // Find it in the existing textDocuments
  1535. logs.info(`[DIFF] Looking for already-opened document: ${modifiedUri.fsPath}`, "VSCode.Commands")
  1536. let document = workspace.textDocuments.find((doc: any) => doc.uri.fsPath === modifiedUri.fsPath)
  1537. if (!document) {
  1538. // If not found, open it now
  1539. logs.info(`[DIFF] Document not found, opening: ${modifiedUri.fsPath}`, "VSCode.Commands")
  1540. document = await workspace.openTextDocument(modifiedUri)
  1541. logs.info(`[DIFF] Document opened successfully, lineCount: ${document.lineCount}`, "VSCode.Commands")
  1542. } else {
  1543. logs.info(`[DIFF] Found existing document, lineCount: ${document.lineCount}`, "VSCode.Commands")
  1544. }
  1545. // Create a mock editor for the diff view
  1546. const mockEditor = {
  1547. document,
  1548. selection: new Selection(new Position(0, 0), new Position(0, 0)),
  1549. selections: [new Selection(new Position(0, 0), new Position(0, 0))],
  1550. visibleRanges: [new Range(new Position(0, 0), new Position(0, 0))],
  1551. options: {},
  1552. viewColumn: ViewColumn.One,
  1553. edit: async (callback: (editBuilder: any) => void) => {
  1554. // Create a mock edit builder
  1555. const editBuilder = {
  1556. replace: (_range: Range, _text: string) => {
  1557. // In CLI mode, we don't actually edit here
  1558. // The DiffViewProvider will handle the actual edits
  1559. logs.debug("Mock edit builder replace called", "VSCode.Commands")
  1560. },
  1561. insert: (_position: Position, _text: string) => {
  1562. logs.debug("Mock edit builder insert called", "VSCode.Commands")
  1563. },
  1564. delete: (_range: Range) => {
  1565. logs.debug("Mock edit builder delete called", "VSCode.Commands")
  1566. },
  1567. }
  1568. callback(editBuilder)
  1569. return true
  1570. },
  1571. insertSnippet: () => Promise.resolve(true),
  1572. setDecorations: () => {},
  1573. revealRange: () => {},
  1574. show: () => {},
  1575. hide: () => {},
  1576. }
  1577. // Add the editor to visible editors
  1578. if (!window.visibleTextEditors) {
  1579. window.visibleTextEditors = []
  1580. }
  1581. // Check if this editor is already in visibleTextEditors (from showTextDocument)
  1582. const existingEditor = window.visibleTextEditors.find(
  1583. (e: any) => e.document.uri.fsPath === modifiedUri.fsPath,
  1584. )
  1585. if (existingEditor) {
  1586. logs.info(`[DIFF] Editor already in visibleTextEditors, updating it`, "VSCode.Commands")
  1587. // Update the existing editor with the mock editor properties
  1588. Object.assign(existingEditor, mockEditor)
  1589. } else {
  1590. logs.info(`[DIFF] Adding new mock editor to visibleTextEditors`, "VSCode.Commands")
  1591. window.visibleTextEditors.push(mockEditor)
  1592. }
  1593. logs.info(`[DIFF] visibleTextEditors count: ${window.visibleTextEditors.length}`, "VSCode.Commands")
  1594. // The onDidChangeVisibleTextEditors event was already fired by showTextDocument
  1595. // We don't need to fire it again here
  1596. logs.info(
  1597. `[DIFF] Diff view simulation complete (events already fired by showTextDocument)`,
  1598. "VSCode.Commands",
  1599. )
  1600. } catch (error) {
  1601. logs.error("[DIFF] Error simulating diff view", "VSCode.Commands", { error })
  1602. }
  1603. }
  1604. }
  1605. // Main VSCode API mock
  1606. export function createVSCodeAPIMock(extensionRootPath: string, workspacePath: string, identity?: IdentityInfo) {
  1607. const context = new ExtensionContext(extensionRootPath, workspacePath)
  1608. const workspace = new WorkspaceAPI(workspacePath, context)
  1609. const window = new WindowAPI()
  1610. const commands = new CommandsAPI()
  1611. // Link window and workspace for cross-API calls
  1612. window.setWorkspace(workspace)
  1613. // Environment mock with identity values
  1614. const env = {
  1615. appName: `wrapper|cli|cli|${Package.version}`,
  1616. appRoot: import.meta.dirname,
  1617. language: "en",
  1618. machineId: identity?.machineId || "cli-machine-id",
  1619. sessionId: identity?.sessionId || "cli-session-id",
  1620. remoteName: undefined,
  1621. shell: process.env.SHELL || "/bin/bash",
  1622. uriScheme: "vscode",
  1623. uiKind: 1, // Desktop
  1624. openExternal: async (uri: Uri): Promise<boolean> => {
  1625. logs.info(`Would open external URL: ${uri.toString()}`, "VSCode.Env")
  1626. return true
  1627. },
  1628. clipboard: {
  1629. readText: async (): Promise<string> => {
  1630. logs.debug("Clipboard read requested", "VSCode.Clipboard")
  1631. return ""
  1632. },
  1633. writeText: async (text: string): Promise<void> => {
  1634. logs.debug(
  1635. `Clipboard write: ${text.substring(0, 100)}${text.length > 100 ? "..." : ""}`,
  1636. "VSCode.Clipboard",
  1637. )
  1638. },
  1639. },
  1640. }
  1641. return {
  1642. version: "1.84.0",
  1643. Uri,
  1644. EventEmitter,
  1645. ConfigurationTarget,
  1646. ViewColumn,
  1647. TextEditorRevealType,
  1648. StatusBarAlignment,
  1649. DiagnosticSeverity,
  1650. DiagnosticTag,
  1651. Position,
  1652. Range,
  1653. Selection,
  1654. Location,
  1655. Diagnostic,
  1656. DiagnosticRelatedInformation,
  1657. TextEdit,
  1658. WorkspaceEdit,
  1659. EndOfLine,
  1660. UIKind,
  1661. ExtensionMode,
  1662. CodeActionKind,
  1663. ThemeColor,
  1664. ThemeIcon,
  1665. DecorationRangeBehavior,
  1666. OverviewRulerLane,
  1667. StatusBarItem,
  1668. CancellationToken: class CancellationTokenClass implements CancellationToken {
  1669. isCancellationRequested = false
  1670. onCancellationRequested = (_listener: (e: any) => any) => ({ dispose: () => {} })
  1671. },
  1672. CancellationTokenSource,
  1673. CodeLens,
  1674. LanguageModelTextPart,
  1675. LanguageModelToolCallPart,
  1676. LanguageModelToolResultPart,
  1677. ExtensionContext,
  1678. FileType,
  1679. FileSystemError,
  1680. Disposable: class DisposableClass implements Disposable {
  1681. dispose(): void {
  1682. // No-op for CLI
  1683. }
  1684. static from(...disposables: Disposable[]): Disposable {
  1685. return {
  1686. dispose: () => {
  1687. disposables.forEach((d) => d.dispose())
  1688. },
  1689. }
  1690. }
  1691. },
  1692. TabInputText: class TabInputText {
  1693. constructor(public uri: Uri) {}
  1694. },
  1695. TabInputTextDiff: class TabInputTextDiff {
  1696. constructor(
  1697. public original: Uri,
  1698. public modified: Uri,
  1699. ) {}
  1700. },
  1701. workspace,
  1702. window,
  1703. commands,
  1704. env,
  1705. context,
  1706. // Add more APIs as needed
  1707. languages: {
  1708. registerCodeActionsProvider: () => ({ dispose: () => {} }),
  1709. registerCodeLensProvider: () => ({ dispose: () => {} }),
  1710. registerCompletionItemProvider: () => ({ dispose: () => {} }),
  1711. registerHoverProvider: () => ({ dispose: () => {} }),
  1712. registerDefinitionProvider: () => ({ dispose: () => {} }),
  1713. registerReferenceProvider: () => ({ dispose: () => {} }),
  1714. registerDocumentSymbolProvider: () => ({ dispose: () => {} }),
  1715. registerWorkspaceSymbolProvider: () => ({ dispose: () => {} }),
  1716. registerRenameProvider: () => ({ dispose: () => {} }),
  1717. registerDocumentFormattingEditProvider: () => ({ dispose: () => {} }),
  1718. registerDocumentRangeFormattingEditProvider: () => ({ dispose: () => {} }),
  1719. registerSignatureHelpProvider: () => ({ dispose: () => {} }),
  1720. getDiagnostics: (uri?: Uri): [Uri, Diagnostic[]][] | Diagnostic[] => {
  1721. // In CLI mode, we don't have real diagnostics
  1722. // Return empty array or empty diagnostics for the specific URI
  1723. if (uri) {
  1724. return []
  1725. }
  1726. return []
  1727. },
  1728. createDiagnosticCollection: (name?: string): DiagnosticCollection => {
  1729. const diagnostics = new Map<string, Diagnostic[]>()
  1730. const collection: DiagnosticCollection = {
  1731. name: name || "default",
  1732. set: (
  1733. uriOrEntries: Uri | [Uri, Diagnostic[] | undefined][],
  1734. diagnosticsOrUndefined?: Diagnostic[] | undefined,
  1735. ) => {
  1736. if (Array.isArray(uriOrEntries)) {
  1737. // Handle array of entries
  1738. for (const [uri, diags] of uriOrEntries) {
  1739. if (diags === undefined) {
  1740. diagnostics.delete(uri.toString())
  1741. } else {
  1742. diagnostics.set(uri.toString(), diags)
  1743. }
  1744. }
  1745. } else {
  1746. // Handle single URI
  1747. if (diagnosticsOrUndefined === undefined) {
  1748. diagnostics.delete(uriOrEntries.toString())
  1749. } else {
  1750. diagnostics.set(uriOrEntries.toString(), diagnosticsOrUndefined)
  1751. }
  1752. }
  1753. },
  1754. delete: (uri: Uri) => {
  1755. diagnostics.delete(uri.toString())
  1756. },
  1757. clear: () => {
  1758. diagnostics.clear()
  1759. },
  1760. forEach: (
  1761. callback: (uri: Uri, diagnostics: Diagnostic[], collection: DiagnosticCollection) => any,
  1762. thisArg?: any,
  1763. ) => {
  1764. diagnostics.forEach((diags, uriString) => {
  1765. callback.call(thisArg, Uri.parse(uriString), diags, collection)
  1766. })
  1767. },
  1768. get: (uri: Uri) => {
  1769. return diagnostics.get(uri.toString())
  1770. },
  1771. has: (uri: Uri) => {
  1772. return diagnostics.has(uri.toString())
  1773. },
  1774. dispose: () => {
  1775. diagnostics.clear()
  1776. },
  1777. }
  1778. return collection
  1779. },
  1780. },
  1781. debug: {
  1782. onDidStartDebugSession: () => ({ dispose: () => {} }),
  1783. onDidTerminateDebugSession: () => ({ dispose: () => {} }),
  1784. },
  1785. tasks: {
  1786. onDidStartTask: () => ({ dispose: () => {} }),
  1787. onDidEndTask: () => ({ dispose: () => {} }),
  1788. },
  1789. extensions: {
  1790. all: [],
  1791. getExtension: (extensionId: string) => {
  1792. // Mock the extension object with extensionUri for theme loading
  1793. if (extensionId === "kilocode.kilo-code") {
  1794. return {
  1795. id: extensionId,
  1796. extensionUri: context.extensionUri,
  1797. extensionPath: context.extensionPath,
  1798. isActive: true,
  1799. packageJSON: {},
  1800. exports: undefined,
  1801. activate: () => Promise.resolve(),
  1802. }
  1803. }
  1804. return undefined
  1805. },
  1806. onDidChange: () => ({ dispose: () => {} }),
  1807. },
  1808. // Add file system watcher
  1809. FileSystemWatcher: class {
  1810. onDidChange = () => ({ dispose: () => {} })
  1811. onDidCreate = () => ({ dispose: () => {} })
  1812. onDidDelete = () => ({ dispose: () => {} })
  1813. dispose = () => {}
  1814. },
  1815. // Add relative pattern
  1816. RelativePattern: class {
  1817. constructor(
  1818. public base: any,
  1819. public pattern: string,
  1820. ) {}
  1821. },
  1822. // Add progress location
  1823. ProgressLocation: {
  1824. SourceControl: 1,
  1825. Window: 10,
  1826. Notification: 15,
  1827. },
  1828. // Add URI handler
  1829. UriHandler: class {
  1830. handleUri = () => {}
  1831. },
  1832. }
  1833. }