@theia/core 1.66.0-next.44 → 1.66.0-next.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/lib/browser/authentication-service.d.ts +14 -10
  2. package/lib/browser/authentication-service.d.ts.map +1 -1
  3. package/lib/browser/authentication-service.js +23 -11
  4. package/lib/browser/authentication-service.js.map +1 -1
  5. package/lib/browser/catalog.json +17 -9
  6. package/lib/browser/credentials-service.d.ts +2 -0
  7. package/lib/browser/credentials-service.d.ts.map +1 -1
  8. package/lib/browser/credentials-service.js +6 -0
  9. package/lib/browser/credentials-service.js.map +1 -1
  10. package/lib/browser/frontend-application-module.d.ts.map +1 -1
  11. package/lib/browser/frontend-application-module.js +3 -0
  12. package/lib/browser/frontend-application-module.js.map +1 -1
  13. package/lib/browser/symbol-icon-color-contribution.d.ts +6 -0
  14. package/lib/browser/symbol-icon-color-contribution.d.ts.map +1 -0
  15. package/lib/browser/symbol-icon-color-contribution.js +212 -0
  16. package/lib/browser/symbol-icon-color-contribution.js.map +1 -0
  17. package/lib/browser/tree/tree-widget.d.ts +6 -0
  18. package/lib/browser/tree/tree-widget.d.ts.map +1 -1
  19. package/lib/browser/tree/tree-widget.js +23 -6
  20. package/lib/browser/tree/tree-widget.js.map +1 -1
  21. package/lib/browser-only/frontend-only-application-module.d.ts.map +1 -1
  22. package/lib/browser-only/frontend-only-application-module.js +2 -1
  23. package/lib/browser-only/frontend-only-application-module.js.map +1 -1
  24. package/lib/common/color.d.ts +17 -1
  25. package/lib/common/color.d.ts.map +1 -1
  26. package/lib/common/color.js +62 -1
  27. package/lib/common/color.js.map +1 -1
  28. package/lib/common/content-replacer-v2-impl.d.ts +70 -0
  29. package/lib/common/content-replacer-v2-impl.d.ts.map +1 -0
  30. package/lib/common/content-replacer-v2-impl.js +407 -0
  31. package/lib/common/content-replacer-v2-impl.js.map +1 -0
  32. package/lib/common/content-replacer-v2-impl.spec.d.ts +2 -0
  33. package/lib/common/content-replacer-v2-impl.spec.d.ts.map +1 -0
  34. package/lib/common/content-replacer-v2-impl.spec.js +319 -0
  35. package/lib/common/content-replacer-v2-impl.spec.js.map +1 -0
  36. package/lib/common/content-replacer.d.ts +13 -1
  37. package/lib/common/content-replacer.d.ts.map +1 -1
  38. package/lib/common/content-replacer.js +3 -3
  39. package/lib/common/content-replacer.js.map +1 -1
  40. package/lib/common/content-replacer.spec.js +2 -2
  41. package/lib/common/content-replacer.spec.js.map +1 -1
  42. package/lib/common/key-store.d.ts +1 -0
  43. package/lib/common/key-store.d.ts.map +1 -1
  44. package/lib/electron-browser/menu/electron-menu-contribution.js +1 -1
  45. package/lib/electron-browser/menu/electron-menu-contribution.js.map +1 -1
  46. package/lib/node/key-store-server.d.ts +1 -0
  47. package/lib/node/key-store-server.d.ts.map +1 -1
  48. package/lib/node/key-store-server.js +4 -0
  49. package/lib/node/key-store-server.js.map +1 -1
  50. package/lib/node/key-store-server.spec.d.ts +2 -0
  51. package/lib/node/key-store-server.spec.d.ts.map +1 -0
  52. package/lib/node/key-store-server.spec.js +226 -0
  53. package/lib/node/key-store-server.spec.js.map +1 -0
  54. package/package.json +4 -4
  55. package/src/browser/authentication-service.ts +57 -18
  56. package/src/browser/credentials-service.ts +9 -0
  57. package/src/browser/frontend-application-module.ts +3 -0
  58. package/src/browser/style/index.css +1 -0
  59. package/src/browser/style/symbol-icon.css +258 -0
  60. package/src/browser/symbol-icon-color-contribution.ts +242 -0
  61. package/src/browser/tree/tree-widget.tsx +25 -6
  62. package/src/browser-only/frontend-only-application-module.ts +2 -1
  63. package/src/common/color.ts +51 -1
  64. package/src/common/content-replacer-v2-impl.spec.ts +344 -0
  65. package/src/common/content-replacer-v2-impl.ts +471 -0
  66. package/src/common/content-replacer.spec.ts +4 -4
  67. package/src/common/content-replacer.ts +11 -1
  68. package/src/common/key-store.ts +1 -0
  69. package/src/electron-browser/menu/electron-menu-contribution.ts +1 -1
  70. package/src/node/key-store-server.spec.ts +262 -0
  71. package/src/node/key-store-server.ts +5 -0
