@theia/preferences 1.74.0-next.5 → 1.74.0-next.53
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.
- package/lib/browser/preference-frontend-contribution.d.ts +8 -1
- package/lib/browser/preference-frontend-contribution.d.ts.map +1 -1
- package/lib/browser/preference-frontend-contribution.js +45 -5
- package/lib/browser/preference-frontend-contribution.js.map +1 -1
- package/lib/browser/preference-frontend-module.d.ts.map +1 -1
- package/lib/browser/preference-frontend-module.js +3 -0
- package/lib/browser/preference-frontend-module.js.map +1 -1
- package/lib/browser/session-preference-status-bar-contribution.d.ts +33 -0
- package/lib/browser/session-preference-status-bar-contribution.d.ts.map +1 -0
- package/lib/browser/session-preference-status-bar-contribution.js +157 -0
- package/lib/browser/session-preference-status-bar-contribution.js.map +1 -0
- package/lib/browser/views/components/preference-node-renderer.d.ts +8 -0
- package/lib/browser/views/components/preference-node-renderer.d.ts.map +1 -1
- package/lib/browser/views/components/preference-node-renderer.js +37 -5
- package/lib/browser/views/components/preference-node-renderer.js.map +1 -1
- package/lib/common/cli-preferences.d.ts +1 -0
- package/lib/common/cli-preferences.d.ts.map +1 -1
- package/lib/node/preference-backend-module.d.ts.map +1 -1
- package/lib/node/preference-backend-module.js +2 -0
- package/lib/node/preference-backend-module.js.map +1 -1
- package/lib/node/preference-cli-contribution.d.ts +11 -1
- package/lib/node/preference-cli-contribution.d.ts.map +1 -1
- package/lib/node/preference-cli-contribution.js +44 -9
- package/lib/node/preference-cli-contribution.js.map +1 -1
- package/lib/node/preference-cli-contribution.spec.d.ts +2 -0
- package/lib/node/preference-cli-contribution.spec.d.ts.map +1 -0
- package/lib/node/preference-cli-contribution.spec.js +157 -0
- package/lib/node/preference-cli-contribution.spec.js.map +1 -0
- package/package.json +8 -8
- package/src/browser/preference-frontend-contribution.ts +50 -5
- package/src/browser/preference-frontend-module.ts +3 -0
- package/src/browser/session-preference-status-bar-contribution.ts +157 -0
- package/src/browser/views/components/preference-node-renderer.ts +38 -5
- package/src/common/cli-preferences.ts +1 -0
- package/src/node/preference-backend-module.ts +2 -0
- package/src/node/preference-cli-contribution.spec.ts +171 -0
- package/src/node/preference-cli-contribution.ts +48 -10
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource and others.
|
|
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 { expect } from 'chai';
|
|
18
|
+
import { OS } from '@theia/core/lib/common/os';
|
|
19
|
+
import { RemoteCliContext } from '@theia/core/lib/node/remote/remote-cli-contribution';
|
|
20
|
+
import { PreferenceCliContribution } from './preference-cli-contribution';
|
|
21
|
+
|
|
22
|
+
const REMOTE_CLI_CONTEXT: RemoteCliContext = {
|
|
23
|
+
platform: { os: OS.Type.Linux, arch: 'x64' },
|
|
24
|
+
directory: '/'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('PreferenceCliContribution', () => {
|
|
28
|
+
|
|
29
|
+
let contribution: PreferenceCliContribution;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
contribution = new PreferenceCliContribution();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('--set-preference', () => {
|
|
36
|
+
it('parses a single key=value entry as JSON', async () => {
|
|
37
|
+
contribution.setArguments({ setPreference: 'editor.fontSize=14' });
|
|
38
|
+
expect(await contribution.getPreferences()).to.deep.equal([['editor.fontSize', 14]]);
|
|
39
|
+
expect(await contribution.getSessionPreferences()).to.deep.equal([]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('parses string values quoted as JSON strings', async () => {
|
|
43
|
+
contribution.setArguments({ setPreference: 'workbench.colorTheme="Dark+"' });
|
|
44
|
+
expect(await contribution.getPreferences()).to.deep.equal([['workbench.colorTheme', 'Dark+']]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('parses object values', async () => {
|
|
48
|
+
contribution.setArguments({ setPreference: 'ai-features.chat.toolConfirmation={"shellExecute":"always_allow"}' });
|
|
49
|
+
expect(await contribution.getPreferences()).to.deep.equal([
|
|
50
|
+
['ai-features.chat.toolConfirmation', { shellExecute: 'always_allow' }]
|
|
51
|
+
]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('accepts an array of entries', async () => {
|
|
55
|
+
contribution.setArguments({ setPreference: ['editor.fontSize=12', 'editor.tabSize=2'] });
|
|
56
|
+
expect(await contribution.getPreferences()).to.deep.equal([
|
|
57
|
+
['editor.fontSize', 12],
|
|
58
|
+
['editor.tabSize', 2]
|
|
59
|
+
]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('decodes base64-prefixed JSON values', async () => {
|
|
63
|
+
const json = JSON.stringify({ nested: true, n: 1 });
|
|
64
|
+
const b64 = Buffer.from(json, 'utf-8').toString('base64');
|
|
65
|
+
contribution.setArguments({ setPreference: `my.pref=base64:${b64}` });
|
|
66
|
+
expect(await contribution.getPreferences()).to.deep.equal([
|
|
67
|
+
['my.pref', { nested: true, n: 1 }]
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('preserves "=" characters inside the value', async () => {
|
|
72
|
+
contribution.setArguments({ setPreference: 'my.pref="a=b=c"' });
|
|
73
|
+
expect(await contribution.getPreferences()).to.deep.equal([['my.pref', 'a=b=c']]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('skips entries that do not contain "="', async () => {
|
|
77
|
+
const originalWarn = console.warn;
|
|
78
|
+
console.warn = () => { /* silence expected warning */ };
|
|
79
|
+
try {
|
|
80
|
+
contribution.setArguments({ setPreference: ['justAKey', 'editor.fontSize=14'] });
|
|
81
|
+
} finally {
|
|
82
|
+
console.warn = originalWarn;
|
|
83
|
+
}
|
|
84
|
+
expect(await contribution.getPreferences()).to.deep.equal([['editor.fontSize', 14]]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('skips entries whose value is not valid JSON', async () => {
|
|
88
|
+
const originalWarn = console.warn;
|
|
89
|
+
console.warn = () => { /* silence expected warning */ };
|
|
90
|
+
try {
|
|
91
|
+
contribution.setArguments({ setPreference: ['my.pref=notJson', 'editor.fontSize=14'] });
|
|
92
|
+
} finally {
|
|
93
|
+
console.warn = originalWarn;
|
|
94
|
+
}
|
|
95
|
+
expect(await contribution.getPreferences()).to.deep.equal([['editor.fontSize', 14]]);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('--session-preference', () => {
|
|
100
|
+
it('routes entries into the session preferences bucket', async () => {
|
|
101
|
+
contribution.setArguments({ sessionPreference: 'ai-features.chat.defaultToolConfirmation="always_allow"' });
|
|
102
|
+
expect(await contribution.getSessionPreferences()).to.deep.equal([
|
|
103
|
+
['ai-features.chat.defaultToolConfirmation', 'always_allow']
|
|
104
|
+
]);
|
|
105
|
+
expect(await contribution.getPreferences()).to.deep.equal([]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('supports multiple session entries and base64', async () => {
|
|
109
|
+
const b64 = Buffer.from('{"shellExecute":"always_allow"}', 'utf-8').toString('base64');
|
|
110
|
+
contribution.setArguments({
|
|
111
|
+
sessionPreference: [
|
|
112
|
+
'ai-features.chat.defaultToolConfirmation="always_allow"',
|
|
113
|
+
`ai-features.chat.toolConfirmation=base64:${b64}`
|
|
114
|
+
]
|
|
115
|
+
});
|
|
116
|
+
expect(await contribution.getSessionPreferences()).to.deep.equal([
|
|
117
|
+
['ai-features.chat.defaultToolConfirmation', 'always_allow'],
|
|
118
|
+
['ai-features.chat.toolConfirmation', { shellExecute: 'always_allow' }]
|
|
119
|
+
]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('keeps --set-preference and --session-preference independent', async () => {
|
|
123
|
+
contribution.setArguments({
|
|
124
|
+
setPreference: 'editor.fontSize=14',
|
|
125
|
+
sessionPreference: 'editor.fontSize=20'
|
|
126
|
+
});
|
|
127
|
+
expect(await contribution.getPreferences()).to.deep.equal([['editor.fontSize', 14]]);
|
|
128
|
+
expect(await contribution.getSessionPreferences()).to.deep.equal([['editor.fontSize', 20]]);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('enhanceArgs (remote forwarding)', () => {
|
|
133
|
+
it('returns an empty list when no session prefs are set', () => {
|
|
134
|
+
expect(contribution.enhanceArgs(REMOTE_CLI_CONTEXT)).to.deep.equal([]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('emits one base64-encoded --session-preference flag per entry', () => {
|
|
138
|
+
contribution.setArguments({
|
|
139
|
+
sessionPreference: [
|
|
140
|
+
'ai-features.chat.defaultToolConfirmation="always_allow"',
|
|
141
|
+
'ai-features.chat.toolConfirmation={"shellExecute":"always_allow"}'
|
|
142
|
+
]
|
|
143
|
+
});
|
|
144
|
+
const args = contribution.enhanceArgs(REMOTE_CLI_CONTEXT);
|
|
145
|
+
expect(args).to.have.lengthOf(2);
|
|
146
|
+
for (const arg of args) {
|
|
147
|
+
expect(arg.startsWith('--session-preference=')).to.be.true;
|
|
148
|
+
const eq = arg.indexOf('=', '--session-preference='.length);
|
|
149
|
+
const key = arg.substring('--session-preference='.length, eq);
|
|
150
|
+
const rawValue = arg.substring(eq + 1);
|
|
151
|
+
expect(rawValue.startsWith('base64:')).to.be.true;
|
|
152
|
+
const decoded = JSON.parse(Buffer.from(rawValue.substring('base64:'.length), 'base64').toString('utf-8'));
|
|
153
|
+
if (key === 'ai-features.chat.defaultToolConfirmation') {
|
|
154
|
+
expect(decoded).to.equal('always_allow');
|
|
155
|
+
} else {
|
|
156
|
+
expect(decoded).to.deep.equal({ shellExecute: 'always_allow' });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('does not forward --set-preference values', () => {
|
|
162
|
+
contribution.setArguments({
|
|
163
|
+
setPreference: 'editor.fontSize=14',
|
|
164
|
+
sessionPreference: 'foo="bar"'
|
|
165
|
+
});
|
|
166
|
+
const args = contribution.enhanceArgs(REMOTE_CLI_CONTEXT);
|
|
167
|
+
expect(args).to.have.lengthOf(1);
|
|
168
|
+
expect(args[0]).to.match(/^--session-preference=foo=base64:/);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -17,30 +17,52 @@
|
|
|
17
17
|
import { injectable } from '@theia/core/shared/inversify';
|
|
18
18
|
import { Argv } from '@theia/core/shared/yargs';
|
|
19
19
|
import { CliContribution } from '@theia/core/lib/node/cli';
|
|
20
|
+
import { RemoteCliContext, RemoteCliContribution } from '@theia/core/lib/node/remote/remote-cli-contribution';
|
|
20
21
|
import { CliPreferences } from '../common/cli-preferences';
|
|
21
22
|
|
|
22
23
|
@injectable()
|
|
23
|
-
export class PreferenceCliContribution implements CliContribution, CliPreferences {
|
|
24
|
+
export class PreferenceCliContribution implements CliContribution, CliPreferences, RemoteCliContribution {
|
|
24
25
|
|
|
25
26
|
protected preferences: [string, unknown][] = [];
|
|
27
|
+
protected sessionPreferences: [string, unknown][] = [];
|
|
26
28
|
|
|
27
29
|
configure(conf: Argv<{}>): void {
|
|
28
30
|
conf.option('set-preference', {
|
|
29
31
|
nargs: 1,
|
|
30
|
-
desc: 'sets the specified preference'
|
|
32
|
+
desc: 'sets the specified preference (persisted to user settings)'
|
|
33
|
+
});
|
|
34
|
+
conf.option('session-preference', {
|
|
35
|
+
nargs: 1,
|
|
36
|
+
desc: 'sets the specified preference for this process only (in-memory, not persisted)'
|
|
31
37
|
});
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
setArguments(args: Record<string, unknown>): void {
|
|
35
41
|
if (args.setPreference) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
this.parseInto(args.setPreference, this.preferences);
|
|
43
|
+
}
|
|
44
|
+
if (args.sessionPreference) {
|
|
45
|
+
this.parseInto(args.sessionPreference, this.sessionPreferences);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected parseInto(raw: unknown, target: [string, unknown][]): void {
|
|
50
|
+
const entries: string[] = raw instanceof Array ? raw : [raw as string];
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const firstEqualIndex = entry.indexOf('=');
|
|
53
|
+
if (firstEqualIndex <= 0) {
|
|
54
|
+
console.warn(`Ignoring preference CLI argument "${entry}": expected KEY=JSONVALUE.`);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
let rawValue = entry.substring(firstEqualIndex + 1);
|
|
58
|
+
if (rawValue.startsWith('base64:')) {
|
|
59
|
+
rawValue = Buffer.from(rawValue.substring('base64:'.length), 'base64').toString('utf-8');
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
target.push([entry.substring(0, firstEqualIndex), JSON.parse(rawValue)]);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
65
|
+
console.warn(`Ignoring preference CLI argument "${entry}": value is not valid JSON (${reason}).`);
|
|
44
66
|
}
|
|
45
67
|
}
|
|
46
68
|
}
|
|
@@ -49,4 +71,20 @@ export class PreferenceCliContribution implements CliContribution, CliPreference
|
|
|
49
71
|
return this.preferences;
|
|
50
72
|
}
|
|
51
73
|
|
|
74
|
+
async getSessionPreferences(): Promise<[string, unknown][]> {
|
|
75
|
+
return this.sessionPreferences;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Forward `--session-preference` values to the remote backend when attaching
|
|
80
|
+
* to a remote (e.g. dev container). Values are base64-encoded JSON to survive
|
|
81
|
+
* shell argument parsing intact.
|
|
82
|
+
*/
|
|
83
|
+
enhanceArgs(_context: RemoteCliContext): string[] {
|
|
84
|
+
return this.sessionPreferences.map(([key, value]) => {
|
|
85
|
+
const encoded = Buffer.from(JSON.stringify(value), 'utf-8').toString('base64');
|
|
86
|
+
return `--session-preference=${key}=base64:${encoded}`;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
52
90
|
}
|