@theia/preferences 1.59.0-next.72 → 1.60.0-next.43

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 (52) hide show
  1. package/lib/browser/util/preference-layout.d.ts.map +1 -1
  2. package/lib/browser/util/preference-layout.js +73 -1
  3. package/lib/browser/util/preference-layout.js.map +1 -1
  4. package/lib/browser/util/preference-tree-generator.d.ts +6 -0
  5. package/lib/browser/util/preference-tree-generator.d.ts.map +1 -1
  6. package/lib/browser/util/preference-tree-generator.js +37 -23
  7. package/lib/browser/util/preference-tree-generator.js.map +1 -1
  8. package/lib/browser/util/preference-tree-label-provider.d.ts +1 -1
  9. package/lib/browser/util/preference-tree-label-provider.d.ts.map +1 -1
  10. package/lib/browser/util/preference-tree-label-provider.js.map +1 -1
  11. package/lib/browser/util/preference-types.d.ts +1 -1
  12. package/lib/browser/util/preference-types.d.ts.map +1 -1
  13. package/lib/browser/views/components/preference-json-input.d.ts +1 -1
  14. package/lib/browser/views/components/preference-json-input.d.ts.map +1 -1
  15. package/lib/browser/views/components/preference-node-renderer.d.ts +1 -1
  16. package/lib/browser/views/components/preference-node-renderer.d.ts.map +1 -1
  17. package/lib/browser/views/components/preference-node-renderer.js +1 -0
  18. package/lib/browser/views/components/preference-node-renderer.js.map +1 -1
  19. package/lib/browser/views/components/preference-null-input.d.ts +16 -0
  20. package/lib/browser/views/components/preference-null-input.d.ts.map +1 -0
  21. package/lib/browser/views/components/preference-null-input.js +59 -0
  22. package/lib/browser/views/components/preference-null-input.js.map +1 -0
  23. package/lib/browser/views/components/preference-number-input.d.ts.map +1 -1
  24. package/lib/browser/views/components/preference-number-input.js +44 -8
  25. package/lib/browser/views/components/preference-number-input.js.map +1 -1
  26. package/lib/browser/views/components/preference-select-input.d.ts +1 -1
  27. package/lib/browser/views/components/preference-select-input.d.ts.map +1 -1
  28. package/lib/browser/views/preference-editor-widget.d.ts +3 -0
  29. package/lib/browser/views/preference-editor-widget.d.ts.map +1 -1
  30. package/lib/browser/views/preference-editor-widget.js +15 -2
  31. package/lib/browser/views/preference-editor-widget.js.map +1 -1
  32. package/lib/browser/views/preference-scope-tabbar-widget.d.ts +1 -1
  33. package/lib/browser/views/preference-scope-tabbar-widget.d.ts.map +1 -1
  34. package/lib/browser/views/preference-scope-tabbar-widget.js +2 -1
  35. package/lib/browser/views/preference-scope-tabbar-widget.js.map +1 -1
  36. package/lib/browser/views/preference-widget-bindings.d.ts.map +1 -1
  37. package/lib/browser/views/preference-widget-bindings.js +3 -0
  38. package/lib/browser/views/preference-widget-bindings.js.map +1 -1
  39. package/package.json +9 -9
  40. package/src/browser/style/index.css +7 -10
  41. package/src/browser/util/preference-layout.ts +74 -1
  42. package/src/browser/util/preference-tree-generator.ts +38 -22
  43. package/src/browser/util/preference-tree-label-provider.ts +1 -1
  44. package/src/browser/util/preference-types.ts +1 -1
  45. package/src/browser/views/components/preference-json-input.ts +1 -1
  46. package/src/browser/views/components/preference-node-renderer.ts +2 -1
  47. package/src/browser/views/components/preference-null-input.ts +52 -0
  48. package/src/browser/views/components/preference-number-input.ts +43 -7
  49. package/src/browser/views/components/preference-select-input.ts +1 -1
  50. package/src/browser/views/preference-editor-widget.ts +13 -2
  51. package/src/browser/views/preference-scope-tabbar-widget.tsx +2 -1
  52. package/src/browser/views/preference-widget-bindings.ts +3 -0