@@ -0,0 +1,471 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 EclipseSource GmbH.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { ContentReplacer, Replacement } from './content-replacer';
18
+
19
+ /**
20
+ * Represents a match with its position and the actual matched content
21
+ */
22
+ interface MatchInfo {
23
+ startIndex: number;
24
+ endIndex: number;
25
+ matchedContent: string;
26
+ }
27
+
28
+ /**
29
+ * Result of finding matches
30
+ */
31
+ interface MatchResult {
32
+ matches: MatchInfo[];
33
+ strategy: string;
34
+ }
35
+
36
+ export class ContentReplacerV2Impl implements ContentReplacer {
37
+ /**
38
+ * Applies a list of replacements to the original content using a multi-step matching strategy with improved flexibility.
39
+ * @param originalContent The original file content.
40
+ * @param replacements Array of Replacement objects.
41
+ * @returns An object containing the updated content and any error messages.
42
+ */
43
+ applyReplacements(originalContent: string, replacements: Replacement[]): { updatedContent: string, errors: string[] } {
44
+ let updatedContent = originalContent;
45
+ const errorMessages: string[] = [];
46
+
47
+ // Guard against conflicting replacements: if the same oldContent appears with different newContent, return with an error.
48
+ const conflictMap = new Map<string, string>();
49
+ for (const replacement of replacements) {
50
+ if (conflictMap.has(replacement.oldContent) && conflictMap.get(replacement.oldContent) !== replacement.newContent) {
51
+ return { updatedContent: originalContent, errors: [`Conflicting replacement values for: "${replacement.oldContent}"`] };
52
+ }
53
+ conflictMap.set(replacement.oldContent, replacement.newContent);
54
+ }
55
+
56
+ replacements.forEach(({ oldContent, newContent, multiple }) => {
57
+ // If the old content is empty, prepend the new content to the beginning of the file (e.g. in new file)
58
+ if (oldContent === '') {
59
+ updatedContent = newContent + updatedContent;
60
+ return;
61
+ }
62
+
63
+ // Try multiple matching strategies
64
+ const matchResult = this.findMatches(updatedContent, oldContent);
65
+
66
+ if (matchResult.matches.length === 0) {
67
+ const truncatedOld = this.truncateForError(oldContent);
68
+ errorMessages.push(`Content to replace not found: "${truncatedOld}"`);
69
+ } else if (matchResult.matches.length > 1) {
70
+ if (multiple) {
71
+ updatedContent = this.replaceAllMatches(updatedContent, matchResult.matches, newContent);
72
+ } else {
73
+ const truncatedOld = this.truncateForError(oldContent);
74
+ errorMessages.push(`Multiple occurrences found for: "${truncatedOld}". Set 'multiple' to true if multiple occurrences of the oldContent are expected to be\
75
+ replaced at once.`);
76
+ }
77
+ } else {
78
+ updatedContent = this.replaceSingleMatch(updatedContent, matchResult.matches[0], newContent);
79
+ }
80
+ });
81
+
82
+ return { updatedContent, errors: errorMessages };
83
+ }
84
+
85
+ /**
86
+ * Normalizes line endings to LF
87
+ */
88
+ private normalizeLineEndings(text: string): string {
89
+ return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
90
+ }
91
+
92
+ /**
93
+ * Finds matches using multiple strategies with increasing flexibility
94
+ */
95
+ private findMatches(content: string, search: string): MatchResult {
96
+ // Strategy 1: Exact match
97
+ const exactMatches = this.findExactMatches(content, search);
98
+ if (exactMatches.length > 0) {
99
+ return { matches: exactMatches, strategy: 'exact' };
100
+ }
101
+
102
+ // Strategy 2: Match with normalized line endings
103
+ const normalizedMatches = this.findNormalizedLineEndingMatches(content, search);
104
+ if (normalizedMatches.length > 0) {
105
+ return { matches: normalizedMatches, strategy: 'normalized-line-endings' };
106
+ }
107
+
108
+ // Strategy 3: Single line trimmed match (for backward compatibility)
109
+ const lineTrimmedMatches = this.findLineTrimmedMatches(content, search);
110
+ if (lineTrimmedMatches.length > 0) {
111
+ return { matches: lineTrimmedMatches, strategy: 'line-trimmed' };
112
+ }
113
+
114
+ // Strategy 4: Multi-line fuzzy match with trimmed comparison
115
+ const fuzzyMatches = this.findFuzzyMultilineMatches(content, search);
116
+ if (fuzzyMatches.length > 0) {
117
+ return { matches: fuzzyMatches, strategy: 'fuzzy-multiline' };
118
+ }
119
+
120
+ return { matches: [], strategy: 'none' };
121
+ }
122
+
123
+ /**
124
+ * Finds all exact matches of a substring within a string.
125
+ */
126
+ private findExactMatches(content: string, search: string): MatchInfo[] {
127
+ const matches: MatchInfo[] = [];
128
+ let startIndex = 0;
129
+
130
+ while ((startIndex = content.indexOf(search, startIndex)) !== -1) {
131
+ matches.push({
132
+ startIndex,
133
+ endIndex: startIndex + search.length,
134
+ matchedContent: search
135
+ });
136
+ startIndex += search.length;
137
+ }
138
+
139
+ return matches;
140
+ }
141
+
142
+ /**
143
+ * Finds matches after normalizing line endings
144
+ */
145
+ private findNormalizedLineEndingMatches(content: string, search: string): MatchInfo[] {
146
+ const normalizedContent = this.normalizeLineEndings(content);
147
+ const normalizedSearch = this.normalizeLineEndings(search);
148
+
149
+ const matches: MatchInfo[] = [];
150
+ let startIndex = 0;
151
+
152
+ while ((startIndex = normalizedContent.indexOf(normalizedSearch, startIndex)) !== -1) {
153
+ // Map back to original content position
154
+ const originalStartIndex = this.mapNormalizedPositionToOriginal(content, startIndex);
155
+ const originalEndIndex = this.mapNormalizedPositionToOriginal(content, startIndex + normalizedSearch.length);
156
+
157
+ matches.push({
158
+ startIndex: originalStartIndex,
159
+ endIndex: originalEndIndex,
160
+ matchedContent: content.substring(originalStartIndex, originalEndIndex)
161
+ });
162
+ startIndex += normalizedSearch.length;
163
+ }
164
+
165
+ return matches;
166
+ }
167
+
168
+ /**
169
+ * Maps a position in normalized content back to the original content
170
+ */
171
+ private mapNormalizedPositionToOriginal(originalContent: string, normalizedPosition: number): number {
172
+ let originalPos = 0;
173
+ let normalizedPos = 0;
174
+
175
+ while (normalizedPos < normalizedPosition && originalPos < originalContent.length) {
176
+ if (originalPos + 1 < originalContent.length &&
177
+ originalContent[originalPos] === '\r' &&
178
+ originalContent[originalPos + 1] === '\n') {
179
+ // CRLF in original maps to single LF in normalized
180
+ originalPos += 2;
181
+ normalizedPos += 1;
182
+ } else if (originalContent[originalPos] === '\r') {
183
+ // Single CR in original maps to LF in normalized
184
+ originalPos += 1;
185
+ normalizedPos += 1;
186
+ } else {
187
+ // All other characters map 1:1
188
+ originalPos += 1;
189
+ normalizedPos += 1;
190
+ }
191
+ }
192
+
193
+ return originalPos;
194
+ }
195
+
196
+ /**
197
+ * Attempts to find matches by trimming whitespace from lines (single line only, for backward compatibility)
198
+ */
199
+ private findLineTrimmedMatches(content: string, search: string): MatchInfo[] {
200
+ const trimmedSearch = search.trim();
201
+ const lines = content.split(/\r?\n/);
202
+
203
+ for (let i = 0; i < lines.length; i++) {
204
+ const trimmedLine = lines[i].trim();
205
+ if (trimmedLine === trimmedSearch) {
206
+ // Calculate the starting index of this line in the original content
207
+ const startIndex = this.getLineStartIndex(content, i);
208
+ const endIndex = startIndex + lines[i].length;
209
+ return [{
210
+ startIndex,
211
+ endIndex,
212
+ matchedContent: lines[i]
213
+ }];
214
+ }
215
+ }
216
+
217
+ return [];
218
+ }
219
+
220
+ /**
221
+ * Finds matches using fuzzy multi-line comparison with trimmed lines
222
+ */
223
+ private findFuzzyMultilineMatches(content: string, search: string): MatchInfo[] {
224
+ // Extract non-empty lines from search for matching
225
+ const searchLines = search.split(/\r?\n/);
226
+ const nonEmptySearchLines = searchLines
227
+ .map(line => line.trim())
228
+ .filter(line => line.length > 0);
229
+
230
+ if (nonEmptySearchLines.length === 0) { return []; }
231
+
232
+ const contentLines = content.split(/\r?\n/);
233
+ const matches: MatchInfo[] = [];
234
+
235
+ // Try to find sequences in content that match all non-empty lines from search
236
+ for (let contentStart = 0; contentStart < contentLines.length; contentStart++) {
237
+ // First, check if this could be a valid starting position
238
+ const startLineTrimmed = contentLines[contentStart].trim();
239
+ if (startLineTrimmed.length === 0 || startLineTrimmed !== nonEmptySearchLines[0]) {
240
+ continue;
241
+ }
242
+
243
+ let searchIndex = 1; // We already matched the first line
244
+ let contentIndex = contentStart + 1;
245
+ let lastMatchedLine = contentStart;
246
+
247
+ // Try to match remaining non-empty lines from search
248
+ while (searchIndex < nonEmptySearchLines.length && contentIndex < contentLines.length) {
249
+ const contentLineTrimmed = contentLines[contentIndex].trim();
250
+
251
+ if (contentLineTrimmed.length === 0) {
252
+ // Skip empty lines in content
253
+ contentIndex++;
254
+ } else if (contentLineTrimmed === nonEmptySearchLines[searchIndex]) {
255
+ // Found a match
256
+ lastMatchedLine = contentIndex;
257
+ searchIndex++;
258
+ contentIndex++;
259
+ } else {
260
+ // No match, this starting position doesn't work
261
+ break;
262
+ }
263
+ }
264
+
265
+ // Check if we matched all non-empty lines
266
+ if (searchIndex === nonEmptySearchLines.length) {
267
+ const startIndex = this.getLineStartIndex(content, contentStart);
268
+ const endIndex = this.getLineEndIndex(content, lastMatchedLine);
269
+
270
+ matches.push({
271
+ startIndex,
272
+ endIndex,
273
+ matchedContent: content.substring(startIndex, endIndex)
274
+ });
275
+ }
276
+ }
277
+
278
+ return matches;
279
+ }
280
+
281
+ /**
282
+ * Calculates the starting index of a specific line number in the content.
283
+ */
284
+ private getLineStartIndex(content: string, lineNumber: number): number {
285
+ if (lineNumber === 0) { return 0; }
286
+
287
+ let index = 0;
288
+ let currentLine = 0;
289
+
290
+ while (currentLine < lineNumber && index < content.length) {
291
+ if (content[index] === '\r' && index + 1 < content.length && content[index + 1] === '\n') {
292
+ index += 2; // CRLF
293
+ currentLine++;
294
+ } else if (content[index] === '\r' || content[index] === '\n') {
295
+ index += 1; // CR or LF
296
+ currentLine++;
297
+ } else {
298
+ index += 1;
299
+ }
300
+ }
301
+
302
+ return index;
303
+ }
304
+
305
+ /**
306
+ * Calculates the ending index of a specific line number in the content (including the line).
307
+ */
308
+ private getLineEndIndex(content: string, lineNumber: number): number {
309
+ const lines = content.split(/\r?\n/);
310
+ if (lineNumber >= lines.length) {
311
+ return content.length;
312
+ }
313
+
314
+ let index = 0;
315
+ for (let i = 0; i <= lineNumber; i++) {
316
+ index += lines[i].length;
317
+ if (i < lineNumber) {
318
+ // Add line ending length
319
+ const searchPos = index;
320
+ if (content.indexOf('\r\n', searchPos) === searchPos) {
321
+ index += 2; // CRLF
322
+ } else if (index < content.length && (content[index] === '\r' || content[index] === '\n')) {
323
+ index += 1; // CR or LF
324
+ }
325
+ }
326
+ }
327
+
328
+ return index;
329
+ }
330
+
331
+ /**
332
+ * Replaces a single match while preserving indentation
333
+ */
334
+ private replaceSingleMatch(content: string, match: MatchInfo, newContent: string): string {
335
+ const beforeMatch = content.substring(0, match.startIndex);
336
+ const afterMatch = content.substring(match.endIndex);
337
+
338
+ // Detect the line ending style from entire original content, not just the match
339
+ const originalLineEnding = content.includes('\r\n') ? '\r\n' :
340
+ content.includes('\r') ? '\r' : '\n';
341
+
342
+ // Convert line endings in newContent to match original
343
+ const newContentWithCorrectLineEndings = this.convertLineEndings(newContent, originalLineEnding);
344
+
345
+ // Preserve indentation from the matched content
346
+ const preservedReplacement = this.preserveIndentation(match.matchedContent, newContentWithCorrectLineEndings, originalLineEnding);
347
+
348
+ return beforeMatch + preservedReplacement + afterMatch;
349
+ }
350
+
351
+ /**
352
+ * Replaces all matches
353
+ */
354
+ private replaceAllMatches(content: string, matches: MatchInfo[], newContent: string): string {
355
+ // Sort matches by position (descending) to avoid position shifts
356
+ const sortedMatches = [...matches].sort((a, b) => b.startIndex - a.startIndex);
357
+
358
+ // Detect the line ending style from entire original content
359
+ const originalLineEnding = content.includes('\r\n') ? '\r\n' :
360
+ content.includes('\r') ? '\r' : '\n';
361
+
362
+ let result = content;
363
+ for (const match of sortedMatches) {
364
+ const beforeMatch = result.substring(0, match.startIndex);
365
+ const afterMatch = result.substring(match.endIndex);
366
+
367
+ // Convert line endings in newContent to match original
368
+ const newContentWithCorrectLineEndings = this.convertLineEndings(newContent, originalLineEnding);
369
+
370
+ const preservedReplacement = this.preserveIndentation(match.matchedContent, newContentWithCorrectLineEndings, originalLineEnding);
371
+ result = beforeMatch + preservedReplacement + afterMatch;
372
+ }
373
+
374
+ return result;
375
+ }
376
+
377
+ /**
378
+ * Preserves the indentation from the original content when applying the replacement
379
+ */
380
+ private preserveIndentation(originalContent: string, newContent: string, lineEnding: string): string {
381
+ const originalLines = originalContent.split(/\r?\n/);
382
+ const newLines = newContent.split(/\r?\n/);
383
+
384
+ if (originalLines.length === 0 || newLines.length === 0) {
385
+ return newContent;
386
+ }
387
+
388
+ // Find first non-empty line in original to get base indentation
389
+ let originalBaseIndent = '';
390
+ let originalUseTabs = false;
391
+ for (const line of originalLines) {
392
+ if (line.trim().length > 0) {
393
+ originalBaseIndent = line.match(/^\s*/)?.[0] || '';
394
+ originalUseTabs = originalBaseIndent.includes('\t');
395
+ break;
396
+ }
397
+ }
398
+
399
+ // Find first non-empty line in new content to get base indentation
400
+ let newBaseIndent = '';
401
+ for (const line of newLines) {
402
+ if (line.trim().length > 0) {
403
+ newBaseIndent = line.match(/^\s*/)?.[0] || '';
404
+ break;
405
+ }
406
+ }
407
+
408
+ // Apply the indentation to all lines of new content
409
+ const result = newLines.map(line => {
410
+ // Empty lines remain empty
411
+ if (line.trim().length === 0) {
412
+ return '';
413
+ }
414
+
415
+ // Get current line's indentation
416
+ const currentIndent = line.match(/^\s*/)?.[0] || '';
417
+
418
+ // Calculate relative indentation
419
+ let relativeIndent = currentIndent;
420
+ if (newBaseIndent.length > 0) {
421
+ // If the current line has at least the base indentation, preserve relative indentation
422
+ if (currentIndent.startsWith(newBaseIndent)) {
423
+ relativeIndent = currentIndent.substring(newBaseIndent.length);
424
+ } else {
425
+ // If current line has less indentation than base, use it as-is
426
+ relativeIndent = '';
427
+ }
428
+ }
429
+
430
+ // Convert spaces to tabs if original uses tabs
431
+ let convertedIndent = originalBaseIndent + relativeIndent;
432
+ if (originalUseTabs && !relativeIndent.includes('\t')) {
433
+ // Convert 4 spaces to 1 tab (common convention)
434
+ convertedIndent = convertedIndent.replace(/ /g, '\t');
435
+ }
436
+
437
+ // Apply converted indentation + trimmed content
438
+ return convertedIndent + line.trim();
439
+ });
440
+
441
+ return result.join(lineEnding);
442
+ }
443
+
444
+ /**
445
+ * Converts line endings in content to the specified line ending style
446
+ */
447
+ private convertLineEndings(content: string, lineEnding: string): string {
448
+ // First normalize to LF
449
+ const normalized = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
450
+
451
+ // Then convert to target line ending
452
+ if (lineEnding === '\r\n') {
453
+ return normalized.replace(/\n/g, '\r\n');
454
+ } else if (lineEnding === '\r') {
455
+ return normalized.replace(/\n/g, '\r');
456
+ }
457
+ return normalized;
458
+ }
459
+
460
+ /**
461
+ * Truncates content for error messages to avoid overly long error messages
462
+ */
463
+ private truncateForError(content: string, maxLength: number = 100): string {
464
+ if (content.length <= maxLength) {
465
+ return content;
466
+ }
467
+
468
+ const half = Math.floor(maxLength / 2) - 3; // -3 for "..."
469
+ return content.substring(0, half) + '...' + content.substring(content.length - half);
470
+ }
471
+ }
@@ -15,13 +15,13 @@
15
15
  // **
16
16
 
17
17
  import { expect } from 'chai';
18
- import { ContentReplacer, Replacement } from './content-replacer';
18
+ import { ContentReplacerV1Impl, Replacement } from './content-replacer';
19
19
 
20
- describe('ContentReplacer', () => {
21
- let contentReplacer: ContentReplacer;
20
+ describe('ContentReplacerV1Impl', () => {
21
+ let contentReplacer: ContentReplacerV1Impl;
22
22
 
23
23
  before(() => {
24
- contentReplacer = new ContentReplacer();
24
+ contentReplacer = new ContentReplacerV1Impl();
25
25
  });
26
26
 
27
27
  it('should replace content when oldContent matches exactly', () => {
@@ -20,7 +20,17 @@ export interface Replacement {
20
20
  multiple?: boolean;
21
21
  }
22
22
 
23
- export class ContentReplacer {
23
+ export interface ContentReplacer {
24
+ /**
25
+ * Applies a list of replacements to the original content using a multi-step matching strategy.
26
+ * @param originalContent The original file content.
27
+ * @param replacements Array of Replacement objects.
28
+ * @returns An object containing the updated content and any error messages.
29
+ */
30
+ applyReplacements(originalContent: string, replacements: Replacement[]): { updatedContent: string, errors: string[] };
31
+ }
32
+
33
+ export class ContentReplacerV1Impl implements ContentReplacer {
24
34
  /**
25
35
  * Applies a list of replacements to the original content using a multi-step matching strategy.
26
36
  * @param originalContent The original file content.
@@ -23,4 +23,5 @@ export interface KeyStoreService {
23
23
  deletePassword(service: string, account: string): Promise<boolean>;
24
24
  findPassword(service: string): Promise<string | undefined>;
25
25
  findCredentials(service: string): Promise<Array<{ account: string, password: string }>>;
26
+ keys(service: string): Promise<string[]>;
26
27
  }
@@ -335,7 +335,7 @@ export class ElectronMenuContribution extends BrowserMenuBarContribution impleme
335
335
  registry.registerKeybindings(
336
336
  {
337
337
  command: ElectronCommands.TOGGLE_DEVELOPER_TOOLS.id,
338
- keybinding: 'ctrlcmd+alt+i'
338
+ keybinding: 'alt+f12'
339
339
  },
340
340
  {
341
341
  command: ElectronCommands.RELOAD.id,