@@ -21,6 +21,7 @@ import { Emitter } from '@theia/core';
21
21
  import debounce = require('@theia/core/shared/lodash.debounce');
22
22
  import { Preference } from './preference-types';
23
23
  import { COMMONLY_USED_SECTION_PREFIX, PreferenceLayoutProvider } from './preference-layout';
24
+ import { PreferenceTreeLabelProvider } from './preference-tree-label-provider';
24
25
 
25
26
  export interface CreatePreferencesGroupOptions {
26
27
  id: string,
@@ -37,6 +38,7 @@ export class PreferenceTreeGenerator {
37
38
  @inject(PreferenceSchemaProvider) protected readonly schemaProvider: PreferenceSchemaProvider;
38
39
  @inject(PreferenceConfigurations) protected readonly preferenceConfigs: PreferenceConfigurations;
39
40
  @inject(PreferenceLayoutProvider) protected readonly layoutProvider: PreferenceLayoutProvider;
41
+ @inject(PreferenceTreeLabelProvider) protected readonly labelProvider: PreferenceTreeLabelProvider;
40
42
 
41
43
  protected _root: CompositeTreeNode;
42
44
  protected _idCache = new Map<string, string>();
@@ -92,7 +94,7 @@ export class PreferenceTreeGenerator {
92
94
  }
93
95
  for (const propertyName of propertyNames) {
94
96
  const property = preferencesSchema.properties[propertyName];
95
- if (!this.preferenceConfigs.isSectionName(propertyName) && !OVERRIDE_PROPERTY_PATTERN.test(propertyName) && !property.deprecationMessage) {
97
+ if (!property.hidden && !property.deprecationMessage && !this.preferenceConfigs.isSectionName(propertyName) && !OVERRIDE_PROPERTY_PATTERN.test(propertyName)) {
96
98
  if (property.owner) {
97
99
  this.createPluginLeafNode(propertyName, property, root, groups);
98
100
  } else {
@@ -122,26 +124,8 @@ export class PreferenceTreeGenerator {
122
124
  };
123
125
 
124
126
  protected createBuiltinLeafNode(name: string, property: PreferenceDataProperty, root: CompositeTreeNode, groups: Map<string, Preference.CompositeTreeNode>): void {
125
- const layoutItem = this.layoutProvider.getLayoutForPreference(name);
126
- const labels = (layoutItem?.id ?? name).split('.');
127
- const groupID = this.getGroupName(labels);
128
- const subgroupName = this.getSubgroupName(labels, groupID);
129
- const subgroupID = [groupID, subgroupName].join('.');
130
- const toplevelParent = this.getOrCreatePreferencesGroup({
131
- id: groupID,
132
- group: groupID,
133
- root,
134
- groups,
135
- label: this.generateName(groupID)
136
- });
137
- const immediateParent = subgroupName ? this.getOrCreatePreferencesGroup({
138
- id: subgroupID,
139
- group: groupID,
140
- root: toplevelParent,
141
- groups,
142
- label: layoutItem?.label ?? this.generateName(subgroupName)
143
- }) : undefined;
144
- this.createLeafNode(name, immediateParent || toplevelParent, property);
127
+ const { immediateParent, topLevelParent } = this.getParents(name, root, groups);
128
+ this.createLeafNode(name, immediateParent || topLevelParent, property);
145
129
  }
146
130
 
147
131
  protected createPluginLeafNode(name: string, property: PreferenceDataProperty, root: CompositeTreeNode, groups: Map<string, Preference.CompositeTreeNode>): void {
@@ -183,6 +167,38 @@ export class PreferenceTreeGenerator {
183
167
  return this._idCache.get(preferenceId) ?? '';
184
168
  }
185
169
 
170
+ protected getParents(
171
+ name: string, root: CompositeTreeNode, groups: Map<string, Preference.CompositeTreeNode>
172
+ ): {
173
+ topLevelParent: Preference.CompositeTreeNode, immediateParent: Preference.CompositeTreeNode | undefined
174
+ } {
175
+ const layoutItem = this.layoutProvider.getLayoutForPreference(name);
176
+ const labels = (layoutItem?.id ?? name).split('.');
177
+ const groupID = this.getGroupName(labels);
178
+ const subgroupName = groupID !== labels[0]
179
+ ? labels[0]
180
+ // If a layout item is present, any additional segments are sections
181
+ // If not, then the name describes a leaf node and only non-final segments are sections.
182
+ : layoutItem || labels.length > 2
183
+ ? labels.at(1)
184
+ : undefined;
185
+ const topLevelParent = this.getOrCreatePreferencesGroup({
186
+ id: groupID,
187
+ group: groupID,
188
+ root,
189
+ groups,
190
+ label: this.generateName(groupID)
191
+ });
192
+ const immediateParent = subgroupName ? this.getOrCreatePreferencesGroup({
193
+ id: [groupID, subgroupName].join('.'),
194
+ group: groupID,
195
+ root: topLevelParent,
196
+ groups,
197
+ label: layoutItem?.label ?? this.generateName(subgroupName)
198
+ }) : undefined;
199
+ return { immediateParent, topLevelParent };
200
+ }
201
+
186
202
  protected getGroupName(labels: string[]): string {
187
203
  const defaultGroup = labels[0];
188
204
  if (this.layoutProvider.hasCategory(defaultGroup)) {
@@ -202,7 +218,7 @@ export class PreferenceTreeGenerator {
202
218
  }
203
219
 
204
220
  protected generateName(id: string): string {
205
- return id.substring(0, 1).toUpperCase() + id.substring(1);
221
+ return this.labelProvider.formatString(id);
206
222
  }
207
223
 
208
224
  doHandleChangedSchema(): void {
@@ -55,7 +55,7 @@ export class PreferenceTreeLabelProvider implements LabelProviderContribution {
55
55
  }
56
56
  }
57
57
 
58
- protected formatString(string: string): string {
58
+ formatString(string: string): string {
59
59
  let formattedString = string[0].toLocaleUpperCase();
60
60
  for (let i = 1; i < string.length; i++) {
61
61
  if (this.isUpperCase(string[i]) && !/\s/.test(string[i - 1]) && !this.isUpperCase(string[i - 1])) {
@@ -24,7 +24,7 @@ import {
24
24
  CommonCommands,
25
25
  } from '@theia/core/lib/browser';
26
26
  import { Command, MenuPath } from '@theia/core';
27
- import { JSONValue } from '@theia/core/shared/@phosphor/coreutils';
27
+ import { JSONValue } from '@theia/core/shared/@lumino/coreutils';
28
28
  import { JsonType } from '@theia/core/lib/common/json-schema';
29
29
 
30
30
  export namespace Preference {
@@ -18,7 +18,7 @@ import { PreferenceLeafNodeRenderer, PreferenceNodeRenderer } from './preference
18
18
  import { injectable, inject, interfaces } from '@theia/core/shared/inversify';
19
19
  import { CommandService, nls } from '@theia/core/lib/common';
20
20
  import { Preference, PreferencesCommands } from '../../util/preference-types';
21
- import { JSONValue } from '@theia/core/shared/@phosphor/coreutils';
21
+ import { JSONValue } from '@theia/core/shared/@lumino/coreutils';
22
22
  import { PreferenceLeafNodeRendererContribution } from './preference-node-renderer-creator';
23
23
 
24
24
  @injectable()
@@ -23,7 +23,7 @@ import { Preference, PreferenceMenus } from '../../util/preference-types';
23
23
  import { PreferenceTreeLabelProvider } from '../../util/preference-tree-label-provider';
24
24
  import { PreferencesScopeTabBar } from '../preference-scope-tabbar-widget';
25
25
  import { Disposable, nls } from '@theia/core/lib/common';
26
- import { JSONValue } from '@theia/core/shared/@phosphor/coreutils';
26
+ import { JSONValue } from '@theia/core/shared/@lumino/coreutils';
27
27
  import debounce = require('@theia/core/shared/lodash.debounce');
28
28
  import { PreferenceTreeModel } from '../../preference-tree-model';
29
29
  import { PreferencesSearchbarWidget } from '../preference-searchbar-widget';
@@ -269,6 +269,7 @@ export abstract class PreferenceLeafNodeRenderer<ValueType extends JSONValue, In
269
269
  menuPath: PreferenceMenus.PREFERENCE_EDITOR_CONTEXT_MENU,
270
270
  anchor: { x: domRect.left, y: domRect.bottom },
271
271
  args: [{ id: this.id, value }],
272
+ context: target,
272
273
  onHide: () => this.hideCog()
273
274
  });
274
275
  }
@@ -0,0 +1,52 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 EclipseSource GmbH 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 { injectable, interfaces } from '@theia/core/shared/inversify';
18
+ import { PreferenceLeafNodeRenderer, PreferenceNodeRenderer } from './preference-node-renderer';
19
+ import { Preference } from '../../util/preference-types';
20
+ import { PreferenceLeafNodeRendererContribution } from './preference-node-renderer-creator';
21
+
22
+ @injectable()
23
+ /** For rendering preference items for which the only interesting feature is the description */
24
+ export class PreferenceNullInputRenderer extends PreferenceLeafNodeRenderer<null, HTMLElement> {
25
+ protected override createInteractable(container: HTMLElement): void {
26
+ const span = document.createElement('span');
27
+ this.interactable = span;
28
+ container.appendChild(span);
29
+ }
30
+
31
+ protected override getFallbackValue(): null {
32
+ // eslint-disable-next-line no-null/no-null
33
+ return null;
34
+ }
35
+
36
+ protected override doHandleValueChange(): void { }
37
+ }
38
+
39
+ @injectable()
40
+ export class PreferenceNullRendererContribution extends PreferenceLeafNodeRendererContribution {
41
+ static ID = 'preference-null-renderer';
42
+ id = PreferenceNullRendererContribution.ID;
43
+
44
+ canHandleLeafNode(node: Preference.LeafNode): number {
45
+ const isOnlyNull = node.preference.data.type === 'null' || Array.isArray(node.preference.data.type) && node.preference.data.type.every(candidate => candidate === 'null');
46
+ return isOnlyNull ? 5 : 0;
47
+ }
48
+
49
+ createLeafNodeRenderer(container: interfaces.Container): PreferenceNodeRenderer {
50
+ return container.get(PreferenceNullInputRenderer);
51
+ }
52
+ }
@@ -14,7 +14,7 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import { nls } from '@theia/core';
17
+ import { nls, isBoolean, isNumber } from '@theia/core';
18
18
  import { injectable, interfaces } from '@theia/core/shared/inversify';
19
19
  import { Preference } from '../../util/preference-types';
20
20
  import { PreferenceLeafNodeRenderer, PreferenceNodeRenderer } from './preference-node-renderer';
@@ -105,15 +105,51 @@ export class PreferenceNumberInputRenderer extends PreferenceLeafNodeRenderer<nu
105
105
  if (input === '' || isNaN(inputValue)) {
106
106
  return { value: NaN, message: nls.localizeByDefault('Value must be a number.') };
107
107
  }
108
- if (data.minimum && inputValue < data.minimum) {
109
- errorMessages.push(nls.localizeByDefault('Value must be greater than or equal to {0}.', data.minimum));
110
- };
111
- if (data.maximum && inputValue > data.maximum) {
112
- errorMessages.push(nls.localizeByDefault('Value must be less than or equal to {0}.', data.maximum));
113
- };
114
108
  if (data.type === 'integer' && !Number.isInteger(inputValue)) {
115
109
  errorMessages.push(nls.localizeByDefault('Value must be an integer.'));
116
110
  }
111
+ if (data.minimum !== undefined && isFinite(data.minimum)) {
112
+ // https://json-schema.org/understanding-json-schema/reference/numeric
113
+ // "In JSON Schema Draft 4, exclusiveMinimum and exclusiveMaximum work differently.
114
+ // There they are boolean values, that indicate whether minimum and maximum are exclusive of the value"
115
+ if (isBoolean(data.exclusiveMinimum) && data.exclusiveMinimum) {
116
+ if (inputValue <= data.minimum) {
117
+ errorMessages.push(nls.localizeByDefault('Value must be strictly greater than {0}.', data.minimum));
118
+ }
119
+ } else {
120
+ if (inputValue < data.minimum) {
121
+ errorMessages.push(nls.localizeByDefault('Value must be greater than or equal to {0}.', data.minimum));
122
+ }
123
+ }
124
+ }
125
+ if (data.maximum !== undefined && isFinite(data.maximum)) {
126
+ // https://json-schema.org/understanding-json-schema/reference/numeric
127
+ // "In JSON Schema Draft 4, exclusiveMinimum and exclusiveMaximum work differently.
128
+ // There they are boolean values, that indicate whether minimum and maximum are exclusive of the value"
129
+ if (isBoolean(data.exclusiveMaximum) && data.exclusiveMaximum) {
130
+ if (inputValue >= data.maximum) {
131
+ errorMessages.push(nls.localizeByDefault('Value must be strictly less than {0}.', data.maximum));
132
+ }
133
+ } else {
134
+ if (inputValue > data.maximum) {
135
+ errorMessages.push(nls.localizeByDefault('Value must be less than or equal to {0}.', data.maximum));
136
+ }
137
+ }
138
+ }
139
+ // Using JSON Schema before Draft 4 both exclusive and non-exclusive variants can be set
140
+ if (isNumber(data.exclusiveMinimum) && isFinite(data.exclusiveMinimum)) {
141
+ if (inputValue <= data.exclusiveMinimum) {
142
+ errorMessages.push(nls.localizeByDefault('Value must be strictly greater than {0}.', data.exclusiveMinimum));
143
+ }
144
+ }
145
+ if (isNumber(data.exclusiveMaximum) && isFinite(data.exclusiveMaximum)) {
146
+ if (inputValue >= data.exclusiveMaximum) {
147
+ errorMessages.push(nls.localizeByDefault('Value must be strictly less than {0}.', data.exclusiveMaximum));
148
+ }
149
+ }
150
+ if (isNumber(data.multipleOf) && data.multipleOf !== 0 && !Number.isInteger(inputValue / data.multipleOf)) {
151
+ errorMessages.push(nls.localizeByDefault('Value must be a multiple of {0}.', data.multipleOf));
152
+ }
117
153
 
118
154
  return {
119
155
  value: errorMessages.length ? NaN : inputValue,
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { PreferenceLeafNodeRenderer, PreferenceNodeRenderer } from './preference-node-renderer';
18
18
  import { injectable, interfaces } from '@theia/core/shared/inversify';
19
- import { JSONValue } from '@theia/core/shared/@phosphor/coreutils';
19
+ import { JSONValue } from '@theia/core/shared/@lumino/coreutils';
20
20
  import { PreferenceProvider } from '@theia/core/lib/browser/preferences/preference-provider';
21
21
  import { SelectComponent, SelectOption } from '@theia/core/lib/browser/widgets/select-component';
22
22
  import { Preference } from '../../util/preference-types';
@@ -275,9 +275,12 @@ export class PreferencesEditorWidget extends BaseWidget implements StatefulWidge
275
275
  }
276
276
  }
277
277
 
278
+ protected shouldUpdateModelSelection = true;
279
+
278
280
  protected setFirstVisibleChildID(id?: string): void {
279
281
  if (id && id !== this.firstVisibleChildID) {
280
282
  this.firstVisibleChildID = id;
283
+ if (!this.shouldUpdateModelSelection) { return; }
281
284
  let currentNode = this.model.getNode(id);
282
285
  let expansionAncestor;
283
286
  let selectionAncestor;
@@ -314,18 +317,26 @@ export class PreferencesEditorWidget extends BaseWidget implements StatefulWidge
314
317
  if (renderer?.visible) {
315
318
  // When filtered, treat the first visible child as the selected node, since it will be the one scrolled to.
316
319
  this.lastUserSelection = renderer.nodeId;
317
- renderer.node.scrollIntoView();
320
+ this.scrollWithoutModelUpdate(renderer.node);
318
321
  return;
319
322
  }
320
323
  }
321
324
  } else {
322
325
  const { id, collection } = this.analyzeIDAndGetRendererGroup(node.id);
323
326
  const renderer = collection.get(id);
324
- renderer?.node.scrollIntoView();
327
+ this.scrollWithoutModelUpdate(renderer?.node);
325
328
  }
326
329
  }
327
330
  }
328
331
 
332
+ /** Ensures that we don't set the model's selection while attempting to scroll in reaction to a model selection change. */
333
+ protected scrollWithoutModelUpdate(node?: HTMLElement): void {
334
+ if (!node) { return; }
335
+ this.shouldUpdateModelSelection = false;
336
+ node.scrollIntoView();
337
+ requestAnimationFrame(() => this.shouldUpdateModelSelection = true);
338
+ }
339
+
329
340
  protected analyzeIDAndGetRendererGroup(nodeID: string): { id: string, group: string, collection: Map<string, GeneralPreferenceNodeRenderer> } {
330
341
  const { id, group } = Preference.TreeNode.getGroupAndIdFromNodeId(nodeID);
331
342
  const collection = group === COMMONLY_USED_SECTION_PREFIX ? this.commonlyUsedRenderers : this.renderers;
@@ -15,7 +15,7 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
18
- import { TabBar, Widget, Title } from '@theia/core/shared/@phosphor/widgets';
18
+ import { TabBar, Widget, Title } from '@theia/core/shared/@lumino/widgets';
19
19
  import { PreferenceScope, Message, ContextMenuRenderer, LabelProvider, StatefulWidget, codicon } from '@theia/core/lib/browser';
20
20
  import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
21
21
  import URI from '@theia/core/lib/common/uri';
@@ -260,6 +260,7 @@ export class PreferencesScopeTabBar extends TabBar<Widget> implements StatefulWi
260
260
  this.contextMenuRenderer.render({
261
261
  menuPath: PreferenceMenus.FOLDER_SCOPE_MENU_PATH,
262
262
  anchor: { x: tabRect.left, y: tabRect.bottom },
263
+ context: folderTabNode,
263
264
  onHide: () => {
264
265
  setTimeout(() => toDisposeOnHide.dispose());
265
266
  if (source === 'click') { folderTabNode.blur(); }
@@ -36,6 +36,7 @@ import { PreferencesScopeTabBar } from './preference-scope-tabbar-widget';
36
36
  import { PreferencesSearchbarWidget } from './preference-searchbar-widget';
37
37
  import { PreferencesTreeWidget } from './preference-tree-widget';
38
38
  import { PreferencesWidget } from './preference-widget';
39
+ import { PreferenceNullInputRenderer, PreferenceNullRendererContribution } from './components/preference-null-input';
39
40
 
40
41
  export function bindPreferencesWidgets(bind: interfaces.Bind): void {
41
42
  bind(PreferenceTreeLabelProvider).toSelf().inSingletonScope();
@@ -58,6 +59,8 @@ export function bindPreferencesWidgets(bind: interfaces.Bind): void {
58
59
 
59
60
  bind(PreferenceStringInputRenderer).toSelf();
60
61
  bind(PreferenceNodeRendererContribution).to(PreferenceStringInputRendererContribution).inSingletonScope();
62
+ bind(PreferenceNullInputRenderer).toSelf();
63
+ bind(PreferenceNodeRendererContribution).to(PreferenceNullRendererContribution).inSingletonScope();
61
64
 
62
65
  bind(PreferenceBooleanInputRenderer).toSelf();
63
66
  bind(PreferenceNodeRendererContribution).to(PreferenceBooleanInputRendererContribution).inSingletonScope();