@lvce-editor/extension-search-view 7.21.0 → 7.23.0

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.
@@ -2908,2039 +2908,2060 @@ const getComponentState = uid => {
2908
2908
  return get(uid).newState;
2909
2909
  };
2910
2910
 
2911
- const Tab = 2;
2912
- const Enter = 3;
2913
- const Escape = 8;
2914
- const Space = 9;
2915
- const PageUp = 10;
2916
- const PageDown = 11;
2917
- const End = 255;
2918
- const Home = 12;
2919
- const UpArrow = 14;
2920
- const DownArrow = 16;
2911
+ const mergeClassNames = (...classNames) => {
2912
+ return classNames.filter(Boolean).join(' ');
2913
+ };
2921
2914
 
2922
- const CtrlCmd = 1 << 11 >>> 0;
2915
+ const text = data => {
2916
+ return {
2917
+ childCount: 0,
2918
+ text: data,
2919
+ type: Text
2920
+ };
2921
+ };
2923
2922
 
2924
- const FocusExtensions = 15;
2925
- const FocusExtensionsInput = 7000;
2923
+ new Set(Object.values(VirtualDomElements));
2926
2924
 
2927
- const getKeyBindings = () => {
2928
- return [{
2929
- command: 'Extensions.closeSuggest',
2930
- key: Escape,
2931
- when: FocusExtensionsInput
2932
- }, {
2933
- command: 'Extensions.acceptCompletion',
2934
- key: Enter,
2935
- when: FocusExtensionsInput
2936
- }, {
2937
- command: 'Extensions.acceptCompletion',
2938
- key: Tab,
2939
- when: FocusExtensionsInput
2940
- }, {
2941
- command: 'Extensions.selectPreviousCompletion',
2942
- key: UpArrow,
2943
- when: FocusExtensionsInput
2944
- }, {
2945
- command: 'Extensions.selectNextCompletion',
2946
- key: DownArrow,
2947
- when: FocusExtensionsInput
2948
- }, {
2949
- command: 'Extensions.openSuggest',
2950
- key: CtrlCmd | Space,
2951
- when: FocusExtensionsInput
2952
- }, {
2953
- command: 'Extensions.focusFirst',
2954
- key: Home,
2955
- when: FocusExtensions
2956
- }, {
2957
- command: 'Extensions.focusLast',
2958
- key: End,
2959
- when: FocusExtensions
2960
- }, {
2961
- command: 'Extensions.focusPreviousPage',
2962
- key: PageUp,
2963
- when: FocusExtensions
2964
- }, {
2965
- command: 'Extensions.focusNextPage',
2966
- key: PageDown,
2967
- when: FocusExtensions
2968
- }, {
2969
- command: 'Extensions.focusPrevious',
2970
- key: UpArrow,
2971
- when: FocusExtensions
2972
- }, {
2973
- command: 'Extensions.focusNext',
2974
- key: DownArrow,
2975
- when: FocusExtensions
2976
- }, {
2977
- command: 'Extensions.handleClickCurrentButKeepFocus',
2978
- key: Space,
2979
- when: FocusExtensions
2980
- }, {
2981
- command: 'Extensions.handleClickCurrent',
2982
- key: Enter,
2983
- when: FocusExtensions
2984
- }, {
2985
- command: 'Extensions.toggleSuggest',
2986
- key: CtrlCmd | Space,
2987
- when: FocusExtensions
2988
- }, {
2989
- command: 'Extensions.scrollDown',
2990
- key: CtrlCmd | DownArrow,
2991
- when: FocusExtensions
2992
- }];
2925
+ const SetText = 1;
2926
+ const Replace = 2;
2927
+ const SetAttribute = 3;
2928
+ const RemoveAttribute = 4;
2929
+ const Add = 6;
2930
+ const NavigateChild = 7;
2931
+ const NavigateParent = 8;
2932
+ const RemoveChild = 9;
2933
+ const NavigateSibling = 10;
2934
+ const SetReferenceNodeUid = 11;
2935
+
2936
+ const isKey = key => {
2937
+ return key !== 'type' && key !== 'childCount';
2993
2938
  };
2994
2939
 
2995
- const Separator = 1;
2996
- const None$1 = 0;
2997
- const SubMenu = 4;
2998
- const Disabled = 5;
2940
+ const getKeys = node => {
2941
+ const keys = Object.keys(node).filter(isKey);
2942
+ return keys;
2943
+ };
2999
2944
 
3000
- const nonEnableableStatuses = [Installing, NotInstalled, Uninstalling];
3001
- const getEnablementFlags = (disabled, status) => {
3002
- if (status && nonEnableableStatuses.includes(status)) {
2945
+ const arrayToTree = nodes => {
2946
+ const result = [];
2947
+ let i = 0;
2948
+ while (i < nodes.length) {
2949
+ const node = nodes[i];
2950
+ const {
2951
+ children,
2952
+ nodesConsumed
2953
+ } = getChildrenWithCount(nodes, i + 1, node.childCount || 0);
2954
+ result.push({
2955
+ node,
2956
+ children
2957
+ });
2958
+ i += 1 + nodesConsumed;
2959
+ }
2960
+ return result;
2961
+ };
2962
+ const getChildrenWithCount = (nodes, startIndex, childCount) => {
2963
+ if (childCount === 0) {
3003
2964
  return {
3004
- disable: Disabled,
3005
- enable: Disabled
2965
+ children: [],
2966
+ nodesConsumed: 0
3006
2967
  };
3007
2968
  }
3008
- const isDisabled = status === Disabled$1 || status === undefined && disabled;
2969
+ const children = [];
2970
+ let i = startIndex;
2971
+ let remaining = childCount;
2972
+ let totalConsumed = 0;
2973
+ while (remaining > 0 && i < nodes.length) {
2974
+ const node = nodes[i];
2975
+ const nodeChildCount = node.childCount || 0;
2976
+ const {
2977
+ children: nodeChildren,
2978
+ nodesConsumed
2979
+ } = getChildrenWithCount(nodes, i + 1, nodeChildCount);
2980
+ children.push({
2981
+ node,
2982
+ children: nodeChildren
2983
+ });
2984
+ const nodeSize = 1 + nodesConsumed;
2985
+ i += nodeSize;
2986
+ totalConsumed += nodeSize;
2987
+ remaining--;
2988
+ }
3009
2989
  return {
3010
- disable: isDisabled ? Disabled : None$1,
3011
- enable: isDisabled ? None$1 : Disabled
2990
+ children,
2991
+ nodesConsumed: totalConsumed
3012
2992
  };
3013
2993
  };
3014
- const getMenuEntriesList = (builtin, disabled = false, status) => {
3015
- const enablementFlags = getEnablementFlags(disabled, status);
3016
- return [{
3017
- command: 'Extensions.enable',
3018
- flags: enablementFlags.enable,
3019
- id: 'enable',
3020
- label: enable$2()
3021
- }, {
3022
- command: 'Extensions.enableWorkspace',
3023
- flags: enablementFlags.enable,
3024
- id: 'enableWorkspace',
3025
- label: enableWorkspace$1()
3026
- }, {
3027
- command: '',
3028
- flags: Separator,
3029
- id: 'separator1',
3030
- label: ''
3031
- }, {
3032
- command: 'Extensions.disable',
3033
- flags: enablementFlags.disable,
3034
- id: 'disable',
3035
- label: disable$2()
3036
- }, {
3037
- command: 'Extensions.disableWorkspace',
3038
- flags: enablementFlags.disable,
3039
- id: 'disableWorkspace',
3040
- label: disableWorkspace$1()
3041
- }, {
3042
- command: '',
3043
- flags: Separator,
3044
- id: 'separator2',
3045
- label: ''
3046
- }, {
3047
- command: 'Extensions.installAnotherVersion',
3048
- flags: Disabled,
3049
- id: 'installAnotherVersion',
3050
- label: installAnotherVersion$1()
3051
- }, {
3052
- command: 'Extensions.copyExtensionInfo',
3053
- flags: None$1,
3054
- id: 'copy',
3055
- label: copy()
3056
- }, {
3057
- command: 'Extensions.copyExtensionId',
3058
- flags: None$1,
3059
- id: 'copyExtensionId',
3060
- label: copyExtensionId$1()
3061
- }];
2994
+
2995
+ const compareNodes = (oldNode, newNode) => {
2996
+ // Check if node type changed - return null to signal incompatible nodes
2997
+ // (caller should handle this with a Replace operation)
2998
+ if (oldNode.type !== newNode.type) {
2999
+ return null;
3000
+ }
3001
+ const patches = [];
3002
+ // Handle reference nodes - special handling for uid changes
3003
+ if (oldNode.type === Reference && oldNode.uid !== newNode.uid) {
3004
+ patches.push({
3005
+ type: SetReferenceNodeUid,
3006
+ uid: newNode.uid
3007
+ });
3008
+ }
3009
+ // Handle text nodes
3010
+ if (oldNode.type === Text && newNode.type === Text) {
3011
+ if (oldNode.text !== newNode.text) {
3012
+ patches.push({
3013
+ type: SetText,
3014
+ value: newNode.text
3015
+ });
3016
+ }
3017
+ return patches;
3018
+ }
3019
+ // Compare attributes
3020
+ const oldKeys = getKeys(oldNode).filter(key => oldNode.type !== Reference || key !== 'uid');
3021
+ const newKeys = getKeys(newNode).filter(key => newNode.type !== Reference || key !== 'uid');
3022
+ // Check for attribute changes
3023
+ for (const key of newKeys) {
3024
+ if (oldNode[key] !== newNode[key]) {
3025
+ patches.push({
3026
+ type: SetAttribute,
3027
+ key,
3028
+ value: newNode[key]
3029
+ });
3030
+ }
3031
+ }
3032
+ // Check for removed attributes
3033
+ for (const key of oldKeys) {
3034
+ if (!Object.hasOwn(newNode, key)) {
3035
+ patches.push({
3036
+ type: RemoveAttribute,
3037
+ key
3038
+ });
3039
+ }
3040
+ }
3041
+ return patches;
3062
3042
  };
3063
3043
 
3064
- const getMenuEntriesFilter = () => {
3065
- return [{
3066
- command: 'Extensions.filterByFeatured',
3067
- flags: None$1,
3068
- id: 'filterByFeatured',
3069
- label: featured()
3070
- }, {
3071
- command: 'Extensions.filterByMcpServers',
3072
- flags: None$1,
3073
- id: 'filterByMcpServers',
3074
- label: mcpServers()
3075
- }, {
3076
- command: 'Extensions.filterByMostPopular',
3077
- flags: None$1,
3078
- id: 'filterByMostPopular',
3079
- label: mostPopular()
3080
- }, {
3081
- command: 'Extensions.filterByRecentlyPublished',
3082
- flags: None$1,
3083
- id: 'filterByRecentlyPublished',
3084
- label: recentlyPublished()
3085
- }, {
3086
- command: 'Extensions.filterByRecommended',
3087
- flags: None$1,
3088
- id: 'filterByRecommended',
3089
- label: recommended()
3090
- }, {
3091
- command: '',
3092
- flags: Separator,
3093
- id: 'separator1',
3094
- label: ''
3095
- }, {
3096
- command: 'SearchExtensions.filterByCategory',
3097
- flags: SubMenu,
3098
- id: 'filterByCategory',
3099
- label: category()
3100
- }, {
3101
- command: 'Extensions.filterByInstalled',
3102
- flags: None$1,
3103
- id: 'filterByInstalled',
3104
- label: installed()
3105
- }, {
3106
- command: 'Extensions.filterByUpdates',
3107
- flags: None$1,
3108
- id: 'filterByUpdates',
3109
- label: updates()
3110
- }, {
3111
- command: 'Extensions.filterByBuiltin',
3112
- flags: None$1,
3113
- id: 'filterByBuiltin',
3114
- label: builtIn()
3115
- }, {
3116
- command: 'Extensions.filterByLinked',
3117
- flags: None$1,
3118
- id: 'filterByLinked',
3119
- label: linked()
3120
- }, {
3121
- command: 'Extensions.filterByEnabled',
3122
- flags: None$1,
3123
- id: 'filterByEnabled',
3124
- label: enabled()
3125
- }, {
3126
- command: 'Extensions.filterByDisabled',
3127
- flags: None$1,
3128
- id: 'filterByDisabled',
3129
- label: disabled()
3130
- }, {
3131
- command: 'Extensions.filterByWorkspaceUnsupported',
3132
- flags: None$1,
3133
- id: 'filterByWorkspaceUnsupported',
3134
- label: workspaceUnsupported()
3135
- }, {
3136
- command: '',
3137
- flags: Separator,
3138
- id: 'separator2',
3139
- label: ''
3140
- }, {
3141
- command: 'SearchExtensions.filterBySortBy',
3142
- flags: SubMenu,
3143
- id: 'filterBySortBy',
3144
- label: sortBy()
3145
- }];
3044
+ const treeToArray = node => {
3045
+ const result = [];
3046
+ const stack = [node];
3047
+ while (stack.length > 0) {
3048
+ const current = stack.pop();
3049
+ result.push(current.node);
3050
+ for (let i = current.children.length - 1; i >= 0; i--) {
3051
+ stack.push(current.children[i]);
3052
+ }
3053
+ }
3054
+ return result;
3146
3055
  };
3147
3056
 
3148
- const getMenuEntries2 = (state, props) => {
3149
- const {
3150
- menuId
3151
- } = props;
3152
- switch (menuId) {
3153
- case ExtensionSearchFilter:
3154
- return getMenuEntriesFilter();
3155
- default:
3156
- return getMenuEntriesList(props.builtin, props.disabled, props.status);
3057
+ const navigateToChild = (patches, currentChildIndex, index) => {
3058
+ if (currentChildIndex === -1) {
3059
+ patches.push({
3060
+ type: NavigateChild,
3061
+ index
3062
+ });
3063
+ return index;
3064
+ }
3065
+ if (currentChildIndex !== index) {
3066
+ patches.push({
3067
+ type: NavigateSibling,
3068
+ index
3069
+ });
3157
3070
  }
3071
+ return index;
3158
3072
  };
3159
-
3160
- const getMenuIds = () => {
3161
- return [ManageExtension, ExtensionSearchFilter];
3073
+ const navigateToParent = (patches, currentChildIndex) => {
3074
+ if (currentChildIndex >= 0) {
3075
+ patches.push({
3076
+ type: NavigateParent
3077
+ });
3078
+ }
3079
+ return -1;
3162
3080
  };
3163
-
3164
- const handleBlur = state => {
3165
- return {
3166
- ...state,
3167
- focus: None$2,
3168
- suggestOpen: false
3169
- };
3081
+ const addTree = (newNode, patches) => {
3082
+ patches.push({
3083
+ type: Add,
3084
+ nodes: treeToArray(newNode)
3085
+ });
3170
3086
  };
3171
-
3172
- const getExtensionDetailUri = extensionId => {
3173
- return `extension-detail://${extensionId}`;
3087
+ const replaceTree = (newNode, patches) => {
3088
+ patches.push({
3089
+ type: Replace,
3090
+ nodes: treeToArray(newNode)
3091
+ });
3174
3092
  };
3175
-
3176
- const openUri = async uri => {
3177
- return openUri$1(uri);
3093
+ const diffExistingChild = (oldNode, newNode, patches, currentChildIndex, index) => {
3094
+ const nodePatches = compareNodes(oldNode.node, newNode.node);
3095
+ if (nodePatches === null) {
3096
+ const nextChildIndex = navigateToChild(patches, currentChildIndex, index);
3097
+ replaceTree(newNode, patches);
3098
+ return nextChildIndex;
3099
+ }
3100
+ const hasChildrenToCompare = oldNode.children.length > 0 || newNode.children.length > 0;
3101
+ if (nodePatches.length === 0 && !hasChildrenToCompare) {
3102
+ return currentChildIndex;
3103
+ }
3104
+ const nextChildIndex = navigateToChild(patches, currentChildIndex, index);
3105
+ if (nodePatches.length > 0) {
3106
+ patches.push(...nodePatches);
3107
+ }
3108
+ if (hasChildrenToCompare) {
3109
+ diffChildren(oldNode.children, newNode.children, patches);
3110
+ }
3111
+ return nextChildIndex;
3178
3112
  };
3179
-
3180
- const selectIndex = (state, index) => {
3181
- return {
3182
- ...state,
3183
- focusedIndex: index
3184
- };
3113
+ const diffRootNode = (oldNode, newNode, patches) => {
3114
+ const nodePatches = compareNodes(oldNode.node, newNode.node);
3115
+ if (nodePatches === null) {
3116
+ replaceTree(newNode, patches);
3117
+ return;
3118
+ }
3119
+ if (nodePatches.length > 0) {
3120
+ patches.push(...nodePatches);
3121
+ }
3122
+ if (oldNode.children.length > 0 || newNode.children.length > 0) {
3123
+ diffChildren(oldNode.children, newNode.children, patches);
3124
+ }
3185
3125
  };
3186
-
3187
- const handleClick = async (state, index) => {
3188
- const {
3189
- items,
3190
- minLineY
3191
- } = state;
3192
- const actualIndex = index + minLineY;
3193
- if (actualIndex < 0 || actualIndex >= items.length) {
3194
- return {
3195
- ...state,
3196
- focus: List$2,
3197
- focusedIndex: -1
3198
- };
3126
+ const diffChildren = (oldChildren, newChildren, patches) => {
3127
+ const maxLength = Math.max(oldChildren.length, newChildren.length);
3128
+ let currentChildIndex = -1;
3129
+ const indicesToRemove = [];
3130
+ for (let i = 0; i < maxLength; i++) {
3131
+ const oldNode = oldChildren[i];
3132
+ const newNode = newChildren[i];
3133
+ if (!oldNode && !newNode) {
3134
+ continue;
3135
+ }
3136
+ if (!oldNode) {
3137
+ currentChildIndex = navigateToParent(patches, currentChildIndex);
3138
+ addTree(newNode, patches);
3139
+ continue;
3140
+ }
3141
+ if (!newNode) {
3142
+ indicesToRemove.push(i);
3143
+ continue;
3144
+ }
3145
+ currentChildIndex = diffExistingChild(oldNode, newNode, patches, currentChildIndex, i);
3146
+ }
3147
+ navigateToParent(patches, currentChildIndex);
3148
+ for (let j = indicesToRemove.length - 1; j >= 0; j--) {
3149
+ patches.push({
3150
+ type: RemoveChild,
3151
+ index: indicesToRemove[j]
3152
+ });
3199
3153
  }
3200
- const extension = items[actualIndex];
3201
- const uri = getExtensionDetailUri(extension.id);
3202
- await openUri(uri);
3203
- const partialNewState = selectIndex(state, actualIndex);
3204
- const newState = {
3205
- ...partialNewState,
3206
- focus: List$2
3207
- };
3208
- return newState;
3209
3154
  };
3210
-
3211
- const mergeClassNames = (...classNames) => {
3212
- return classNames.filter(Boolean).join(' ');
3155
+ const diffTrees = (oldTree, newTree, patches, path) => {
3156
+ if (path.length === 0 && oldTree.length === 1 && newTree.length === 1) {
3157
+ diffRootNode(oldTree[0], newTree[0], patches);
3158
+ return;
3159
+ }
3160
+ diffChildren(oldTree, newTree, patches);
3213
3161
  };
3214
3162
 
3215
- const text = data => {
3216
- return {
3217
- childCount: 0,
3218
- text: data,
3219
- type: Text
3220
- };
3163
+ const removeTrailingNavigationPatches = patches => {
3164
+ while (patches.length > 0) {
3165
+ const patch = patches.at(-1);
3166
+ if (patch.type !== NavigateChild && patch.type !== NavigateParent && patch.type !== NavigateSibling) {
3167
+ break;
3168
+ }
3169
+ patches.pop();
3170
+ }
3171
+ return patches;
3221
3172
  };
3222
3173
 
3223
- new Set(Object.values(VirtualDomElements));
3174
+ const diffTree = (oldNodes, newNodes) => {
3175
+ // Step 1: Convert flat arrays to tree structures
3176
+ const oldTree = arrayToTree(oldNodes);
3177
+ const newTree = arrayToTree(newNodes);
3178
+ // Step 3: Compare the trees
3179
+ const patches = [];
3180
+ diffTrees(oldTree, newTree, patches, []);
3181
+ // Remove trailing navigation patches since they serve no purpose
3182
+ return removeTrailingNavigationPatches(patches);
3183
+ };
3224
3184
 
3225
- const SetText = 1;
3226
- const Replace = 2;
3227
- const SetAttribute = 3;
3228
- const RemoveAttribute = 4;
3229
- const Add = 6;
3230
- const NavigateChild = 7;
3231
- const NavigateParent = 8;
3232
- const RemoveChild = 9;
3233
- const NavigateSibling = 10;
3234
- const SetReferenceNodeUid = 11;
3235
-
3236
- const isKey = key => {
3237
- return key !== 'type' && key !== 'childCount';
3238
- };
3185
+ const Actions = 'Actions';
3186
+ const ExtensionActions = 'ExtensionActions';
3187
+ const ExtensionActionButton = 'ExtensionActionButton';
3188
+ const ExtensionActive = 'ExtensionActive';
3189
+ const ExtensionHeader = 'ExtensionHeader';
3190
+ const ExtensionListItem = 'ExtensionListItem';
3191
+ const ExtensionListItemActionInstall = 'ExtensionListItemActionInstall';
3192
+ const ExtensionListItemAuthorName = 'ExtensionListItemAuthorName';
3193
+ const ExtensionListItemDescription = 'ExtensionListItemDescription';
3194
+ const ExtensionListItemDetail = 'ExtensionListItemDetail';
3195
+ const ExtensionListItemDisabled = 'ExtensionListItemDisabled';
3196
+ const ExtensionListItemDownloadCount = 'ExtensionListItemDownloadCount';
3197
+ const ExtensionListItemFooter = 'ExtensionListItemFooter';
3198
+ const ExtensionListItemIcon = 'ExtensionListItemIcon';
3199
+ const ExtensionListItemLinkedIcon = 'ExtensionListItemLinkedIcon';
3200
+ const ExtensionListItemMetadata = 'ExtensionListItemMetadata';
3201
+ const ExtensionListItemName = 'ExtensionListItemName';
3202
+ const ExtensionListItemRating = 'ExtensionListItemRating';
3203
+ const ExtensionListItemStatistic = 'ExtensionListItemStatistic';
3204
+ const ExtensionSearchCompletionHighlight = 'ExtensionSearchCompletionHighlight';
3205
+ const ExtensionSearchCompletionItem = 'ExtensionSearchCompletionItem';
3206
+ const ExtensionSearchCompletionItemFocused = 'ExtensionSearchCompletionItemFocused';
3207
+ const ExtensionSearchCompletionWidget = 'ExtensionSearchCompletionWidget';
3208
+ const Extensions$1 = 'Extensions';
3209
+ const FocusOutline = 'FocusOutline';
3210
+ const IconButton = 'IconButton';
3211
+ const List$1 = 'List';
3212
+ const ListItems = 'ListItems';
3213
+ const MaskIcon = 'MaskIcon';
3214
+ const MultilineInputBox = 'MultilineInputBox';
3215
+ const NoExtensionsFoundMessage = 'NoExtensionsFoundMessage';
3216
+ const ScrollBar = 'ScrollBar';
3217
+ const ScrollBarSmall = 'ScrollBarSmall';
3218
+ const ScrollBarThumb = 'ScrollBarThumb';
3219
+ const ScrollBarThumbActive = 'ScrollBarThumbActive';
3220
+ const SearchField = 'SearchField';
3221
+ const SearchFieldButton = 'SearchFieldButton';
3222
+ const SearchFieldButtonDisabled = 'SearchFieldButtonDisabled';
3223
+ const SearchFieldButtons = 'SearchFieldButtons';
3224
+ const SearchFieldContainer = 'SearchFieldContainer';
3225
+ const Viewlet = 'Viewlet';
3239
3226
 
3240
- const getKeys = node => {
3241
- const keys = Object.keys(node).filter(isKey);
3242
- return keys;
3243
- };
3227
+ const ComboBox = 'combobox';
3228
+ const Image = 'img';
3229
+ const List = 'list';
3230
+ const ListBox = 'listbox';
3231
+ const ListItem = 'listitem';
3232
+ const None$1 = 'none';
3233
+ const Option = 'option';
3234
+ const ToolBar = 'toolbar';
3244
3235
 
3245
- const arrayToTree = nodes => {
3246
- const result = [];
3247
- let i = 0;
3248
- while (i < nodes.length) {
3249
- const node = nodes[i];
3250
- const {
3251
- children,
3252
- nodesConsumed
3253
- } = getChildrenWithCount(nodes, i + 1, node.childCount || 0);
3254
- result.push({
3255
- node,
3256
- children
3257
- });
3258
- i += 1 + nodesConsumed;
3259
- }
3260
- return result;
3261
- };
3262
- const getChildrenWithCount = (nodes, startIndex, childCount) => {
3263
- if (childCount === 0) {
3264
- return {
3265
- children: [],
3266
- nodesConsumed: 0
3267
- };
3236
+ const getCompletionLabelVirtualDom = (label, highlights) => {
3237
+ const dom = [];
3238
+ let position = 0;
3239
+ for (let i = 0; i < highlights.length; i += 2) {
3240
+ const start = highlights[i];
3241
+ const end = highlights[i + 1];
3242
+ if (position < start) {
3243
+ dom.push(text(label.slice(position, start)));
3244
+ }
3245
+ dom.push({
3246
+ childCount: 1,
3247
+ className: ExtensionSearchCompletionHighlight,
3248
+ name: label,
3249
+ type: Span
3250
+ }, text(label.slice(start, end)));
3251
+ position = end;
3268
3252
  }
3269
- const children = [];
3270
- let i = startIndex;
3271
- let remaining = childCount;
3272
- let totalConsumed = 0;
3273
- while (remaining > 0 && i < nodes.length) {
3274
- const node = nodes[i];
3275
- const nodeChildCount = node.childCount || 0;
3276
- const {
3277
- children: nodeChildren,
3278
- nodesConsumed
3279
- } = getChildrenWithCount(nodes, i + 1, nodeChildCount);
3280
- children.push({
3281
- node,
3282
- children: nodeChildren
3283
- });
3284
- const nodeSize = 1 + nodesConsumed;
3285
- i += nodeSize;
3286
- totalConsumed += nodeSize;
3287
- remaining--;
3253
+ if (position < label.length) {
3254
+ dom.push(text(label.slice(position)));
3288
3255
  }
3289
- return {
3290
- children,
3291
- nodesConsumed: totalConsumed
3292
- };
3256
+ return dom;
3293
3257
  };
3294
3258
 
3295
- const compareNodes = (oldNode, newNode) => {
3296
- // Check if node type changed - return null to signal incompatible nodes
3297
- // (caller should handle this with a Replace operation)
3298
- if (oldNode.type !== newNode.type) {
3299
- return null;
3300
- }
3301
- const patches = [];
3302
- // Handle reference nodes - special handling for uid changes
3303
- if (oldNode.type === Reference && oldNode.uid !== newNode.uid) {
3304
- patches.push({
3305
- type: SetReferenceNodeUid,
3306
- uid: newNode.uid
3307
- });
3308
- }
3309
- // Handle text nodes
3310
- if (oldNode.type === Text && newNode.type === Text) {
3311
- if (oldNode.text !== newNode.text) {
3312
- patches.push({
3313
- type: SetText,
3314
- value: newNode.text
3315
- });
3316
- }
3317
- return patches;
3318
- }
3319
- // Compare attributes
3320
- const oldKeys = getKeys(oldNode).filter(key => oldNode.type !== Reference || key !== 'uid');
3321
- const newKeys = getKeys(newNode).filter(key => newNode.type !== Reference || key !== 'uid');
3322
- // Check for attribute changes
3323
- for (const key of newKeys) {
3324
- if (oldNode[key] !== newNode[key]) {
3325
- patches.push({
3326
- type: SetAttribute,
3327
- key,
3328
- value: newNode[key]
3329
- });
3259
+ const getRootNodeCount = nodes => {
3260
+ let count = 0;
3261
+ const remainingChildCounts = [];
3262
+ for (const node of nodes) {
3263
+ while (remainingChildCounts.length > 0 && remainingChildCounts.at(-1) === 0) {
3264
+ remainingChildCounts.pop();
3330
3265
  }
3331
- }
3332
- // Check for removed attributes
3333
- for (const key of oldKeys) {
3334
- if (!Object.hasOwn(newNode, key)) {
3335
- patches.push({
3336
- type: RemoveAttribute,
3337
- key
3338
- });
3266
+ if (remainingChildCounts.length === 0) {
3267
+ count++;
3268
+ } else {
3269
+ const lastIndex = remainingChildCounts.length - 1;
3270
+ remainingChildCounts[lastIndex]--;
3339
3271
  }
3272
+ remainingChildCounts.push(node.childCount);
3340
3273
  }
3341
- return patches;
3274
+ return count;
3342
3275
  };
3343
-
3344
- const treeToArray = node => {
3345
- const result = [];
3346
- const stack = [node];
3347
- while (stack.length > 0) {
3348
- const current = stack.pop();
3349
- result.push(current.node);
3350
- for (let i = current.children.length - 1; i >= 0; i--) {
3351
- stack.push(current.children[i]);
3352
- }
3353
- }
3354
- return result;
3276
+ const getCompletionItemVirtualDom = (item, index, focusedIndex) => {
3277
+ const labelDom = getCompletionLabelVirtualDom(item.label, item.highlights);
3278
+ const focused = index === focusedIndex;
3279
+ return [{
3280
+ ariaSelected: focused,
3281
+ childCount: getRootNodeCount(labelDom),
3282
+ className: focused ? mergeClassNames(ExtensionSearchCompletionItem, ExtensionSearchCompletionItemFocused) : ExtensionSearchCompletionItem,
3283
+ id: `ExtensionSearchCompletion-${index}`,
3284
+ name: item.label,
3285
+ onPointerDown: HandlePointerDown,
3286
+ role: Option,
3287
+ type: Button$2
3288
+ }, ...labelDom];
3355
3289
  };
3356
3290
 
3357
- const navigateToChild = (patches, currentChildIndex, index) => {
3358
- if (currentChildIndex === -1) {
3359
- patches.push({
3360
- type: NavigateChild,
3361
- index
3362
- });
3363
- return index;
3364
- }
3365
- if (currentChildIndex !== index) {
3366
- patches.push({
3367
- type: NavigateSibling,
3368
- index
3369
- });
3370
- }
3371
- return index;
3291
+ const getCompletionWidgetVirtualDom = (items, focusedIndex) => {
3292
+ return [{
3293
+ ariaLabel: 'Extension search completions',
3294
+ childCount: items.length,
3295
+ className: ExtensionSearchCompletionWidget,
3296
+ id: 'ExtensionSearchCompletions',
3297
+ role: ListBox,
3298
+ type: Div
3299
+ }, ...items.flatMap((item, index) => getCompletionItemVirtualDom(item, index, focusedIndex))];
3372
3300
  };
3373
- const navigateToParent = (patches, currentChildIndex) => {
3374
- if (currentChildIndex >= 0) {
3375
- patches.push({
3376
- type: NavigateParent
3377
- });
3301
+
3302
+ const Focusable = 0;
3303
+
3304
+ const disabledClassName = mergeClassNames(SearchFieldButton, SearchFieldButtonDisabled);
3305
+ const getClassName = enabled => {
3306
+ if (enabled) {
3307
+ return SearchFieldButton;
3378
3308
  }
3379
- return -1;
3380
- };
3381
- const addTree = (newNode, patches) => {
3382
- patches.push({
3383
- type: Add,
3384
- nodes: treeToArray(newNode)
3385
- });
3386
- };
3387
- const replaceTree = (newNode, patches) => {
3388
- patches.push({
3389
- type: Replace,
3390
- nodes: treeToArray(newNode)
3391
- });
3309
+ return disabledClassName;
3392
3310
  };
3393
- const diffExistingChild = (oldNode, newNode, patches, currentChildIndex, index) => {
3394
- const nodePatches = compareNodes(oldNode.node, newNode.node);
3395
- if (nodePatches === null) {
3396
- const nextChildIndex = navigateToChild(patches, currentChildIndex, index);
3397
- replaceTree(newNode, patches);
3398
- return nextChildIndex;
3399
- }
3400
- const hasChildrenToCompare = oldNode.children.length > 0 || newNode.children.length > 0;
3401
- if (nodePatches.length === 0 && !hasChildrenToCompare) {
3402
- return currentChildIndex;
3403
- }
3404
- const nextChildIndex = navigateToChild(patches, currentChildIndex, index);
3405
- if (nodePatches.length > 0) {
3406
- patches.push(...nodePatches);
3407
- }
3408
- if (hasChildrenToCompare) {
3409
- diffChildren(oldNode.children, newNode.children, patches);
3410
- }
3411
- return nextChildIndex;
3311
+ const getSearchFieldButtonVirtualDom = button => {
3312
+ const {
3313
+ enabled,
3314
+ icon,
3315
+ onClick,
3316
+ title
3317
+ } = button;
3318
+ return [{
3319
+ childCount: 1,
3320
+ className: getClassName(enabled),
3321
+ onClick,
3322
+ tabIndex: Focusable,
3323
+ title,
3324
+ type: Button$2
3325
+ }, {
3326
+ childCount: 0,
3327
+ className: mergeClassNames(MaskIcon, icon),
3328
+ type: Div
3329
+ }];
3412
3330
  };
3413
- const diffRootNode = (oldNode, newNode, patches) => {
3414
- const nodePatches = compareNodes(oldNode.node, newNode.node);
3415
- if (nodePatches === null) {
3416
- replaceTree(newNode, patches);
3417
- return;
3418
- }
3419
- if (nodePatches.length > 0) {
3420
- patches.push(...nodePatches);
3421
- }
3422
- if (oldNode.children.length > 0 || newNode.children.length > 0) {
3423
- diffChildren(oldNode.children, newNode.children, patches);
3424
- }
3331
+
3332
+ const searchFieldNode = {
3333
+ childCount: 2,
3334
+ className: SearchField,
3335
+ role: None$1,
3336
+ type: Div
3425
3337
  };
3426
- const diffChildren = (oldChildren, newChildren, patches) => {
3427
- const maxLength = Math.max(oldChildren.length, newChildren.length);
3428
- let currentChildIndex = -1;
3429
- const indicesToRemove = [];
3430
- for (let i = 0; i < maxLength; i++) {
3431
- const oldNode = oldChildren[i];
3432
- const newNode = newChildren[i];
3433
- if (!oldNode && !newNode) {
3434
- continue;
3435
- }
3436
- if (!oldNode) {
3437
- currentChildIndex = navigateToParent(patches, currentChildIndex);
3438
- addTree(newNode, patches);
3439
- continue;
3440
- }
3441
- if (!newNode) {
3442
- indicesToRemove.push(i);
3443
- continue;
3444
- }
3445
- currentChildIndex = diffExistingChild(oldNode, newNode, patches, currentChildIndex, i);
3446
- }
3447
- navigateToParent(patches, currentChildIndex);
3448
- for (let j = indicesToRemove.length - 1; j >= 0; j--) {
3449
- patches.push({
3450
- type: RemoveChild,
3451
- index: indicesToRemove[j]
3338
+ const getSearchFieldVirtualDom = (name, placeholder, onInput, insideButtons, outsideButtons, onFocus = '', onBlur = '', inputProperties = {}) => {
3339
+ // TODO avoid mutation
3340
+ const dom = [searchFieldNode, {
3341
+ autocapitalize: 'off',
3342
+ autocomplete: 'off',
3343
+ autocorrect: 'off',
3344
+ childCount: 0,
3345
+ className: MultilineInputBox,
3346
+ inputType: 'search',
3347
+ name,
3348
+ ...(onBlur && {
3349
+ onBlur
3350
+ }),
3351
+ onFocus,
3352
+ onInput,
3353
+ placeholder,
3354
+ spellcheck: false,
3355
+ type: Input$1,
3356
+ ...inputProperties
3357
+ }, {
3358
+ childCount: insideButtons.length,
3359
+ className: SearchFieldButtons,
3360
+ type: Div
3361
+ }, ...insideButtons.flatMap(getSearchFieldButtonVirtualDom)];
3362
+ if (outsideButtons.length > 0) {
3363
+ dom.unshift({
3364
+ childCount: 1 + outsideButtons.length,
3365
+ className: SearchFieldContainer,
3366
+ role: None$1,
3367
+ type: Div
3452
3368
  });
3369
+ dom.push(...outsideButtons.flatMap(getSearchFieldButtonVirtualDom));
3453
3370
  }
3454
- };
3455
- const diffTrees = (oldTree, newTree, patches, path) => {
3456
- if (path.length === 0 && oldTree.length === 1 && newTree.length === 1) {
3457
- diffRootNode(oldTree[0], newTree[0], patches);
3458
- return;
3459
- }
3460
- diffChildren(oldTree, newTree, patches);
3371
+ return dom;
3461
3372
  };
3462
3373
 
3463
- const removeTrailingNavigationPatches = patches => {
3464
- while (patches.length > 0) {
3465
- const patch = patches.at(-1);
3466
- if (patch.type !== NavigateChild && patch.type !== NavigateParent && patch.type !== NavigateSibling) {
3467
- break;
3468
- }
3469
- patches.pop();
3374
+ const Extensions = 'extensions';
3375
+
3376
+ const getCompletionVirtualDom = (completionItems, completionFocusedIndex, suggestOpen) => {
3377
+ if (!suggestOpen) {
3378
+ return [];
3470
3379
  }
3471
- return patches;
3380
+ return getCompletionWidgetVirtualDom(completionItems, completionFocusedIndex);
3472
3381
  };
3473
-
3474
- const diffTree = (oldNodes, newNodes) => {
3475
- // Step 1: Convert flat arrays to tree structures
3476
- const oldTree = arrayToTree(oldNodes);
3477
- const newTree = arrayToTree(newNodes);
3478
- // Step 3: Compare the trees
3479
- const patches = [];
3480
- diffTrees(oldTree, newTree, patches, []);
3481
- // Remove trailing navigation patches since they serve no purpose
3482
- return removeTrailingNavigationPatches(patches);
3382
+ const getExtensionHeaderVirtualDom = (placeholder, actions, completionItems = [], completionFocusedIndex = 0, suggestOpen = false) => {
3383
+ const inputProperties = suggestOpen ? {
3384
+ ariaActivedescendant: `ExtensionSearchCompletion-${completionFocusedIndex}`,
3385
+ ariaAutoComplete: 'list',
3386
+ ariaControls: 'ExtensionSearchCompletions',
3387
+ ariaExpanded: true,
3388
+ role: ComboBox
3389
+ } : {
3390
+ ariaAutoComplete: 'list',
3391
+ ariaExpanded: false,
3392
+ role: ComboBox
3393
+ };
3394
+ const completionDom = getCompletionVirtualDom(completionItems, completionFocusedIndex, suggestOpen);
3395
+ return [{
3396
+ childCount: suggestOpen ? 2 : 1,
3397
+ className: ExtensionHeader,
3398
+ onContextMenu: HandleHeaderContextMenu,
3399
+ type: Div
3400
+ }, ...getSearchFieldVirtualDom(Extensions, placeholder, HandleExtensionsInput, actions, [], HandleInputFocus, HandleInputBlur, inputProperties), ...completionDom];
3483
3401
  };
3484
3402
 
3485
- const getListIndex = (eventX, eventY, x, y, deltaY, itemHeight, headerHeight) => {
3486
- const relativeDeltaY = deltaY % itemHeight;
3487
- const relativeY = eventY - y - headerHeight + relativeDeltaY;
3488
- const index = Math.floor(relativeY / itemHeight);
3489
- return index;
3490
- };
3403
+ const Extension = 'Extension';
3491
3404
 
3492
- const handleCompletionPointerDown = (state, label) => {
3493
- if (state.completionItems.every(item => item.label !== label)) {
3494
- return Promise.resolve(state);
3495
- }
3496
- return acceptCompletion(state, label);
3405
+ const install = {
3406
+ disabled: false,
3407
+ label: install$1(),
3408
+ onClick: HandleInstall
3497
3409
  };
3498
-
3499
- const handleClickAt = async (state, button, eventX, eventY, name = '') => {
3500
- if (name.startsWith('@')) {
3501
- return handleCompletionPointerDown(state, name);
3410
+ const installing = {
3411
+ disabled: true,
3412
+ label: installing$1(),
3413
+ onClick: HandleInstall
3414
+ };
3415
+ const enable = {
3416
+ disabled: false,
3417
+ label: enable$2(),
3418
+ onClick: HandleEnable
3419
+ };
3420
+ const disable = {
3421
+ disabled: false,
3422
+ label: disable$2(),
3423
+ onClick: HandleDisable
3424
+ };
3425
+ const uninstall = {
3426
+ disabled: false,
3427
+ label: uninstall$1(),
3428
+ onClick: HandleUninstall
3429
+ };
3430
+ const uninstalling = {
3431
+ disabled: true,
3432
+ label: uninstalling$1(),
3433
+ onClick: HandleUninstall
3434
+ };
3435
+ const getExtensionActions = (builtin, disabled, status) => {
3436
+ if (status === NotInstalled) {
3437
+ return [install];
3502
3438
  }
3503
- if (name) {
3504
- return state;
3439
+ if (status === Installing) {
3440
+ return [installing];
3505
3441
  }
3506
- if (button !== LeftClick) {
3507
- return state;
3442
+ if (status === Uninstalling) {
3443
+ return builtin ? [] : [uninstalling];
3508
3444
  }
3509
- const {
3510
- deltaY,
3511
- headerHeight,
3512
- itemHeight,
3513
- x,
3514
- y
3515
- } = state;
3516
- const index = getListIndex(eventX, eventY, x, y, deltaY, itemHeight, headerHeight);
3517
- return handleClick(state, index);
3445
+ const enableOrDisable = isExtensionDisabled(disabled, status) ? enable : disable;
3446
+ return builtin ? [enableOrDisable] : [enableOrDisable, uninstall];
3518
3447
  };
3519
3448
 
3520
- const handleClickCurrent = state => {
3521
- const {
3522
- focusedIndex
3523
- } = state;
3524
- return handleClick(state, focusedIndex);
3449
+ const className = mergeClassNames(ExtensionListItemActionInstall, ExtensionActionButton);
3450
+ const getExtensionActionVirtualDom = (action, id) => {
3451
+ return [{
3452
+ childCount: 1,
3453
+ className,
3454
+ disabled: action.disabled,
3455
+ name: id,
3456
+ onClick: action.onClick,
3457
+ type: Button$2
3458
+ }, text(action.label)];
3525
3459
  };
3526
3460
 
3527
- const handleClickCurrentButKeepFocus = state => {
3528
- const {
3529
- focusedIndex
3530
- } = state;
3531
- return handleClick(state, focusedIndex);
3461
+ const getExtensionActionsVirtualDom = (id, builtin, disabled, status) => {
3462
+ const actions = getExtensionActions(builtin, disabled, status);
3463
+ return [{
3464
+ childCount: actions.length,
3465
+ className: ExtensionActions,
3466
+ type: Div
3467
+ }, ...actions.flatMap(action => getExtensionActionVirtualDom(action, id))];
3532
3468
  };
3533
3469
 
3534
- const show2 = async (uid, menuId, x, y, args) => {
3535
- await showContextMenu2(uid, menuId, x, y, args);
3536
- };
3537
-
3538
- const handleClickFilter = async state => {
3539
- const {
3540
- headerHeight,
3541
- uid,
3542
- width,
3543
- x,
3544
- y
3545
- } = state;
3546
- const menuX = x + width + 60;
3547
- const menuHeight = 370;
3548
- const menuY = y + headerHeight + menuHeight;
3549
- await show2(uid, ExtensionSearchFilter, menuX, menuY, {
3550
- menuId: ExtensionSearchFilter
3551
- });
3552
- return state;
3470
+ const getExtensionListItemClassName = (focused, disabled) => {
3471
+ return mergeClassNames(ExtensionListItem, focused ? ExtensionActive : '', disabled ? ExtensionListItemDisabled : '');
3553
3472
  };
3554
3473
 
3555
- const handleContextMenu = async (state, button, eventX, eventY) => {
3556
- // TODO use focused index when when context menu button is -1 (keyboard)
3557
- const {
3558
- deltaY,
3559
- headerHeight,
3560
- itemHeight,
3561
- items,
3562
- minLineY,
3563
- uid,
3564
- x,
3565
- y
3566
- } = state;
3567
- const visibleIndex = getListIndex(eventX, eventY, x, y, deltaY, itemHeight, headerHeight);
3568
- const index = visibleIndex + minLineY;
3569
- if (index < 0 || index >= items.length) {
3570
- return state;
3571
- }
3572
- const item = items[index];
3573
- await show2(uid, ManageExtension, eventX, eventY, {
3574
- builtin: item.builtin === true,
3575
- disabled: item.disabled === true,
3576
- menuId: ManageExtension,
3577
- status: item.status
3578
- });
3474
+ const getExtensionListItemFooter = hasStatistics => {
3579
3475
  return {
3580
- ...state,
3581
- focusedIndex: index
3476
+ childCount: hasStatistics ? 3 : 2,
3477
+ className: ExtensionListItemFooter,
3478
+ type: Div
3582
3479
  };
3583
3480
  };
3584
3481
 
3585
- const handleDisableWorkspace = (state, id) => {
3586
- return state;
3482
+ const getExtensionListItemId = focused => {
3483
+ if (focused) {
3484
+ return `ExtensionActive`;
3485
+ }
3486
+ return undefined;
3587
3487
  };
3588
3488
 
3589
- const handleEnableWorkspace = (state, id) => {
3590
- return state;
3489
+ const extensionListItemMetadataNode = {
3490
+ childCount: 2,
3491
+ className: ExtensionListItemMetadata,
3492
+ type: Div
3493
+ };
3494
+ const getStatisticVirtualDom = (label, value, className) => {
3495
+ const accessibleLabel = `${label}: ${value}`;
3496
+ return [{
3497
+ ariaLabel: accessibleLabel,
3498
+ childCount: 1,
3499
+ className: mergeClassNames(ExtensionListItemStatistic, className),
3500
+ title: accessibleLabel,
3501
+ type: Span
3502
+ }, text(value)];
3503
+ };
3504
+ const getExtensionStatisticsVirtualDom = (downloadCount, rating$1) => {
3505
+ return [extensionListItemMetadataNode, ...getStatisticVirtualDom(downloads(), downloadCount, ExtensionListItemDownloadCount), ...getStatisticVirtualDom(rating(), rating$1, ExtensionListItemRating)];
3591
3506
  };
3592
3507
 
3593
- const Web = 1;
3594
- const Electron = 2;
3595
- const Remote = 3;
3596
-
3597
- const getAllExtensions$1 = async (assetDir, platform) => {
3598
- try {
3599
- return await invoke$3('Extensions.getAllExtensions', assetDir, platform);
3600
- } catch (error) {
3601
- if (platform === Web) {
3602
- return [];
3603
- }
3604
- throw error;
3508
+ const getExtensionListItemStatisticsVirtualDom = (hasStatistics, downloadCount, rating) => {
3509
+ if (!hasStatistics) {
3510
+ return [];
3605
3511
  }
3512
+ return getExtensionStatisticsVirtualDom(downloadCount, rating);
3606
3513
  };
3607
3514
 
3608
- const getAllExtensions = (assetDir, platform) => {
3609
- return getAllExtensions$1(assetDir, platform);
3515
+ const getListItemDetail = linked => {
3516
+ return {
3517
+ childCount: linked ? 4 : 3,
3518
+ className: ExtensionListItemDetail,
3519
+ type: Div
3520
+ };
3610
3521
  };
3611
-
3612
- const getBuiltin = extension => {
3613
- if (extension === null || typeof extension !== 'object') {
3614
- return false;
3522
+ const listItemName = {
3523
+ childCount: 1,
3524
+ className: ExtensionListItemName,
3525
+ type: Div
3526
+ };
3527
+ const listItemDescription = {
3528
+ childCount: 1,
3529
+ className: ExtensionListItemDescription,
3530
+ type: Div
3531
+ };
3532
+ const listItemAuthorName = {
3533
+ childCount: 1,
3534
+ className: ExtensionListItemAuthorName,
3535
+ type: Div
3536
+ };
3537
+ const getLinkedIconVirtualDom = linked$1 => {
3538
+ if (!linked$1) {
3539
+ return [];
3615
3540
  }
3541
+ const label = linked();
3542
+ return [{
3543
+ ariaLabel: label,
3544
+ childCount: 0,
3545
+ className: mergeClassNames(MaskIcon, 'MaskIconLinkExternal', ExtensionListItemLinkedIcon),
3546
+ role: Image,
3547
+ title: 'Extension is linked',
3548
+ type: Div
3549
+ }];
3550
+ };
3551
+ const getExtensionListItemVirtualDom = extension => {
3616
3552
  const {
3617
- builtin,
3553
+ builtin = false,
3554
+ description,
3555
+ disabled = false,
3556
+ downloadCount = 'n/a',
3557
+ focused,
3558
+ icon,
3618
3559
  id,
3619
- isBuiltin
3560
+ linked = false,
3561
+ name,
3562
+ posInSet,
3563
+ publisher,
3564
+ rating = 'n/a',
3565
+ setSize,
3566
+ status
3620
3567
  } = extension;
3621
- return isBuiltin === true || builtin === true || typeof id === 'string' && id.startsWith('builtin.');
3568
+ const actionsDom = getExtensionActionsVirtualDom(id, builtin, disabled, status);
3569
+ const hasStatistics = !builtin;
3570
+ const dom = [{
3571
+ ariaPosInSet: posInSet,
3572
+ ariaRoleDescription: Extension,
3573
+ ariaSetSize: setSize,
3574
+ childCount: 2,
3575
+ className: getExtensionListItemClassName(focused, disabled),
3576
+ id: getExtensionListItemId(focused),
3577
+ role: ListItem,
3578
+ type: Div
3579
+ }, {
3580
+ childCount: 0,
3581
+ className: ExtensionListItemIcon,
3582
+ role: None$1,
3583
+ src: icon,
3584
+ type: Img
3585
+ }, getListItemDetail(linked), listItemName, text(name), listItemDescription, text(description), getExtensionListItemFooter(hasStatistics), listItemAuthorName, text(publisher), ...getExtensionListItemStatisticsVirtualDom(hasStatistics, downloadCount, rating), ...actionsDom, ...getLinkedIconVirtualDom(linked)];
3586
+ return dom;
3622
3587
  };
3623
3588
 
3624
- const isString = item => {
3625
- return typeof item === 'string';
3589
+ const getListClassName = focusOutline => {
3590
+ const className = focusOutline ? mergeClassNames(ListItems, FocusOutline) : ListItems;
3591
+ return className;
3626
3592
  };
3627
-
3628
- const getCategories = extension => {
3629
- if (extension === null || typeof extension !== 'object' || !('categories' in extension) || !Array.isArray(extension.categories)) {
3630
- return [];
3631
- }
3632
- return extension.categories.filter(isString);
3593
+ const getExtensionsListVirtualDom = (visibleExtensions, focusOutline) => {
3594
+ const dom = [{
3595
+ ariaLabel: extensions(),
3596
+ childCount: visibleExtensions.length,
3597
+ className: getListClassName(focusOutline),
3598
+ onBlur: HandleBlur,
3599
+ onContextmenu: HandleContextMenu,
3600
+ onContextMenu: HandleContextMenu,
3601
+ onFocus: HandleFocus,
3602
+ onPointerDown: HandlePointerDown,
3603
+ onTouchEnd: HandleTouchEnd,
3604
+ onTouchMove: HandleTouchMove,
3605
+ onTouchStart: HandleTouchStart,
3606
+ onWheel: HandleWheel,
3607
+ role: List,
3608
+ tabIndex: Focusable,
3609
+ type: Div
3610
+ }, ...visibleExtensions.flatMap(getExtensionListItemVirtualDom)];
3611
+ return dom;
3633
3612
  };
3634
3613
 
3635
- const getDescription = extension => {
3636
- if (extension === null || typeof extension !== 'object' || !('description' in extension) || typeof extension.description !== 'string' || !extension.description) {
3637
- return 'n/a';
3638
- }
3639
- return extension.description;
3614
+ const getExtensionsVirtualDom = (visibleExtensions, focusOutline) => {
3615
+ const dom = getExtensionsListVirtualDom(visibleExtensions, focusOutline);
3616
+ // TODO
3617
+ return dom;
3640
3618
  };
3641
3619
 
3642
- const getDisabled = extension => {
3643
- return extension !== null && typeof extension === 'object' && 'disabled' in extension && extension.disabled === true;
3620
+ const noExtensionsFoundNode = {
3621
+ childCount: 1,
3622
+ className: NoExtensionsFoundMessage,
3623
+ type: Div
3624
+ };
3625
+ const getNoExtensionsFoundVirtualDom = message => {
3626
+ return [noExtensionsFoundNode, text(message)];
3644
3627
  };
3645
3628
 
3646
- const getDownloadCountValue = extension => {
3647
- if (extension === null || typeof extension !== 'object') {
3648
- return undefined;
3649
- }
3650
- const marketplace = 'marketplace' in extension && extension.marketplace !== null && typeof extension.marketplace === 'object' ? extension.marketplace : {};
3651
- const packageJson = 'packageJSON' in extension && extension.packageJSON !== null && typeof extension.packageJSON === 'object' ? extension.packageJSON : {};
3652
- return ('downloadCount' in extension ? extension.downloadCount : undefined) ?? ('downloads' in extension ? extension.downloads : undefined) ?? ('downloadCount' in marketplace ? marketplace.downloadCount : undefined) ?? ('downloads' in marketplace ? marketplace.downloads : undefined) ?? ('downloadCount' in packageJson ? packageJson.downloadCount : undefined) ?? ('downloads' in packageJson ? packageJson.downloads : undefined);
3629
+ const scrollBarNode = {
3630
+ childCount: 1,
3631
+ className: mergeClassNames(ScrollBar, ScrollBarSmall),
3632
+ onPointerDown: HandleScrollBarPointerDown,
3633
+ // TODO support pointercapture event
3634
+ type: Div
3653
3635
  };
3654
- const getDownloadCount = extension => {
3655
- const downloadCount = getDownloadCountValue(extension);
3656
- if (typeof downloadCount !== 'number') {
3657
- return 'n/a';
3636
+ const scrollBarThumbNode = {
3637
+ childCount: 0,
3638
+ className: ScrollBarThumb,
3639
+ type: Div
3640
+ };
3641
+ const getScrollBarVirtualDom = (scrollBarHeight, scrollBarTop) => {
3642
+ const shouldShowScrollbar = scrollBarHeight > 0;
3643
+ if (!shouldShowScrollbar) {
3644
+ return [];
3658
3645
  }
3659
- return downloadCount.toLocaleString();
3646
+ return [scrollBarNode, scrollBarThumbNode];
3660
3647
  };
3661
3648
 
3662
- const getRemoteUrl = (extension, platform, assetDir) => {
3663
- if (extension === null || typeof extension !== 'object') {
3664
- return '';
3665
- }
3666
- const builtin = 'builtin' in extension && extension.builtin === true;
3667
- const icon = 'icon' in extension && typeof extension.icon === 'string' ? extension.icon : '';
3668
- const id = 'id' in extension && typeof extension.id === 'string' ? extension.id : '';
3669
- const path = 'path' in extension && typeof extension.path === 'string' ? extension.path : '';
3670
- if (platform === Remote || platform === Electron) {
3671
- if (builtin) {
3672
- return `${assetDir}/extensions/${id}/${icon}`;
3673
- }
3674
- return `/remote/${path}/${icon}`; // TODO support windows paths
3675
- }
3676
- if (platform === Web) {
3677
- return `${path}/${icon}`;
3678
- }
3679
- return '';
3649
+ const getVisibleItem = (item, setSize, itemHeight, minLineY, relative, i, focusedIndex) => {
3650
+ // TODO use normal parameters
3651
+ const {
3652
+ builtin,
3653
+ description,
3654
+ disabled,
3655
+ downloadCount,
3656
+ icon,
3657
+ id,
3658
+ linked,
3659
+ name,
3660
+ publisher,
3661
+ rating,
3662
+ status
3663
+ } = item;
3664
+ return {
3665
+ builtin,
3666
+ description,
3667
+ disabled,
3668
+ downloadCount,
3669
+ focused: i === focusedIndex,
3670
+ icon,
3671
+ id,
3672
+ index: i,
3673
+ linked,
3674
+ name,
3675
+ posInSet: i + 1,
3676
+ publisher,
3677
+ rating,
3678
+ setSize,
3679
+ status,
3680
+ top: (i - minLineY) * itemHeight - relative
3681
+ };
3680
3682
  };
3681
3683
 
3682
- const getExtensionDefaultIcon = assetDir => {
3683
- return `${assetDir}/icons/extensionDefaultIcon.png`;
3684
- };
3685
- const getExtensionLanguageBasicsIcon = assetDir => {
3686
- return `${assetDir}/icons/language-icon.svg`;
3687
- };
3688
- const getExtensionThemeIcon = assetDir => {
3689
- return `${assetDir}/icons/theme-icon.png`;
3684
+ const getVisible = state => {
3685
+ const {
3686
+ deltaY,
3687
+ focusedIndex,
3688
+ itemHeight,
3689
+ items,
3690
+ maxLineY,
3691
+ minLineY
3692
+ } = state;
3693
+ const setSize = items.length;
3694
+ const visible = [];
3695
+ const relative = deltaY % itemHeight;
3696
+ for (let i = minLineY; i < maxLineY; i++) {
3697
+ const item = items[i];
3698
+ visible.push(getVisibleItem(item, setSize, itemHeight, minLineY, relative, i, focusedIndex));
3699
+ }
3700
+ return visible;
3690
3701
  };
3691
3702
 
3692
- const isLanguageBasicsExtension = extension => {
3693
- return 'name' in extension && typeof extension.name === 'string' && extension.name.startsWith('Language Basics');
3703
+ const contentNode = {
3704
+ childCount: 2,
3705
+ className: mergeClassNames(Viewlet, List$1),
3706
+ type: Div
3694
3707
  };
3695
- const isThemeExtension = extension => {
3696
- return 'name' in extension && typeof extension.name === 'string' && extension.name.endsWith(' Theme');
3708
+ const extensionsNode = {
3709
+ ariaBusy: false,
3710
+ ariaLive: 'polite',
3711
+ childCount: 2,
3712
+ className: mergeClassNames(Viewlet, Extensions$1),
3713
+ role: None$3,
3714
+ type: Div
3697
3715
  };
3698
- const getExtensionIcon = (extension, platform, assetDir) => {
3699
- if (extension === null || typeof extension !== 'object') {
3700
- return getExtensionDefaultIcon(assetDir);
3701
- }
3702
- const hasIcon = 'icon' in extension && typeof extension.icon === 'string' && extension.icon;
3703
- const hasPath = 'path' in extension && typeof extension.path === 'string' && extension.path;
3704
- if (!hasPath || !hasIcon) {
3705
- if (isLanguageBasicsExtension(extension)) {
3706
- return getExtensionLanguageBasicsIcon(assetDir);
3707
- }
3708
- if (isThemeExtension(extension)) {
3709
- return getExtensionThemeIcon(assetDir);
3710
- }
3711
- return getExtensionDefaultIcon(assetDir);
3716
+ const getContentVirtualDom = (visibleExtensions, message, scrollBarHeight, scrollBarY, focusOutline) => {
3717
+ if (message) {
3718
+ return getNoExtensionsFoundVirtualDom(message);
3712
3719
  }
3713
- return getRemoteUrl(extension, platform, assetDir);
3720
+ return [contentNode, ...getExtensionsVirtualDom(visibleExtensions, focusOutline), ...getScrollBarVirtualDom(scrollBarHeight)];
3714
3721
  };
3715
-
3716
- const getIcon = (extension, platform, assetDir) => {
3717
- return getExtensionIcon(extension, platform, assetDir);
3722
+ const getExtensionsViewVirtualDom = state => {
3723
+ const visibleExtensions = getVisible(state);
3724
+ const {
3725
+ completionFocusedIndex,
3726
+ completionItems,
3727
+ focus,
3728
+ focusedIndex,
3729
+ inputActions,
3730
+ message,
3731
+ placeholder,
3732
+ scrollBarHeight,
3733
+ scrollBarY,
3734
+ suggestOpen
3735
+ } = state;
3736
+ const focusOutline = focusedIndex === -1 && focus === List$2;
3737
+ return [extensionsNode, ...getExtensionHeaderVirtualDom(placeholder, inputActions, completionItems, completionFocusedIndex, suggestOpen), ...getContentVirtualDom(visibleExtensions, message, scrollBarHeight, scrollBarY, focusOutline)];
3718
3738
  };
3719
3739
 
3720
- const getId = extension => {
3721
- if (extension === null || typeof extension !== 'object' || !('id' in extension) || typeof extension.id !== 'string' || !extension.id) {
3722
- return 'n/a';
3740
+ const renderItems2 = newState => {
3741
+ const {
3742
+ initial,
3743
+ uid
3744
+ } = newState;
3745
+ if (initial) {
3746
+ return [SetDom2, uid, []];
3723
3747
  }
3724
- return extension.id;
3748
+ const dom = getExtensionsViewVirtualDom(newState);
3749
+ return [SetDom2, uid, dom];
3725
3750
  };
3726
3751
 
3727
- const getLinked = extension => {
3728
- if (extension === null || typeof extension !== 'object') {
3729
- return false;
3730
- }
3731
- if ('linked' in extension && extension.linked === true) {
3732
- return true;
3733
- }
3734
- return 'symlink' in extension && typeof extension.symlink === 'string' && extension.symlink.length > 0;
3752
+ const getComponentDom = uid => {
3753
+ const state = getComponentState(uid);
3754
+ return renderItems2(state)[2];
3735
3755
  };
3736
3756
 
3737
- const getName = extension => {
3738
- if (extension === null || typeof extension !== 'object') {
3739
- return 'n/a';
3740
- }
3741
- if ('name' in extension && typeof extension.name === 'string' && extension.name) {
3742
- return extension.name;
3743
- }
3744
- if ('id' in extension && typeof extension.id === 'string' && extension.id) {
3745
- return extension.id;
3746
- }
3747
- return 'n/a';
3748
- };
3757
+ const Tab = 2;
3758
+ const Enter = 3;
3759
+ const Escape = 8;
3760
+ const Space = 9;
3761
+ const PageUp = 10;
3762
+ const PageDown = 11;
3763
+ const End = 255;
3764
+ const Home = 12;
3765
+ const UpArrow = 14;
3766
+ const DownArrow = 16;
3749
3767
 
3750
- const RE_PUBLISHER = /^[a-z\d-]+/;
3751
- const getPublisher = extension => {
3752
- if (extension === null || typeof extension !== 'object' || !('id' in extension) || typeof extension.id !== 'string') {
3753
- return 'n/a';
3754
- }
3755
- const match = extension.id.match(RE_PUBLISHER);
3756
- if (!match) {
3757
- return 'n/a';
3758
- }
3759
- return match[0];
3760
- };
3768
+ const CtrlCmd = 1 << 11 >>> 0;
3761
3769
 
3762
- const getRatingValue = extension => {
3763
- if (extension === null || typeof extension !== 'object') {
3764
- return undefined;
3765
- }
3766
- const marketplace = 'marketplace' in extension && extension.marketplace !== null && typeof extension.marketplace === 'object' ? extension.marketplace : {};
3767
- const packageJson = 'packageJSON' in extension && extension.packageJSON !== null && typeof extension.packageJSON === 'object' ? extension.packageJSON : {};
3768
- return ('rating' in extension ? extension.rating : undefined) ?? ('averageRating' in extension ? extension.averageRating : undefined) ?? ('rating' in marketplace ? marketplace.rating : undefined) ?? ('averageRating' in marketplace ? marketplace.averageRating : undefined) ?? ('rating' in packageJson ? packageJson.rating : undefined) ?? ('averageRating' in packageJson ? packageJson.averageRating : undefined);
3769
- };
3770
- const getRating = extension => {
3771
- const rating = getRatingValue(extension);
3772
- if (typeof rating !== 'number') {
3773
- return 'n/a';
3774
- }
3775
- return rating.toFixed(1);
3776
- };
3770
+ const FocusExtensions = 15;
3771
+ const FocusExtensionsInput = 7000;
3777
3772
 
3778
- const getSize = extension => {
3779
- if (extension === null || typeof extension !== 'object' || !('size' in extension) || extension.size === 0 || typeof extension.size !== 'number') {
3780
- return 0;
3781
- }
3782
- return extension.size;
3773
+ const getKeyBindings = () => {
3774
+ return [{
3775
+ command: 'Extensions.closeSuggest',
3776
+ key: Escape,
3777
+ when: FocusExtensionsInput
3778
+ }, {
3779
+ command: 'Extensions.acceptCompletion',
3780
+ key: Enter,
3781
+ when: FocusExtensionsInput
3782
+ }, {
3783
+ command: 'Extensions.acceptCompletion',
3784
+ key: Tab,
3785
+ when: FocusExtensionsInput
3786
+ }, {
3787
+ command: 'Extensions.selectPreviousCompletion',
3788
+ key: UpArrow,
3789
+ when: FocusExtensionsInput
3790
+ }, {
3791
+ command: 'Extensions.selectNextCompletion',
3792
+ key: DownArrow,
3793
+ when: FocusExtensionsInput
3794
+ }, {
3795
+ command: 'Extensions.openSuggest',
3796
+ key: CtrlCmd | Space,
3797
+ when: FocusExtensionsInput
3798
+ }, {
3799
+ command: 'Extensions.focusFirst',
3800
+ key: Home,
3801
+ when: FocusExtensions
3802
+ }, {
3803
+ command: 'Extensions.focusLast',
3804
+ key: End,
3805
+ when: FocusExtensions
3806
+ }, {
3807
+ command: 'Extensions.focusPreviousPage',
3808
+ key: PageUp,
3809
+ when: FocusExtensions
3810
+ }, {
3811
+ command: 'Extensions.focusNextPage',
3812
+ key: PageDown,
3813
+ when: FocusExtensions
3814
+ }, {
3815
+ command: 'Extensions.focusPrevious',
3816
+ key: UpArrow,
3817
+ when: FocusExtensions
3818
+ }, {
3819
+ command: 'Extensions.focusNext',
3820
+ key: DownArrow,
3821
+ when: FocusExtensions
3822
+ }, {
3823
+ command: 'Extensions.handleClickCurrentButKeepFocus',
3824
+ key: Space,
3825
+ when: FocusExtensions
3826
+ }, {
3827
+ command: 'Extensions.handleClickCurrent',
3828
+ key: Enter,
3829
+ when: FocusExtensions
3830
+ }, {
3831
+ command: 'Extensions.toggleSuggest',
3832
+ key: CtrlCmd | Space,
3833
+ when: FocusExtensions
3834
+ }, {
3835
+ command: 'Extensions.scrollDown',
3836
+ key: CtrlCmd | DownArrow,
3837
+ when: FocusExtensions
3838
+ }];
3783
3839
  };
3784
3840
 
3785
- const getStatus = extension => {
3786
- if (extension === null || typeof extension !== 'object' || !('status' in extension)) {
3787
- return undefined;
3788
- }
3789
- return typeof extension.status === 'string' ? extension.status : undefined;
3790
- };
3791
-
3792
- const getUpdatedDate = extension => {
3793
- if (extension === null || typeof extension !== 'object' || !('updatedDate' in extension) || !extension.updatedDate || typeof extension.updatedDate !== 'number') {
3794
- return 0;
3795
- }
3796
- return extension.updatedDate;
3797
- };
3798
-
3799
- const normalizeExtension = (extension, platform, assetDir) => {
3800
- return {
3801
- builtin: getBuiltin(extension),
3802
- categories: getCategories(extension),
3803
- description: getDescription(extension),
3804
- disabled: getDisabled(extension),
3805
- downloadCount: getDownloadCount(extension),
3806
- icon: getIcon(extension, platform, assetDir),
3807
- id: getId(extension),
3808
- linked: getLinked(extension),
3809
- name: getName(extension),
3810
- publisher: getPublisher(extension),
3811
- rating: getRating(extension),
3812
- size: getSize(extension),
3813
- status: getStatus(extension),
3814
- updatedDate: getUpdatedDate(extension),
3815
- uri: ''
3816
- };
3817
- };
3818
-
3819
- const normalizeExtensions = (extensions, platform, assetDir) => {
3820
- return Array.from(extensions, extension => normalizeExtension(extension, platform, assetDir));
3821
- };
3822
-
3823
- const handleExtensionsChanged = async state => {
3824
- const {
3825
- assetDir,
3826
- platform
3827
- } = state;
3828
- const allExtensions = await getAllExtensions(assetDir, platform);
3829
- const normalized = normalizeExtensions(allExtensions, platform, assetDir);
3830
- return handleChange({
3831
- ...state,
3832
- allExtensions: normalized
3833
- }, {});
3834
- };
3835
-
3836
- const handleFocus = async state => {
3837
- return {
3838
- ...state,
3839
- focus: List$2
3840
- };
3841
- };
3842
-
3843
- const handleHeaderContextMenu = async state => {
3844
- return state;
3845
- };
3846
-
3847
- const handleInputFocus = state => {
3848
- return {
3849
- ...state,
3850
- focus: Input
3851
- };
3852
- };
3853
-
3854
- const handleInstall = async (state, id) => {
3855
- await installExtension(id);
3856
- return setExtensionStatus(state, id, Enabled);
3857
- };
3858
-
3859
- const state = {
3860
- connected: false
3861
- };
3862
- const isConnected = () => {
3863
- const {
3864
- connected
3865
- } = state;
3866
- return connected;
3867
- };
3868
- const invoke = (method, ...params) => {
3869
- return invoke$2(method, ...params);
3870
- };
3871
- const set = rpc => {
3872
- set$3(rpc);
3873
- state.connected = true;
3874
- };
3875
-
3876
- const handleMessagePort = async (port, viewletCommandMap, setAsRendererProcess = true) => {
3877
- const executeViewletCommand = async (uid, command, ...args) => {
3878
- const fn = viewletCommandMap[`SearchExtensions.${command}`];
3879
- if (typeof fn !== 'function') {
3880
- throw new TypeError(`Viewlet command not found: ${command}`);
3881
- }
3882
- await fn(uid, ...args);
3883
- await invoke$1('Viewlet.requestRender', uid);
3884
- };
3885
- const rpc = await create$7({
3886
- commandMap: {
3887
- 'Viewlet.executeViewletCommand': executeViewletCommand
3888
- },
3889
- messagePort: port
3890
- });
3891
- if (setAsRendererProcess) {
3892
- set(rpc);
3893
- }
3894
- };
3895
-
3896
- const handleScrollBarCaptureLost = state => {
3897
- return {
3898
- ...state,
3899
- scrollBarActive: false
3900
- };
3901
- };
3841
+ const Separator = 1;
3842
+ const None = 0;
3843
+ const SubMenu = 4;
3844
+ const Disabled = 5;
3902
3845
 
3903
- const getNewDeltaPercent = (height, scrollBarHeight, relativeY) => {
3904
- const halfScrollBarHeight = scrollBarHeight / 2;
3905
- if (relativeY <= halfScrollBarHeight) {
3906
- // clicked at top
3907
- return {
3908
- handleOffset: relativeY,
3909
- percent: 0
3910
- };
3911
- }
3912
- if (relativeY <= height - halfScrollBarHeight) {
3913
- // clicked in middle
3846
+ const nonEnableableStatuses = [Installing, NotInstalled, Uninstalling];
3847
+ const getEnablementFlags = (disabled, status) => {
3848
+ if (status && nonEnableableStatuses.includes(status)) {
3914
3849
  return {
3915
- handleOffset: halfScrollBarHeight,
3916
- percent: (relativeY - halfScrollBarHeight) / (height - scrollBarHeight)
3850
+ disable: Disabled,
3851
+ enable: Disabled
3917
3852
  };
3918
3853
  }
3919
- // clicked at bottom
3920
- return {
3921
- handleOffset: scrollBarHeight - height + relativeY,
3922
- percent: 1
3923
- };
3924
- };
3925
-
3926
- const clamp = (num, min, max) => {
3927
- number(num);
3928
- number(min);
3929
- number(max);
3930
- return Math.min(Math.max(num, min), max);
3931
- };
3932
-
3933
- const setDeltaY = (state, value) => {
3934
- object(state);
3935
- number(value);
3936
- const {
3937
- deltaY,
3938
- finalDeltaY,
3939
- headerHeight,
3940
- height,
3941
- itemHeight,
3942
- items,
3943
- minimumSliderSize
3944
- } = state;
3945
- const listHeight = height - headerHeight;
3946
- const newDeltaY = clamp(value, 0, finalDeltaY);
3947
- if (deltaY === newDeltaY) {
3948
- return state;
3949
- }
3950
- // TODO when it only moves by one px, extensions don't need to be rerendered, only negative margin
3951
- const minLineY = Math.floor(newDeltaY / itemHeight);
3952
- const total = items.length;
3953
- const maxLineY = Math.min(minLineY + getNumberOfVisibleItems$1(listHeight, itemHeight), total);
3954
- const contentHeight = total * itemHeight;
3955
- const scrollBarHeight = getScrollBarSize(listHeight, contentHeight, minimumSliderSize);
3956
- const scrollBarY = getScrollBarY$1(newDeltaY, finalDeltaY, height - headerHeight, scrollBarHeight);
3854
+ const isDisabled = status === Disabled$1 || status === undefined && disabled;
3957
3855
  return {
3958
- ...state,
3959
- deltaY: newDeltaY,
3960
- maxLineY,
3961
- minLineY,
3962
- scrollBarY
3856
+ disable: isDisabled ? Disabled : None,
3857
+ enable: isDisabled ? None : Disabled
3963
3858
  };
3964
3859
  };
3965
-
3966
- const handleScrollBarClick = (state, eventY) => {
3967
- // TODO move this to list
3968
- const {
3969
- deltaY,
3970
- finalDeltaY,
3971
- headerHeight,
3972
- height,
3973
- scrollBarHeight,
3974
- y
3975
- } = state;
3976
- const contentHeight = height - headerHeight;
3977
- const relativeY = eventY - y - headerHeight;
3978
- const currentScrollBarY = getScrollBarY$1(deltaY, finalDeltaY, contentHeight, scrollBarHeight);
3979
- const diff = relativeY - currentScrollBarY;
3980
- if (diff >= 0 && diff < scrollBarHeight) {
3981
- return {
3982
- ...state,
3983
- handleOffset: diff,
3984
- scrollBarActive: true
3985
- };
3986
- }
3987
- const {
3988
- handleOffset,
3989
- percent
3990
- } = getNewDeltaPercent(contentHeight, scrollBarHeight, relativeY);
3991
- const newDeltaY = percent * finalDeltaY;
3992
- return {
3993
- ...setDeltaY(state, newDeltaY),
3994
- handleOffset,
3995
- scrollBarActive: true
3996
- };
3860
+ const getMenuEntriesList = (builtin, disabled = false, status) => {
3861
+ const enablementFlags = getEnablementFlags(disabled, status);
3862
+ return [{
3863
+ command: 'Extensions.enable',
3864
+ flags: enablementFlags.enable,
3865
+ id: 'enable',
3866
+ label: enable$2()
3867
+ }, {
3868
+ command: 'Extensions.enableWorkspace',
3869
+ flags: enablementFlags.enable,
3870
+ id: 'enableWorkspace',
3871
+ label: enableWorkspace$1()
3872
+ }, {
3873
+ command: '',
3874
+ flags: Separator,
3875
+ id: 'separator1',
3876
+ label: ''
3877
+ }, {
3878
+ command: 'Extensions.disable',
3879
+ flags: enablementFlags.disable,
3880
+ id: 'disable',
3881
+ label: disable$2()
3882
+ }, {
3883
+ command: 'Extensions.disableWorkspace',
3884
+ flags: enablementFlags.disable,
3885
+ id: 'disableWorkspace',
3886
+ label: disableWorkspace$1()
3887
+ }, {
3888
+ command: '',
3889
+ flags: Separator,
3890
+ id: 'separator2',
3891
+ label: ''
3892
+ }, {
3893
+ command: 'Extensions.installAnotherVersion',
3894
+ flags: Disabled,
3895
+ id: 'installAnotherVersion',
3896
+ label: installAnotherVersion$1()
3897
+ }, {
3898
+ command: 'Extensions.copyExtensionInfo',
3899
+ flags: None,
3900
+ id: 'copy',
3901
+ label: copy()
3902
+ }, {
3903
+ command: 'Extensions.copyExtensionId',
3904
+ flags: None,
3905
+ id: 'copyExtensionId',
3906
+ label: copyExtensionId$1()
3907
+ }];
3997
3908
  };
3998
3909
 
3999
- const getNewPercent = (contentHeight, scrollBarHeight, relativeY) => {
4000
- if (relativeY <= contentHeight - scrollBarHeight / 2) {
4001
- // clicked in middle
4002
- return relativeY / (contentHeight - scrollBarHeight);
4003
- }
4004
- // clicked at bottom
4005
- return 1;
4006
- };
4007
- const handleScrollBarMove = (state, eventY) => {
4008
- const {
4009
- finalDeltaY,
4010
- handleOffset,
4011
- headerHeight,
4012
- height,
4013
- scrollBarActive,
4014
- scrollBarHeight,
4015
- y
4016
- } = state;
4017
- if (!scrollBarActive) {
4018
- return state;
4019
- }
4020
- const relativeY = eventY - y - headerHeight - handleOffset;
4021
- const contentHeight = height - headerHeight;
4022
- const newPercent = getNewPercent(contentHeight, scrollBarHeight, relativeY);
4023
- const newDeltaY = newPercent * finalDeltaY;
4024
- return setDeltaY(state, newDeltaY);
3910
+ const RE_WORD_START = /\b[a-z]/g;
3911
+ const getMenuEntriesCategory = () => {
3912
+ return CategorySuggestions.map(query => ({
3913
+ args: [query],
3914
+ command: 'Extensions.filterByCategory',
3915
+ flags: None,
3916
+ id: query,
3917
+ label: query.slice(11, -1).replaceAll(RE_WORD_START, character => character.toUpperCase()).replace('Ai', 'AI').replace('Scm', 'SCM')
3918
+ }));
4025
3919
  };
4026
3920
 
4027
- const handleSettingsButtonClick = async (state, index) => {
4028
- const {
4029
- deltaY,
4030
- headerHeight,
4031
- itemHeight,
4032
- items,
4033
- uid,
4034
- x,
4035
- y
4036
- } = state;
4037
- const actualIndex = index;
4038
- if (actualIndex < 0 || actualIndex >= items.length) {
4039
- return state;
4040
- }
4041
-
4042
- // Calculate the position for the context menu
4043
- // The settings button is at the bottom right of the extension list item
4044
- const itemY = y + headerHeight + actualIndex * itemHeight - deltaY;
4045
- const menuX = x + 200; // Position near the right side of the extension item
4046
- const menuY = itemY + itemHeight - 10; // Position at the bottom of the extension item
4047
-
4048
- await show2(uid, ManageExtension, menuX, menuY, {
4049
- builtin: items[actualIndex].builtin === true,
4050
- disabled: items[actualIndex].disabled === true,
4051
- menuId: ManageExtension,
4052
- status: items[actualIndex].status
4053
- });
4054
- return state;
3921
+ const getMenuEntriesFilter = () => {
3922
+ return [{
3923
+ command: 'Extensions.filterByFeatured',
3924
+ flags: None,
3925
+ id: 'filterByFeatured',
3926
+ label: featured()
3927
+ }, {
3928
+ command: 'Extensions.filterByMcpServers',
3929
+ flags: None,
3930
+ id: 'filterByMcpServers',
3931
+ label: mcpServers()
3932
+ }, {
3933
+ command: 'Extensions.filterByMostPopular',
3934
+ flags: None,
3935
+ id: 'filterByMostPopular',
3936
+ label: mostPopular()
3937
+ }, {
3938
+ command: 'Extensions.filterByRecentlyPublished',
3939
+ flags: None,
3940
+ id: 'filterByRecentlyPublished',
3941
+ label: recentlyPublished()
3942
+ }, {
3943
+ command: 'Extensions.filterByRecommended',
3944
+ flags: None,
3945
+ id: 'filterByRecommended',
3946
+ label: recommended()
3947
+ }, {
3948
+ command: '',
3949
+ flags: Separator,
3950
+ id: 'separator1',
3951
+ label: ''
3952
+ }, {
3953
+ args: [{
3954
+ menuId: ExtensionSearchFilter,
3955
+ subMenu: 'category'
3956
+ }],
3957
+ command: '',
3958
+ flags: SubMenu,
3959
+ id: ExtensionSearchFilter,
3960
+ label: category()
3961
+ }, {
3962
+ command: 'Extensions.filterByInstalled',
3963
+ flags: None,
3964
+ id: 'filterByInstalled',
3965
+ label: installed()
3966
+ }, {
3967
+ command: 'Extensions.filterByUpdates',
3968
+ flags: None,
3969
+ id: 'filterByUpdates',
3970
+ label: updates()
3971
+ }, {
3972
+ command: 'Extensions.filterByBuiltin',
3973
+ flags: None,
3974
+ id: 'filterByBuiltin',
3975
+ label: builtIn()
3976
+ }, {
3977
+ command: 'Extensions.filterByLinked',
3978
+ flags: None,
3979
+ id: 'filterByLinked',
3980
+ label: linked()
3981
+ }, {
3982
+ command: 'Extensions.filterByEnabled',
3983
+ flags: None,
3984
+ id: 'filterByEnabled',
3985
+ label: enabled()
3986
+ }, {
3987
+ command: 'Extensions.filterByDisabled',
3988
+ flags: None,
3989
+ id: 'filterByDisabled',
3990
+ label: disabled()
3991
+ }, {
3992
+ command: 'Extensions.filterByWorkspaceUnsupported',
3993
+ flags: None,
3994
+ id: 'filterByWorkspaceUnsupported',
3995
+ label: workspaceUnsupported()
3996
+ }, {
3997
+ command: '',
3998
+ flags: Separator,
3999
+ id: 'separator2',
4000
+ label: ''
4001
+ }, {
4002
+ command: 'SearchExtensions.filterBySortBy',
4003
+ flags: SubMenu,
4004
+ id: 'filterBySortBy',
4005
+ label: sortBy()
4006
+ }];
4055
4007
  };
4056
4008
 
4057
- const handleUninstall = async (state, id) => {
4058
- try {
4059
- await uninstall$2(id);
4060
- return setExtensionStatus(state, id, NotInstalled);
4061
- } catch (error) {
4062
- await showErrorDialog(error);
4063
- return state;
4009
+ const getMenuEntries2 = (state, props) => {
4010
+ const {
4011
+ menuId
4012
+ } = props;
4013
+ switch (menuId) {
4014
+ case ExtensionSearchFilter:
4015
+ return props.subMenu === 'category' ? getMenuEntriesCategory() : getMenuEntriesFilter();
4016
+ default:
4017
+ return getMenuEntriesList(props.builtin, props.disabled, props.status);
4064
4018
  }
4065
4019
  };
4066
4020
 
4067
- const handleWheel = (state, deltaMode, deltaY) => {
4068
- number(deltaMode);
4069
- number(deltaY);
4070
- return setDeltaY(state, state.deltaY + deltaY * state.scrollSensitivity);
4021
+ const getMenuIds = () => {
4022
+ return [ManageExtension, ExtensionSearchFilter];
4071
4023
  };
4072
4024
 
4073
- const createExtensionManagementWorkerRpc = async () => {
4074
- try {
4075
- const rpc = await create$6({
4076
- commandMap: {},
4077
- send: port => sendMessagePortToExtensionManagementWorker(port, 0)
4078
- });
4079
- return rpc;
4080
- } catch (error) {
4081
- throw new VError(error, `Failed to create extension management rpc`);
4082
- }
4025
+ const handleBlur = state => {
4026
+ return {
4027
+ ...state,
4028
+ focus: None$2,
4029
+ suggestOpen: false
4030
+ };
4083
4031
  };
4084
4032
 
4085
- const initializeExtensionManagementWorker = async () => {
4086
- try {
4087
- const rpc = await createExtensionManagementWorkerRpc();
4088
- set$4(rpc);
4089
- } catch {
4090
- // ignore
4091
- }
4033
+ const getExtensionDetailUri = extensionId => {
4034
+ return `extension-detail://${extensionId}`;
4092
4035
  };
4093
4036
 
4094
- const initialize = async () => {
4095
- await initializeExtensionManagementWorker();
4037
+ const openUri = async uri => {
4038
+ return openUri$1(uri);
4096
4039
  };
4097
4040
 
4098
- const installAnotherVersion = async state => {
4099
- await invoke$4('ConfirmPrompt.prompt', 'not implemented', undefined);
4100
- return state;
4041
+ const selectIndex = (state, index) => {
4042
+ return {
4043
+ ...state,
4044
+ focusedIndex: index
4045
+ };
4101
4046
  };
4102
4047
 
4103
- const Small = 1;
4104
- const Normal = 2;
4105
- const Large = 3;
4106
-
4107
- const getViewletSize = width => {
4108
- if (width < 180) {
4109
- return Small;
4110
- }
4111
- if (width < 768) {
4112
- return Normal;
4048
+ const handleClick = async (state, index) => {
4049
+ const {
4050
+ items,
4051
+ minLineY
4052
+ } = state;
4053
+ const actualIndex = index + minLineY;
4054
+ if (actualIndex < 0 || actualIndex >= items.length) {
4055
+ return {
4056
+ ...state,
4057
+ focus: List$2,
4058
+ focusedIndex: -1
4059
+ };
4113
4060
  }
4114
- return Large;
4061
+ const extension = items[actualIndex];
4062
+ const uri = getExtensionDetailUri(extension.id);
4063
+ await openUri(uri);
4064
+ const partialNewState = selectIndex(state, actualIndex);
4065
+ const newState = {
4066
+ ...partialNewState,
4067
+ focus: List$2
4068
+ };
4069
+ return newState;
4115
4070
  };
4116
4071
 
4117
- const getIsFirefox = () => {
4118
- const globalWithNavigator = globalThis;
4119
- return globalWithNavigator.navigator?.userAgent.toLowerCase().includes('firefox') ?? false;
4072
+ const getListIndex = (eventX, eventY, x, y, deltaY, itemHeight, headerHeight) => {
4073
+ const relativeDeltaY = deltaY % itemHeight;
4074
+ const relativeY = eventY - y - headerHeight + relativeDeltaY;
4075
+ const index = Math.floor(relativeY / itemHeight);
4076
+ return index;
4120
4077
  };
4121
4078
 
4122
- const getSavedValue = savedState => {
4123
- if (savedState && typeof savedState === 'object' && 'searchValue' in savedState && typeof savedState.searchValue === 'string') {
4124
- return savedState.searchValue;
4079
+ const handleCompletionPointerDown = (state, label) => {
4080
+ if (state.completionItems.every(item => item.label !== label)) {
4081
+ return Promise.resolve(state);
4125
4082
  }
4126
- return '';
4083
+ return acceptCompletion(state, label);
4127
4084
  };
4128
- const getSavedDeltaY = savedState => {
4129
- if (savedState && typeof savedState === 'object' && 'deltaY' in savedState && typeof savedState.deltaY === 'number' && !Number.isNaN(savedState.deltaY)) {
4130
- return savedState.deltaY;
4085
+
4086
+ const handleClickAt = async (state, button, eventX, eventY, name = '') => {
4087
+ if (name.startsWith('@')) {
4088
+ return handleCompletionPointerDown(state, name);
4131
4089
  }
4132
- return 0;
4090
+ if (name) {
4091
+ return state;
4092
+ }
4093
+ if (button !== LeftClick) {
4094
+ return state;
4095
+ }
4096
+ const {
4097
+ deltaY,
4098
+ headerHeight,
4099
+ itemHeight,
4100
+ x,
4101
+ y
4102
+ } = state;
4103
+ const index = getListIndex(eventX, eventY, x, y, deltaY, itemHeight, headerHeight);
4104
+ return handleClick(state, index);
4133
4105
  };
4134
4106
 
4135
- const restoreState = savedState => {
4136
- const searchValue = getSavedValue(savedState);
4137
- const savedDeltaY = getSavedDeltaY(savedState);
4138
- return {
4139
- deltaY: savedDeltaY,
4140
- searchValue
4141
- };
4107
+ const handleClickCurrent = state => {
4108
+ const {
4109
+ focusedIndex
4110
+ } = state;
4111
+ return handleClick(state, focusedIndex);
4142
4112
  };
4143
4113
 
4144
- const loadContentWithContext = async (context, savedState) => {
4114
+ const handleClickCurrentButKeepFocus = state => {
4145
4115
  const {
4146
- uid
4147
- } = context.getState();
4148
- const loadToken = getToken(uid);
4149
- try {
4150
- const initialState = context.getState();
4151
- const {
4152
- assetDir,
4153
- platform,
4154
- width
4155
- } = initialState;
4156
- const {
4157
- deltaY,
4158
- searchValue: restoredSearchValue
4159
- } = restoreState(savedState);
4160
- const size = getViewletSize(width);
4161
- const scrollSensitivity = getIsFirefox() ? 2.5 : 1;
4162
- await context.updateState(state => {
4163
- if (!state.initial) {
4164
- return state;
4165
- }
4166
- return {
4167
- ...state,
4168
- deltaY,
4169
- initial: false,
4170
- inputSource: Script,
4171
- scrollSensitivity,
4172
- searchValue: restoredSearchValue,
4173
- size
4174
- };
4175
- });
4176
- const allExtensions = await getAllExtensions(assetDir, platform);
4177
- const normalized = normalizeExtensions(allExtensions, platform, assetDir);
4178
- await context.updateState(state => ({
4179
- ...state,
4180
- allExtensions: normalized
4181
- }));
4182
- await handleChangeWithContext(context, {}, false);
4183
- } finally {
4184
- finish(uid, loadToken);
4185
- }
4116
+ focusedIndex
4117
+ } = state;
4118
+ return handleClick(state, focusedIndex);
4186
4119
  };
4187
4120
 
4188
- const openSuggest = state => {
4189
- const completionItems = getCompletionItems(state.searchValue, state.cursorOffset);
4190
- if (completionItems.length === 0) {
4191
- return state;
4192
- }
4193
- return {
4194
- ...state,
4195
- completionFocusedIndex: 0,
4196
- completionItems,
4197
- suggestOpen: true
4198
- };
4121
+ const show2 = async (uid, menuId, x, y, args) => {
4122
+ await showContextMenu2(uid, menuId, x, y, args);
4199
4123
  };
4200
4124
 
4201
- const getCss = state => {
4125
+ const handleClickFilter = async state => {
4126
+ const {
4127
+ headerHeight,
4128
+ uid,
4129
+ width,
4130
+ x,
4131
+ y
4132
+ } = state;
4133
+ const menuX = x + width + 60;
4134
+ const menuHeight = 370;
4135
+ const menuY = y + headerHeight + menuHeight;
4136
+ await show2(uid, ExtensionSearchFilter, menuX, menuY, {
4137
+ menuId: ExtensionSearchFilter,
4138
+ openSubMenuToLeft: true
4139
+ });
4140
+ return state;
4141
+ };
4142
+
4143
+ const handleContextMenu = async (state, button, eventX, eventY) => {
4144
+ // TODO use focused index when when context menu button is -1 (keyboard)
4202
4145
  const {
4203
- cursorOffset,
4204
4146
  deltaY,
4205
4147
  headerHeight,
4206
4148
  itemHeight,
4207
- scrollBarHeight,
4208
- scrollBarY,
4209
- width
4149
+ items,
4150
+ minLineY,
4151
+ uid,
4152
+ x,
4153
+ y
4210
4154
  } = state;
4211
- const relative = -(deltaY % itemHeight);
4212
- const roundedScrollBarY = Math.round(scrollBarY);
4213
- const maximumCompletionLeft = Math.max(8, width - 168);
4214
- const completionLeft = Math.min(Math.round(8 + cursorOffset * 7.5), maximumCompletionLeft);
4215
- const completionTop = Math.max(0, headerHeight - 10);
4216
- return `.Extensions .ScrollBarThumb {
4217
- height: ${scrollBarHeight}px;
4218
- translate: 0 ${roundedScrollBarY}px;
4219
- }
4220
-
4221
-
4222
- /* TODO: avoid using negative margin. find a better way*/
4223
- .ExtensionListItem:nth-child(1) {
4224
- margin-top: ${relative}px;
4225
- }
4155
+ const visibleIndex = getListIndex(eventX, eventY, x, y, deltaY, itemHeight, headerHeight);
4156
+ const index = visibleIndex + minLineY;
4157
+ if (index < 0 || index >= items.length) {
4158
+ return state;
4159
+ }
4160
+ const item = items[index];
4161
+ await show2(uid, ManageExtension, eventX, eventY, {
4162
+ builtin: item.builtin === true,
4163
+ disabled: item.disabled === true,
4164
+ menuId: ManageExtension,
4165
+ status: item.status
4166
+ });
4167
+ return {
4168
+ ...state,
4169
+ focusedIndex: index
4170
+ };
4171
+ };
4226
4172
 
4227
- .ExtensionListItem {
4228
- box-sizing: border-box;
4229
- position: relative !important;
4230
- flex-shrink: 0;
4231
- }
4173
+ const handleDisableWorkspace = (state, id) => {
4174
+ return state;
4175
+ };
4232
4176
 
4233
- .ExtensionListItemLinkedIcon {
4234
- position: absolute;
4235
- top: 6px;
4236
- right: 6px;
4237
- width: 14px;
4238
- height: 14px;
4239
- color: var(--WorkbenchForeground, rgb(188, 190, 190));
4240
- opacity: 0.8;
4241
- }
4177
+ const handleEnableWorkspace = (state, id) => {
4178
+ return state;
4179
+ };
4242
4180
 
4243
- .ExtensionListItemDisabled:not(.ExtensionActive) {
4244
- background: color-mix(in srgb, var(--SideBarBackground, rgb(30, 35, 36)) 95%, black);
4245
- color: var(--ExtensionDisabledForeground, color-mix(in srgb, var(--WorkbenchForeground) 70%, black));
4246
- }
4181
+ const Web = 1;
4182
+ const Electron = 2;
4183
+ const Remote = 3;
4247
4184
 
4248
- .Extensions .ListItems {
4249
- display: flex;
4250
- flex-direction: column;
4251
- gap: 0;
4252
- overflow-y: hidden;
4253
- }
4185
+ const getAllExtensions$1 = async (assetDir, platform) => {
4186
+ try {
4187
+ return await invoke$3('Extensions.getAllExtensions', assetDir, platform);
4188
+ } catch (error) {
4189
+ if (platform === Web) {
4190
+ return [];
4191
+ }
4192
+ throw error;
4193
+ }
4194
+ };
4254
4195
 
4255
- .ExtensionHeader {
4256
- contain: layout style;
4257
- position: relative;
4258
- z-index: 1;
4259
- }
4196
+ const getAllExtensions = (assetDir, platform) => {
4197
+ return getAllExtensions$1(assetDir, platform);
4198
+ };
4260
4199
 
4261
- .ExtensionListItemFooter {
4262
- justify-content: flex-end;
4263
- padding-right: 2px;
4264
- }
4200
+ const getBuiltin = extension => {
4201
+ if (extension === null || typeof extension !== 'object') {
4202
+ return false;
4203
+ }
4204
+ const {
4205
+ builtin,
4206
+ id,
4207
+ isBuiltin
4208
+ } = extension;
4209
+ return isBuiltin === true || builtin === true || typeof id === 'string' && id.startsWith('builtin.');
4210
+ };
4265
4211
 
4266
- .ExtensionListItemAuthorName {
4267
- flex: 1;
4268
- }
4212
+ const isString = item => {
4213
+ return typeof item === 'string';
4214
+ };
4269
4215
 
4270
- .ExtensionActions {
4271
- display: flex;
4272
- gap: 6px;
4273
- }
4216
+ const getCategories = extension => {
4217
+ if (extension === null || typeof extension !== 'object' || !('categories' in extension) || !Array.isArray(extension.categories)) {
4218
+ return [];
4219
+ }
4220
+ return extension.categories.filter(isString);
4221
+ };
4274
4222
 
4275
- .ExtensionActionButton {
4276
- padding: 0 5px;
4277
- }
4223
+ const getDescription = extension => {
4224
+ if (extension === null || typeof extension !== 'object' || !('description' in extension) || typeof extension.description !== 'string' || !extension.description) {
4225
+ return 'n/a';
4226
+ }
4227
+ return extension.description;
4228
+ };
4278
4229
 
4279
- .ExtensionSearchCompletionWidget {
4280
- position: absolute;
4281
- left: ${completionLeft}px;
4282
- top: ${completionTop}px;
4283
- width: min(320px, calc(100% - ${completionLeft + 8}px));
4284
- max-height: 240px;
4285
- overflow-y: auto;
4286
- z-index: 10;
4287
- box-sizing: border-box;
4288
- border: 1px solid var(--CompletionListBorder, #95a29d);
4289
- background: var(--CompletionListBackground, #282e2f);
4290
- color: var(--CompletionListForeground, white);
4291
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.36);
4292
- font-size: 13px;
4293
- line-height: 20px;
4294
- user-select: none;
4295
- }
4230
+ const getDisabled = extension => {
4231
+ return extension !== null && typeof extension === 'object' && 'disabled' in extension && extension.disabled === true;
4232
+ };
4296
4233
 
4297
- .ExtensionSearchCompletionItem {
4298
- display: block;
4299
- width: 100%;
4300
- min-height: 20px;
4301
- padding: 0 6px;
4302
- border: 0;
4303
- background: transparent;
4304
- color: inherit;
4305
- font: inherit;
4306
- line-height: inherit;
4307
- text-align: left;
4308
- overflow: hidden;
4309
- text-overflow: ellipsis;
4310
- white-space: nowrap;
4311
- cursor: pointer;
4312
- }
4234
+ const getDownloadCountValue = extension => {
4235
+ if (extension === null || typeof extension !== 'object') {
4236
+ return undefined;
4237
+ }
4238
+ const marketplace = 'marketplace' in extension && extension.marketplace !== null && typeof extension.marketplace === 'object' ? extension.marketplace : {};
4239
+ const packageJson = 'packageJSON' in extension && extension.packageJSON !== null && typeof extension.packageJSON === 'object' ? extension.packageJSON : {};
4240
+ return ('downloadCount' in extension ? extension.downloadCount : undefined) ?? ('downloads' in extension ? extension.downloads : undefined) ?? ('downloadCount' in marketplace ? marketplace.downloadCount : undefined) ?? ('downloads' in marketplace ? marketplace.downloads : undefined) ?? ('downloadCount' in packageJson ? packageJson.downloadCount : undefined) ?? ('downloads' in packageJson ? packageJson.downloads : undefined);
4241
+ };
4242
+ const getDownloadCount = extension => {
4243
+ const downloadCount = getDownloadCountValue(extension);
4244
+ if (typeof downloadCount !== 'number') {
4245
+ return 'n/a';
4246
+ }
4247
+ return downloadCount.toLocaleString();
4248
+ };
4313
4249
 
4314
- .ExtensionSearchCompletionItem:hover {
4315
- background: var(--CompletionListItemHoverBackground, rgba(64, 92, 80, 0.2));
4316
- }
4250
+ const getRemoteUrl = (extension, platform, assetDir) => {
4251
+ if (extension === null || typeof extension !== 'object') {
4252
+ return '';
4253
+ }
4254
+ const builtin = 'builtin' in extension && extension.builtin === true;
4255
+ const icon = 'icon' in extension && typeof extension.icon === 'string' ? extension.icon : '';
4256
+ const id = 'id' in extension && typeof extension.id === 'string' ? extension.id : '';
4257
+ const path = 'path' in extension && typeof extension.path === 'string' ? extension.path : '';
4258
+ if (platform === Remote || platform === Electron) {
4259
+ if (builtin) {
4260
+ return `${assetDir}/extensions/${id}/${icon}`;
4261
+ }
4262
+ return `/remote/${path}/${icon}`; // TODO support windows paths
4263
+ }
4264
+ if (platform === Web) {
4265
+ return `${path}/${icon}`;
4266
+ }
4267
+ return '';
4268
+ };
4317
4269
 
4318
- .ExtensionSearchCompletionItemFocused {
4319
- background: var(--CompletionListItemActiveBackground, #405c50);
4320
- color: var(--CompletionListItemActiveForeground);
4321
- }
4270
+ const getExtensionDefaultIcon = assetDir => {
4271
+ return `${assetDir}/icons/extensionDefaultIcon.png`;
4272
+ };
4273
+ const getExtensionLanguageBasicsIcon = assetDir => {
4274
+ return `${assetDir}/icons/language-icon.svg`;
4275
+ };
4276
+ const getExtensionThemeIcon = assetDir => {
4277
+ return `${assetDir}/icons/theme-icon.png`;
4278
+ };
4322
4279
 
4323
- .ExtensionSearchCompletionHighlight {
4324
- color: var(--CompletionHighlightForeground, #e1b974);
4325
- font-weight: 700;
4326
- }
4327
- `;
4280
+ const isLanguageBasicsExtension = extension => {
4281
+ return 'name' in extension && typeof extension.name === 'string' && extension.name.startsWith('Language Basics');
4282
+ };
4283
+ const isThemeExtension = extension => {
4284
+ return 'name' in extension && typeof extension.name === 'string' && extension.name.endsWith(' Theme');
4285
+ };
4286
+ const getExtensionIcon = (extension, platform, assetDir) => {
4287
+ if (extension === null || typeof extension !== 'object') {
4288
+ return getExtensionDefaultIcon(assetDir);
4289
+ }
4290
+ const hasIcon = 'icon' in extension && typeof extension.icon === 'string' && extension.icon;
4291
+ const hasPath = 'path' in extension && typeof extension.path === 'string' && extension.path;
4292
+ if (!hasPath || !hasIcon) {
4293
+ if (isLanguageBasicsExtension(extension)) {
4294
+ return getExtensionLanguageBasicsIcon(assetDir);
4295
+ }
4296
+ if (isThemeExtension(extension)) {
4297
+ return getExtensionThemeIcon(assetDir);
4298
+ }
4299
+ return getExtensionDefaultIcon(assetDir);
4300
+ }
4301
+ return getRemoteUrl(extension, platform, assetDir);
4328
4302
  };
4329
4303
 
4330
- const renderCss = newState => {
4331
- const {
4332
- uid
4333
- } = newState;
4334
- const css = getCss(newState);
4335
- return [SetCss, uid, css];
4304
+ const getIcon = (extension, platform, assetDir) => {
4305
+ return getExtensionIcon(extension, platform, assetDir);
4336
4306
  };
4337
4307
 
4338
- const Extensions$1 = 'extensions';
4308
+ const getId = extension => {
4309
+ if (extension === null || typeof extension !== 'object' || !('id' in extension) || typeof extension.id !== 'string' || !extension.id) {
4310
+ return 'n/a';
4311
+ }
4312
+ return extension.id;
4313
+ };
4339
4314
 
4340
- const getSelector = focus => {
4341
- switch (focus) {
4342
- case Input:
4343
- return `[name="${Extensions$1}"]`;
4344
- case List$2:
4345
- return '.ListItems';
4346
- default:
4347
- return '';
4315
+ const getLinked = extension => {
4316
+ if (extension === null || typeof extension !== 'object') {
4317
+ return false;
4318
+ }
4319
+ if ('linked' in extension && extension.linked === true) {
4320
+ return true;
4348
4321
  }
4322
+ return 'symlink' in extension && typeof extension.symlink === 'string' && extension.symlink.length > 0;
4349
4323
  };
4350
- const renderFocus = newState => {
4351
- const {
4352
- focus,
4353
- uid
4354
- } = newState;
4355
- if (!focus) {
4356
- return [];
4324
+
4325
+ const getName = extension => {
4326
+ if (extension === null || typeof extension !== 'object') {
4327
+ return 'n/a';
4328
+ }
4329
+ if ('name' in extension && typeof extension.name === 'string' && extension.name) {
4330
+ return extension.name;
4357
4331
  }
4358
- const selector = getSelector(focus);
4359
- return [FocusSelector, uid, selector];
4332
+ if ('id' in extension && typeof extension.id === 'string' && extension.id) {
4333
+ return extension.id;
4334
+ }
4335
+ return 'n/a';
4360
4336
  };
4361
4337
 
4362
- const renderFocusContext = newState => {
4363
- const {
4364
- uid
4365
- } = newState;
4366
- if (newState.focus === Input) {
4367
- return ['Viewlet.setFocusContext', uid, FocusExtensionsInput];
4338
+ const RE_PUBLISHER = /^[a-z\d-]+/;
4339
+ const getPublisher = extension => {
4340
+ if (extension === null || typeof extension !== 'object' || !('id' in extension) || typeof extension.id !== 'string') {
4341
+ return 'n/a';
4368
4342
  }
4369
- if (newState.focus === List$2) {
4370
- return ['Viewlet.setFocusContext', uid, FocusExtensions];
4343
+ const match = extension.id.match(RE_PUBLISHER);
4344
+ if (!match) {
4345
+ return 'n/a';
4371
4346
  }
4372
- return [];
4347
+ return match[0];
4373
4348
  };
4374
4349
 
4375
- const ComboBox = 'combobox';
4376
- const Image = 'img';
4377
- const List$1 = 'list';
4378
- const ListBox = 'listbox';
4379
- const ListItem = 'listitem';
4380
- const None = 'none';
4381
- const Option = 'option';
4382
- const ToolBar = 'toolbar';
4383
-
4384
- const Actions = 'Actions';
4385
- const ExtensionActions = 'ExtensionActions';
4386
- const ExtensionActionButton = 'ExtensionActionButton';
4387
- const ExtensionActive = 'ExtensionActive';
4388
- const ExtensionHeader = 'ExtensionHeader';
4389
- const ExtensionListItem = 'ExtensionListItem';
4390
- const ExtensionListItemActionInstall = 'ExtensionListItemActionInstall';
4391
- const ExtensionListItemAuthorName = 'ExtensionListItemAuthorName';
4392
- const ExtensionListItemDescription = 'ExtensionListItemDescription';
4393
- const ExtensionListItemDetail = 'ExtensionListItemDetail';
4394
- const ExtensionListItemDisabled = 'ExtensionListItemDisabled';
4395
- const ExtensionListItemDownloadCount = 'ExtensionListItemDownloadCount';
4396
- const ExtensionListItemFooter = 'ExtensionListItemFooter';
4397
- const ExtensionListItemIcon = 'ExtensionListItemIcon';
4398
- const ExtensionListItemLinkedIcon = 'ExtensionListItemLinkedIcon';
4399
- const ExtensionListItemMetadata = 'ExtensionListItemMetadata';
4400
- const ExtensionListItemName = 'ExtensionListItemName';
4401
- const ExtensionListItemRating = 'ExtensionListItemRating';
4402
- const ExtensionListItemStatistic = 'ExtensionListItemStatistic';
4403
- const ExtensionSearchCompletionHighlight = 'ExtensionSearchCompletionHighlight';
4404
- const ExtensionSearchCompletionItem = 'ExtensionSearchCompletionItem';
4405
- const ExtensionSearchCompletionItemFocused = 'ExtensionSearchCompletionItemFocused';
4406
- const ExtensionSearchCompletionWidget = 'ExtensionSearchCompletionWidget';
4407
- const Extensions = 'Extensions';
4408
- const FocusOutline = 'FocusOutline';
4409
- const IconButton = 'IconButton';
4410
- const List = 'List';
4411
- const ListItems = 'ListItems';
4412
- const MaskIcon = 'MaskIcon';
4413
- const MultilineInputBox = 'MultilineInputBox';
4414
- const NoExtensionsFoundMessage = 'NoExtensionsFoundMessage';
4415
- const ScrollBar = 'ScrollBar';
4416
- const ScrollBarSmall = 'ScrollBarSmall';
4417
- const ScrollBarThumb = 'ScrollBarThumb';
4418
- const ScrollBarThumbActive = 'ScrollBarThumbActive';
4419
- const SearchField = 'SearchField';
4420
- const SearchFieldButton = 'SearchFieldButton';
4421
- const SearchFieldButtonDisabled = 'SearchFieldButtonDisabled';
4422
- const SearchFieldButtons = 'SearchFieldButtons';
4423
- const SearchFieldContainer = 'SearchFieldContainer';
4424
- const Viewlet = 'Viewlet';
4425
-
4426
- const getCompletionLabelVirtualDom = (label, highlights) => {
4427
- const dom = [];
4428
- let position = 0;
4429
- for (let i = 0; i < highlights.length; i += 2) {
4430
- const start = highlights[i];
4431
- const end = highlights[i + 1];
4432
- if (position < start) {
4433
- dom.push(text(label.slice(position, start)));
4434
- }
4435
- dom.push({
4436
- childCount: 1,
4437
- className: ExtensionSearchCompletionHighlight,
4438
- name: label,
4439
- type: Span
4440
- }, text(label.slice(start, end)));
4441
- position = end;
4350
+ const getRatingValue = extension => {
4351
+ if (extension === null || typeof extension !== 'object') {
4352
+ return undefined;
4442
4353
  }
4443
- if (position < label.length) {
4444
- dom.push(text(label.slice(position)));
4354
+ const marketplace = 'marketplace' in extension && extension.marketplace !== null && typeof extension.marketplace === 'object' ? extension.marketplace : {};
4355
+ const packageJson = 'packageJSON' in extension && extension.packageJSON !== null && typeof extension.packageJSON === 'object' ? extension.packageJSON : {};
4356
+ return ('rating' in extension ? extension.rating : undefined) ?? ('averageRating' in extension ? extension.averageRating : undefined) ?? ('rating' in marketplace ? marketplace.rating : undefined) ?? ('averageRating' in marketplace ? marketplace.averageRating : undefined) ?? ('rating' in packageJson ? packageJson.rating : undefined) ?? ('averageRating' in packageJson ? packageJson.averageRating : undefined);
4357
+ };
4358
+ const getRating = extension => {
4359
+ const rating = getRatingValue(extension);
4360
+ if (typeof rating !== 'number') {
4361
+ return 'n/a';
4445
4362
  }
4446
- return dom;
4363
+ return rating.toFixed(1);
4447
4364
  };
4448
4365
 
4449
- const getRootNodeCount = nodes => {
4450
- let count = 0;
4451
- const remainingChildCounts = [];
4452
- for (const node of nodes) {
4453
- while (remainingChildCounts.length > 0 && remainingChildCounts.at(-1) === 0) {
4454
- remainingChildCounts.pop();
4455
- }
4456
- if (remainingChildCounts.length === 0) {
4457
- count++;
4458
- } else {
4459
- const lastIndex = remainingChildCounts.length - 1;
4460
- remainingChildCounts[lastIndex]--;
4461
- }
4462
- remainingChildCounts.push(node.childCount);
4366
+ const getSize = extension => {
4367
+ if (extension === null || typeof extension !== 'object' || !('size' in extension) || extension.size === 0 || typeof extension.size !== 'number') {
4368
+ return 0;
4463
4369
  }
4464
- return count;
4370
+ return extension.size;
4465
4371
  };
4466
- const getCompletionItemVirtualDom = (item, index, focusedIndex) => {
4467
- const labelDom = getCompletionLabelVirtualDom(item.label, item.highlights);
4468
- const focused = index === focusedIndex;
4469
- return [{
4470
- ariaSelected: focused,
4471
- childCount: getRootNodeCount(labelDom),
4472
- className: focused ? mergeClassNames(ExtensionSearchCompletionItem, ExtensionSearchCompletionItemFocused) : ExtensionSearchCompletionItem,
4473
- id: `ExtensionSearchCompletion-${index}`,
4474
- name: item.label,
4475
- onPointerDown: HandlePointerDown,
4476
- role: Option,
4477
- type: Button$2
4478
- }, ...labelDom];
4372
+
4373
+ const getStatus = extension => {
4374
+ if (extension === null || typeof extension !== 'object' || !('status' in extension)) {
4375
+ return undefined;
4376
+ }
4377
+ return typeof extension.status === 'string' ? extension.status : undefined;
4479
4378
  };
4480
4379
 
4481
- const getCompletionWidgetVirtualDom = (items, focusedIndex) => {
4482
- return [{
4483
- ariaLabel: 'Extension search completions',
4484
- childCount: items.length,
4485
- className: ExtensionSearchCompletionWidget,
4486
- id: 'ExtensionSearchCompletions',
4487
- role: ListBox,
4488
- type: Div
4489
- }, ...items.flatMap((item, index) => getCompletionItemVirtualDom(item, index, focusedIndex))];
4380
+ const getUpdatedDate = extension => {
4381
+ if (extension === null || typeof extension !== 'object' || !('updatedDate' in extension) || !extension.updatedDate || typeof extension.updatedDate !== 'number') {
4382
+ return 0;
4383
+ }
4384
+ return extension.updatedDate;
4490
4385
  };
4491
4386
 
4492
- const Focusable = 0;
4387
+ const normalizeExtension = (extension, platform, assetDir) => {
4388
+ return {
4389
+ builtin: getBuiltin(extension),
4390
+ categories: getCategories(extension),
4391
+ description: getDescription(extension),
4392
+ disabled: getDisabled(extension),
4393
+ downloadCount: getDownloadCount(extension),
4394
+ icon: getIcon(extension, platform, assetDir),
4395
+ id: getId(extension),
4396
+ linked: getLinked(extension),
4397
+ name: getName(extension),
4398
+ publisher: getPublisher(extension),
4399
+ rating: getRating(extension),
4400
+ size: getSize(extension),
4401
+ status: getStatus(extension),
4402
+ updatedDate: getUpdatedDate(extension),
4403
+ uri: ''
4404
+ };
4405
+ };
4493
4406
 
4494
- const disabledClassName = mergeClassNames(SearchFieldButton, SearchFieldButtonDisabled);
4495
- const getClassName = enabled => {
4496
- if (enabled) {
4497
- return SearchFieldButton;
4498
- }
4499
- return disabledClassName;
4407
+ const normalizeExtensions = (extensions, platform, assetDir) => {
4408
+ return Array.from(extensions, extension => normalizeExtension(extension, platform, assetDir));
4500
4409
  };
4501
- const getSearchFieldButtonVirtualDom = button => {
4410
+
4411
+ const handleExtensionsChanged = async state => {
4502
4412
  const {
4503
- enabled,
4504
- icon,
4505
- onClick,
4506
- title
4507
- } = button;
4508
- return [{
4509
- childCount: 1,
4510
- className: getClassName(enabled),
4511
- onClick,
4512
- tabIndex: Focusable,
4513
- title,
4514
- type: Button$2
4515
- }, {
4516
- childCount: 0,
4517
- className: mergeClassNames(MaskIcon, icon),
4518
- type: Div
4519
- }];
4413
+ assetDir,
4414
+ platform
4415
+ } = state;
4416
+ const allExtensions = await getAllExtensions(assetDir, platform);
4417
+ const normalized = normalizeExtensions(allExtensions, platform, assetDir);
4418
+ return handleChange({
4419
+ ...state,
4420
+ allExtensions: normalized
4421
+ }, {});
4520
4422
  };
4521
4423
 
4522
- const searchFieldNode = {
4523
- childCount: 2,
4524
- className: SearchField,
4525
- role: None,
4526
- type: Div
4424
+ const handleFocus = async state => {
4425
+ return {
4426
+ ...state,
4427
+ focus: List$2
4428
+ };
4527
4429
  };
4528
- const getSearchFieldVirtualDom = (name, placeholder, onInput, insideButtons, outsideButtons, onFocus = '', onBlur = '', inputProperties = {}) => {
4529
- // TODO avoid mutation
4530
- const dom = [searchFieldNode, {
4531
- autocapitalize: 'off',
4532
- autocomplete: 'off',
4533
- autocorrect: 'off',
4534
- childCount: 0,
4535
- className: MultilineInputBox,
4536
- inputType: 'search',
4537
- name,
4538
- ...(onBlur && {
4539
- onBlur
4540
- }),
4541
- onFocus,
4542
- onInput,
4543
- placeholder,
4544
- spellcheck: false,
4545
- type: Input$1,
4546
- ...inputProperties
4547
- }, {
4548
- childCount: insideButtons.length,
4549
- className: SearchFieldButtons,
4550
- type: Div
4551
- }, ...insideButtons.flatMap(getSearchFieldButtonVirtualDom)];
4552
- if (outsideButtons.length > 0) {
4553
- dom.unshift({
4554
- childCount: 1 + outsideButtons.length,
4555
- className: SearchFieldContainer,
4556
- role: None,
4557
- type: Div
4558
- });
4559
- dom.push(...outsideButtons.flatMap(getSearchFieldButtonVirtualDom));
4560
- }
4561
- return dom;
4430
+
4431
+ const handleHeaderContextMenu = async state => {
4432
+ return state;
4562
4433
  };
4563
4434
 
4564
- const getCompletionVirtualDom = (completionItems, completionFocusedIndex, suggestOpen) => {
4565
- if (!suggestOpen) {
4566
- return [];
4567
- }
4568
- return getCompletionWidgetVirtualDom(completionItems, completionFocusedIndex);
4569
- };
4570
- const getExtensionHeaderVirtualDom = (placeholder, actions, completionItems = [], completionFocusedIndex = 0, suggestOpen = false) => {
4571
- const inputProperties = suggestOpen ? {
4572
- ariaActivedescendant: `ExtensionSearchCompletion-${completionFocusedIndex}`,
4573
- ariaAutoComplete: 'list',
4574
- ariaControls: 'ExtensionSearchCompletions',
4575
- ariaExpanded: true,
4576
- role: ComboBox
4577
- } : {
4578
- ariaAutoComplete: 'list',
4579
- ariaExpanded: false,
4580
- role: ComboBox
4435
+ const handleInputFocus = state => {
4436
+ return {
4437
+ ...state,
4438
+ focus: Input
4581
4439
  };
4582
- const completionDom = getCompletionVirtualDom(completionItems, completionFocusedIndex, suggestOpen);
4583
- return [{
4584
- childCount: suggestOpen ? 2 : 1,
4585
- className: ExtensionHeader,
4586
- onContextMenu: HandleHeaderContextMenu,
4587
- type: Div
4588
- }, ...getSearchFieldVirtualDom(Extensions$1, placeholder, HandleExtensionsInput, actions, [], HandleInputFocus, HandleInputBlur, inputProperties), ...completionDom];
4589
4440
  };
4590
4441
 
4591
- const renderHeader = newState => {
4592
- const actions = getInputActions(newState.searchValue.length > 0);
4593
- const dom = getExtensionHeaderVirtualDom(newState.placeholder, actions, newState.completionItems, newState.completionFocusedIndex, newState.suggestOpen);
4594
- return ['setHeaderDom', dom];
4442
+ const handleInstall = async (state, id) => {
4443
+ await installExtension(id);
4444
+ return setExtensionStatus(state, id, Enabled);
4595
4445
  };
4596
4446
 
4597
- const Extension = 'Extension';
4598
-
4599
- const install = {
4600
- disabled: false,
4601
- label: install$1(),
4602
- onClick: HandleInstall
4447
+ const state = {
4448
+ connected: false
4603
4449
  };
4604
- const installing = {
4605
- disabled: true,
4606
- label: installing$1(),
4607
- onClick: HandleInstall
4450
+ const isConnected = () => {
4451
+ const {
4452
+ connected
4453
+ } = state;
4454
+ return connected;
4608
4455
  };
4609
- const enable = {
4610
- disabled: false,
4611
- label: enable$2(),
4612
- onClick: HandleEnable
4456
+ const invoke = (method, ...params) => {
4457
+ return invoke$2(method, ...params);
4613
4458
  };
4614
- const disable = {
4615
- disabled: false,
4616
- label: disable$2(),
4617
- onClick: HandleDisable
4459
+ const set = rpc => {
4460
+ set$3(rpc);
4461
+ state.connected = true;
4618
4462
  };
4619
- const uninstall = {
4620
- disabled: false,
4621
- label: uninstall$1(),
4622
- onClick: HandleUninstall
4463
+
4464
+ const handleMessagePort = async (port, viewletCommandMap, setAsRendererProcess = true) => {
4465
+ const executeViewletCommand = async (uid, command, ...args) => {
4466
+ const fn = viewletCommandMap[`SearchExtensions.${command}`];
4467
+ if (typeof fn !== 'function') {
4468
+ throw new TypeError(`Viewlet command not found: ${command}`);
4469
+ }
4470
+ await fn(uid, ...args);
4471
+ await invoke$1('Viewlet.requestRender', uid);
4472
+ };
4473
+ const rpc = await create$7({
4474
+ commandMap: {
4475
+ 'Viewlet.executeViewletCommand': executeViewletCommand
4476
+ },
4477
+ messagePort: port
4478
+ });
4479
+ if (setAsRendererProcess) {
4480
+ set(rpc);
4481
+ }
4623
4482
  };
4624
- const uninstalling = {
4625
- disabled: true,
4626
- label: uninstalling$1(),
4627
- onClick: HandleUninstall
4483
+
4484
+ const handleScrollBarCaptureLost = state => {
4485
+ return {
4486
+ ...state,
4487
+ scrollBarActive: false
4488
+ };
4628
4489
  };
4629
- const getExtensionActions = (builtin, disabled, status) => {
4630
- if (status === NotInstalled) {
4631
- return [install];
4632
- }
4633
- if (status === Installing) {
4634
- return [installing];
4490
+
4491
+ const getNewDeltaPercent = (height, scrollBarHeight, relativeY) => {
4492
+ const halfScrollBarHeight = scrollBarHeight / 2;
4493
+ if (relativeY <= halfScrollBarHeight) {
4494
+ // clicked at top
4495
+ return {
4496
+ handleOffset: relativeY,
4497
+ percent: 0
4498
+ };
4635
4499
  }
4636
- if (status === Uninstalling) {
4637
- return builtin ? [] : [uninstalling];
4500
+ if (relativeY <= height - halfScrollBarHeight) {
4501
+ // clicked in middle
4502
+ return {
4503
+ handleOffset: halfScrollBarHeight,
4504
+ percent: (relativeY - halfScrollBarHeight) / (height - scrollBarHeight)
4505
+ };
4638
4506
  }
4639
- const enableOrDisable = isExtensionDisabled(disabled, status) ? enable : disable;
4640
- return builtin ? [enableOrDisable] : [enableOrDisable, uninstall];
4507
+ // clicked at bottom
4508
+ return {
4509
+ handleOffset: scrollBarHeight - height + relativeY,
4510
+ percent: 1
4511
+ };
4641
4512
  };
4642
4513
 
4643
- const className = mergeClassNames(ExtensionListItemActionInstall, ExtensionActionButton);
4644
- const getExtensionActionVirtualDom = (action, id) => {
4645
- return [{
4646
- childCount: 1,
4647
- className,
4648
- disabled: action.disabled,
4649
- name: id,
4650
- onClick: action.onClick,
4651
- type: Button$2
4652
- }, text(action.label)];
4514
+ const clamp = (num, min, max) => {
4515
+ number(num);
4516
+ number(min);
4517
+ number(max);
4518
+ return Math.min(Math.max(num, min), max);
4653
4519
  };
4654
4520
 
4655
- const getExtensionActionsVirtualDom = (id, builtin, disabled, status) => {
4656
- const actions = getExtensionActions(builtin, disabled, status);
4657
- return [{
4658
- childCount: actions.length,
4659
- className: ExtensionActions,
4660
- type: Div
4661
- }, ...actions.flatMap(action => getExtensionActionVirtualDom(action, id))];
4521
+ const setDeltaY = (state, value) => {
4522
+ object(state);
4523
+ number(value);
4524
+ const {
4525
+ deltaY,
4526
+ finalDeltaY,
4527
+ headerHeight,
4528
+ height,
4529
+ itemHeight,
4530
+ items,
4531
+ minimumSliderSize
4532
+ } = state;
4533
+ const listHeight = height - headerHeight;
4534
+ const newDeltaY = clamp(value, 0, finalDeltaY);
4535
+ if (deltaY === newDeltaY) {
4536
+ return state;
4537
+ }
4538
+ // TODO when it only moves by one px, extensions don't need to be rerendered, only negative margin
4539
+ const minLineY = Math.floor(newDeltaY / itemHeight);
4540
+ const total = items.length;
4541
+ const maxLineY = Math.min(minLineY + getNumberOfVisibleItems$1(listHeight, itemHeight), total);
4542
+ const contentHeight = total * itemHeight;
4543
+ const scrollBarHeight = getScrollBarSize(listHeight, contentHeight, minimumSliderSize);
4544
+ const scrollBarY = getScrollBarY$1(newDeltaY, finalDeltaY, height - headerHeight, scrollBarHeight);
4545
+ return {
4546
+ ...state,
4547
+ deltaY: newDeltaY,
4548
+ maxLineY,
4549
+ minLineY,
4550
+ scrollBarY
4551
+ };
4662
4552
  };
4663
4553
 
4664
- const getExtensionListItemClassName = (focused, disabled) => {
4665
- return mergeClassNames(ExtensionListItem, focused ? ExtensionActive : '', disabled ? ExtensionListItemDisabled : '');
4554
+ const handleScrollBarClick = (state, eventY) => {
4555
+ // TODO move this to list
4556
+ const {
4557
+ deltaY,
4558
+ finalDeltaY,
4559
+ headerHeight,
4560
+ height,
4561
+ scrollBarHeight,
4562
+ y
4563
+ } = state;
4564
+ const contentHeight = height - headerHeight;
4565
+ const relativeY = eventY - y - headerHeight;
4566
+ const currentScrollBarY = getScrollBarY$1(deltaY, finalDeltaY, contentHeight, scrollBarHeight);
4567
+ const diff = relativeY - currentScrollBarY;
4568
+ if (diff >= 0 && diff < scrollBarHeight) {
4569
+ return {
4570
+ ...state,
4571
+ handleOffset: diff,
4572
+ scrollBarActive: true
4573
+ };
4574
+ }
4575
+ const {
4576
+ handleOffset,
4577
+ percent
4578
+ } = getNewDeltaPercent(contentHeight, scrollBarHeight, relativeY);
4579
+ const newDeltaY = percent * finalDeltaY;
4580
+ return {
4581
+ ...setDeltaY(state, newDeltaY),
4582
+ handleOffset,
4583
+ scrollBarActive: true
4584
+ };
4585
+ };
4586
+
4587
+ const getNewPercent = (contentHeight, scrollBarHeight, relativeY) => {
4588
+ if (relativeY <= contentHeight - scrollBarHeight / 2) {
4589
+ // clicked in middle
4590
+ return relativeY / (contentHeight - scrollBarHeight);
4591
+ }
4592
+ // clicked at bottom
4593
+ return 1;
4594
+ };
4595
+ const handleScrollBarMove = (state, eventY) => {
4596
+ const {
4597
+ finalDeltaY,
4598
+ handleOffset,
4599
+ headerHeight,
4600
+ height,
4601
+ scrollBarActive,
4602
+ scrollBarHeight,
4603
+ y
4604
+ } = state;
4605
+ if (!scrollBarActive) {
4606
+ return state;
4607
+ }
4608
+ const relativeY = eventY - y - headerHeight - handleOffset;
4609
+ const contentHeight = height - headerHeight;
4610
+ const newPercent = getNewPercent(contentHeight, scrollBarHeight, relativeY);
4611
+ const newDeltaY = newPercent * finalDeltaY;
4612
+ return setDeltaY(state, newDeltaY);
4666
4613
  };
4667
4614
 
4668
- const getExtensionListItemFooter = hasStatistics => {
4669
- return {
4670
- childCount: hasStatistics ? 3 : 2,
4671
- className: ExtensionListItemFooter,
4672
- type: Div
4673
- };
4615
+ const handleSettingsButtonClick = async (state, index) => {
4616
+ const {
4617
+ deltaY,
4618
+ headerHeight,
4619
+ itemHeight,
4620
+ items,
4621
+ uid,
4622
+ x,
4623
+ y
4624
+ } = state;
4625
+ const actualIndex = index;
4626
+ if (actualIndex < 0 || actualIndex >= items.length) {
4627
+ return state;
4628
+ }
4629
+
4630
+ // Calculate the position for the context menu
4631
+ // The settings button is at the bottom right of the extension list item
4632
+ const itemY = y + headerHeight + actualIndex * itemHeight - deltaY;
4633
+ const menuX = x + 200; // Position near the right side of the extension item
4634
+ const menuY = itemY + itemHeight - 10; // Position at the bottom of the extension item
4635
+
4636
+ await show2(uid, ManageExtension, menuX, menuY, {
4637
+ builtin: items[actualIndex].builtin === true,
4638
+ disabled: items[actualIndex].disabled === true,
4639
+ menuId: ManageExtension,
4640
+ status: items[actualIndex].status
4641
+ });
4642
+ return state;
4674
4643
  };
4675
4644
 
4676
- const getExtensionListItemId = focused => {
4677
- if (focused) {
4678
- return `ExtensionActive`;
4645
+ const handleUninstall = async (state, id) => {
4646
+ try {
4647
+ await uninstall$2(id);
4648
+ return setExtensionStatus(state, id, NotInstalled);
4649
+ } catch (error) {
4650
+ await showErrorDialog(error);
4651
+ return state;
4679
4652
  }
4680
- return undefined;
4681
4653
  };
4682
4654
 
4683
- const extensionListItemMetadataNode = {
4684
- childCount: 2,
4685
- className: ExtensionListItemMetadata,
4686
- type: Div
4687
- };
4688
- const getStatisticVirtualDom = (label, value, className) => {
4689
- const accessibleLabel = `${label}: ${value}`;
4690
- return [{
4691
- ariaLabel: accessibleLabel,
4692
- childCount: 1,
4693
- className: mergeClassNames(ExtensionListItemStatistic, className),
4694
- title: accessibleLabel,
4695
- type: Span
4696
- }, text(value)];
4697
- };
4698
- const getExtensionStatisticsVirtualDom = (downloadCount, rating$1) => {
4699
- return [extensionListItemMetadataNode, ...getStatisticVirtualDom(downloads(), downloadCount, ExtensionListItemDownloadCount), ...getStatisticVirtualDom(rating(), rating$1, ExtensionListItemRating)];
4655
+ const handleWheel = (state, deltaMode, deltaY) => {
4656
+ number(deltaMode);
4657
+ number(deltaY);
4658
+ return setDeltaY(state, state.deltaY + deltaY * state.scrollSensitivity);
4700
4659
  };
4701
4660
 
4702
- const getExtensionListItemStatisticsVirtualDom = (hasStatistics, downloadCount, rating) => {
4703
- if (!hasStatistics) {
4704
- return [];
4661
+ const createExtensionManagementWorkerRpc = async () => {
4662
+ try {
4663
+ const rpc = await create$6({
4664
+ commandMap: {},
4665
+ send: port => sendMessagePortToExtensionManagementWorker(port, 0)
4666
+ });
4667
+ return rpc;
4668
+ } catch (error) {
4669
+ throw new VError(error, `Failed to create extension management rpc`);
4705
4670
  }
4706
- return getExtensionStatisticsVirtualDom(downloadCount, rating);
4707
4671
  };
4708
4672
 
4709
- const getListItemDetail = linked => {
4710
- return {
4711
- childCount: linked ? 4 : 3,
4712
- className: ExtensionListItemDetail,
4713
- type: Div
4714
- };
4715
- };
4716
- const listItemName = {
4717
- childCount: 1,
4718
- className: ExtensionListItemName,
4719
- type: Div
4720
- };
4721
- const listItemDescription = {
4722
- childCount: 1,
4723
- className: ExtensionListItemDescription,
4724
- type: Div
4725
- };
4726
- const listItemAuthorName = {
4727
- childCount: 1,
4728
- className: ExtensionListItemAuthorName,
4729
- type: Div
4730
- };
4731
- const getLinkedIconVirtualDom = linked$1 => {
4732
- if (!linked$1) {
4733
- return [];
4673
+ const initializeExtensionManagementWorker = async () => {
4674
+ try {
4675
+ const rpc = await createExtensionManagementWorkerRpc();
4676
+ set$4(rpc);
4677
+ } catch {
4678
+ // ignore
4734
4679
  }
4735
- const label = linked();
4736
- return [{
4737
- ariaLabel: label,
4738
- childCount: 0,
4739
- className: mergeClassNames(MaskIcon, 'MaskIconLinkExternal', ExtensionListItemLinkedIcon),
4740
- role: Image,
4741
- title: 'Extension is linked',
4742
- type: Div
4743
- }];
4744
4680
  };
4745
- const getExtensionListItemVirtualDom = extension => {
4746
- const {
4747
- builtin = false,
4748
- description,
4749
- disabled = false,
4750
- downloadCount = 'n/a',
4751
- focused,
4752
- icon,
4753
- id,
4754
- linked = false,
4755
- name,
4756
- posInSet,
4757
- publisher,
4758
- rating = 'n/a',
4759
- setSize,
4760
- status
4761
- } = extension;
4762
- const actionsDom = getExtensionActionsVirtualDom(id, builtin, disabled, status);
4763
- const hasStatistics = !builtin;
4764
- const dom = [{
4765
- ariaPosInSet: posInSet,
4766
- ariaRoleDescription: Extension,
4767
- ariaSetSize: setSize,
4768
- childCount: 2,
4769
- className: getExtensionListItemClassName(focused, disabled),
4770
- id: getExtensionListItemId(focused),
4771
- role: ListItem,
4772
- type: Div
4773
- }, {
4774
- childCount: 0,
4775
- className: ExtensionListItemIcon,
4776
- role: None,
4777
- src: icon,
4778
- type: Img
4779
- }, getListItemDetail(linked), listItemName, text(name), listItemDescription, text(description), getExtensionListItemFooter(hasStatistics), listItemAuthorName, text(publisher), ...getExtensionListItemStatisticsVirtualDom(hasStatistics, downloadCount, rating), ...actionsDom, ...getLinkedIconVirtualDom(linked)];
4780
- return dom;
4681
+
4682
+ const initialize = async () => {
4683
+ await initializeExtensionManagementWorker();
4781
4684
  };
4782
4685
 
4783
- const getListClassName = focusOutline => {
4784
- const className = focusOutline ? mergeClassNames(ListItems, FocusOutline) : ListItems;
4785
- return className;
4686
+ const installAnotherVersion = async state => {
4687
+ await invoke$4('ConfirmPrompt.prompt', 'not implemented', undefined);
4688
+ return state;
4786
4689
  };
4787
- const getExtensionsListVirtualDom = (visibleExtensions, focusOutline) => {
4788
- const dom = [{
4789
- ariaLabel: extensions(),
4790
- childCount: visibleExtensions.length,
4791
- className: getListClassName(focusOutline),
4792
- onBlur: HandleBlur,
4793
- onContextmenu: HandleContextMenu,
4794
- onContextMenu: HandleContextMenu,
4795
- onFocus: HandleFocus,
4796
- onPointerDown: HandlePointerDown,
4797
- onTouchEnd: HandleTouchEnd,
4798
- onTouchMove: HandleTouchMove,
4799
- onTouchStart: HandleTouchStart,
4800
- onWheel: HandleWheel,
4801
- role: List$1,
4802
- tabIndex: Focusable,
4803
- type: Div
4804
- }, ...visibleExtensions.flatMap(getExtensionListItemVirtualDom)];
4805
- return dom;
4690
+
4691
+ const Small = 1;
4692
+ const Normal = 2;
4693
+ const Large = 3;
4694
+
4695
+ const getViewletSize = width => {
4696
+ if (width < 180) {
4697
+ return Small;
4698
+ }
4699
+ if (width < 768) {
4700
+ return Normal;
4701
+ }
4702
+ return Large;
4806
4703
  };
4807
4704
 
4808
- const getExtensionsVirtualDom = (visibleExtensions, focusOutline) => {
4809
- const dom = getExtensionsListVirtualDom(visibleExtensions, focusOutline);
4810
- // TODO
4811
- return dom;
4705
+ const getIsFirefox = () => {
4706
+ const globalWithNavigator = globalThis;
4707
+ return globalWithNavigator.navigator?.userAgent.toLowerCase().includes('firefox') ?? false;
4812
4708
  };
4813
4709
 
4814
- const noExtensionsFoundNode = {
4815
- childCount: 1,
4816
- className: NoExtensionsFoundMessage,
4817
- type: Div
4710
+ const getSavedValue = savedState => {
4711
+ if (savedState && typeof savedState === 'object' && 'searchValue' in savedState && typeof savedState.searchValue === 'string') {
4712
+ return savedState.searchValue;
4713
+ }
4714
+ return '';
4818
4715
  };
4819
- const getNoExtensionsFoundVirtualDom = message => {
4820
- return [noExtensionsFoundNode, text(message)];
4716
+ const getSavedDeltaY = savedState => {
4717
+ if (savedState && typeof savedState === 'object' && 'deltaY' in savedState && typeof savedState.deltaY === 'number' && !Number.isNaN(savedState.deltaY)) {
4718
+ return savedState.deltaY;
4719
+ }
4720
+ return 0;
4821
4721
  };
4822
4722
 
4823
- const scrollBarNode = {
4824
- childCount: 1,
4825
- className: mergeClassNames(ScrollBar, ScrollBarSmall),
4826
- onPointerDown: HandleScrollBarPointerDown,
4827
- // TODO support pointercapture event
4828
- type: Div
4829
- };
4830
- const scrollBarThumbNode = {
4831
- childCount: 0,
4832
- className: ScrollBarThumb,
4833
- type: Div
4723
+ const restoreState = savedState => {
4724
+ const searchValue = getSavedValue(savedState);
4725
+ const savedDeltaY = getSavedDeltaY(savedState);
4726
+ return {
4727
+ deltaY: savedDeltaY,
4728
+ searchValue
4729
+ };
4834
4730
  };
4835
- const getScrollBarVirtualDom = (scrollBarHeight, scrollBarTop) => {
4836
- const shouldShowScrollbar = scrollBarHeight > 0;
4837
- if (!shouldShowScrollbar) {
4838
- return [];
4731
+
4732
+ const loadContentWithContext = async (context, savedState) => {
4733
+ const {
4734
+ uid
4735
+ } = context.getState();
4736
+ const loadToken = getToken(uid);
4737
+ try {
4738
+ const initialState = context.getState();
4739
+ const {
4740
+ assetDir,
4741
+ platform,
4742
+ width
4743
+ } = initialState;
4744
+ const {
4745
+ deltaY,
4746
+ searchValue: restoredSearchValue
4747
+ } = restoreState(savedState);
4748
+ const size = getViewletSize(width);
4749
+ const scrollSensitivity = getIsFirefox() ? 2.5 : 1;
4750
+ await context.updateState(state => {
4751
+ if (!state.initial) {
4752
+ return state;
4753
+ }
4754
+ return {
4755
+ ...state,
4756
+ deltaY,
4757
+ initial: false,
4758
+ inputSource: Script,
4759
+ scrollSensitivity,
4760
+ searchValue: restoredSearchValue,
4761
+ size
4762
+ };
4763
+ });
4764
+ const allExtensions = await getAllExtensions(assetDir, platform);
4765
+ const normalized = normalizeExtensions(allExtensions, platform, assetDir);
4766
+ await context.updateState(state => ({
4767
+ ...state,
4768
+ allExtensions: normalized
4769
+ }));
4770
+ await handleChangeWithContext(context, {}, false);
4771
+ } finally {
4772
+ finish(uid, loadToken);
4839
4773
  }
4840
- return [scrollBarNode, scrollBarThumbNode];
4841
4774
  };
4842
4775
 
4843
- const getVisibleItem = (item, setSize, itemHeight, minLineY, relative, i, focusedIndex) => {
4844
- // TODO use normal parameters
4845
- const {
4846
- builtin,
4847
- description,
4848
- disabled,
4849
- downloadCount,
4850
- icon,
4851
- id,
4852
- linked,
4853
- name,
4854
- publisher,
4855
- rating,
4856
- status
4857
- } = item;
4776
+ const openSuggest = state => {
4777
+ const completionItems = getCompletionItems(state.searchValue, state.cursorOffset);
4778
+ if (completionItems.length === 0) {
4779
+ return state;
4780
+ }
4858
4781
  return {
4859
- builtin,
4860
- description,
4861
- disabled,
4862
- downloadCount,
4863
- focused: i === focusedIndex,
4864
- icon,
4865
- id,
4866
- index: i,
4867
- linked,
4868
- name,
4869
- posInSet: i + 1,
4870
- publisher,
4871
- rating,
4872
- setSize,
4873
- status,
4874
- top: (i - minLineY) * itemHeight - relative
4782
+ ...state,
4783
+ completionFocusedIndex: 0,
4784
+ completionItems,
4785
+ suggestOpen: true
4875
4786
  };
4876
4787
  };
4877
4788
 
4878
- const getVisible = state => {
4789
+ const getCss = state => {
4879
4790
  const {
4791
+ cursorOffset,
4880
4792
  deltaY,
4881
- focusedIndex,
4793
+ headerHeight,
4882
4794
  itemHeight,
4883
- items,
4884
- maxLineY,
4885
- minLineY
4795
+ scrollBarHeight,
4796
+ scrollBarY,
4797
+ width
4886
4798
  } = state;
4887
- const setSize = items.length;
4888
- const visible = [];
4889
- const relative = deltaY % itemHeight;
4890
- for (let i = minLineY; i < maxLineY; i++) {
4891
- const item = items[i];
4892
- visible.push(getVisibleItem(item, setSize, itemHeight, minLineY, relative, i, focusedIndex));
4893
- }
4894
- return visible;
4895
- };
4799
+ const relative = -(deltaY % itemHeight);
4800
+ const roundedScrollBarY = Math.round(scrollBarY);
4801
+ const maximumCompletionLeft = Math.max(8, width - 168);
4802
+ const completionLeft = Math.min(Math.round(8 + cursorOffset * 7.5), maximumCompletionLeft);
4803
+ const completionTop = Math.max(0, headerHeight - 10);
4804
+ return `.Extensions .ScrollBarThumb {
4805
+ height: ${scrollBarHeight}px;
4806
+ translate: 0 ${roundedScrollBarY}px;
4807
+ }
4896
4808
 
4897
- const contentNode = {
4898
- childCount: 2,
4899
- className: mergeClassNames(Viewlet, List),
4900
- type: Div
4809
+
4810
+ /* TODO: avoid using negative margin. find a better way*/
4811
+ .ExtensionListItem:nth-child(1) {
4812
+ margin-top: ${relative}px;
4813
+ }
4814
+
4815
+ .ExtensionListItem {
4816
+ box-sizing: border-box;
4817
+ position: relative !important;
4818
+ flex-shrink: 0;
4819
+ }
4820
+
4821
+ .ExtensionListItemLinkedIcon {
4822
+ position: absolute;
4823
+ top: 6px;
4824
+ right: 6px;
4825
+ width: 14px;
4826
+ height: 14px;
4827
+ color: var(--WorkbenchForeground, rgb(188, 190, 190));
4828
+ opacity: 0.8;
4829
+ }
4830
+
4831
+ .ExtensionListItemDisabled:not(.ExtensionActive) {
4832
+ background: color-mix(in srgb, var(--SideBarBackground, rgb(30, 35, 36)) 95%, black);
4833
+ color: var(--ExtensionDisabledForeground, color-mix(in srgb, var(--WorkbenchForeground) 70%, black));
4834
+ }
4835
+
4836
+ .Extensions .ListItems {
4837
+ display: flex;
4838
+ flex-direction: column;
4839
+ gap: 0;
4840
+ overflow-y: hidden;
4841
+ }
4842
+
4843
+ .ExtensionHeader {
4844
+ contain: layout style;
4845
+ position: relative;
4846
+ z-index: 1;
4847
+ }
4848
+
4849
+ .ExtensionListItemFooter {
4850
+ justify-content: flex-end;
4851
+ padding-right: 2px;
4852
+ }
4853
+
4854
+ .ExtensionListItemAuthorName {
4855
+ flex: 1;
4856
+ }
4857
+
4858
+ .ExtensionActions {
4859
+ display: flex;
4860
+ gap: 6px;
4861
+ }
4862
+
4863
+ .ExtensionActionButton {
4864
+ padding: 0 5px;
4865
+ }
4866
+
4867
+ .ExtensionSearchCompletionWidget {
4868
+ position: absolute;
4869
+ left: ${completionLeft}px;
4870
+ top: ${completionTop}px;
4871
+ width: min(320px, calc(100% - ${completionLeft + 8}px));
4872
+ max-height: 240px;
4873
+ overflow-y: auto;
4874
+ z-index: 10;
4875
+ box-sizing: border-box;
4876
+ border: 1px solid var(--CompletionListBorder, #95a29d);
4877
+ background: var(--CompletionListBackground, #282e2f);
4878
+ color: var(--CompletionListForeground, white);
4879
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.36);
4880
+ font-size: 13px;
4881
+ line-height: 20px;
4882
+ user-select: none;
4883
+ }
4884
+
4885
+ .ExtensionSearchCompletionItem {
4886
+ display: block;
4887
+ width: 100%;
4888
+ min-height: 20px;
4889
+ padding: 0 6px;
4890
+ border: 0;
4891
+ background: transparent;
4892
+ color: inherit;
4893
+ font: inherit;
4894
+ line-height: inherit;
4895
+ text-align: left;
4896
+ overflow: hidden;
4897
+ text-overflow: ellipsis;
4898
+ white-space: nowrap;
4899
+ cursor: pointer;
4900
+ }
4901
+
4902
+ .ExtensionSearchCompletionItem:hover {
4903
+ background: var(--CompletionListItemHoverBackground, rgba(64, 92, 80, 0.2));
4904
+ }
4905
+
4906
+ .ExtensionSearchCompletionItemFocused {
4907
+ background: var(--CompletionListItemActiveBackground, #405c50);
4908
+ color: var(--CompletionListItemActiveForeground);
4909
+ }
4910
+
4911
+ .ExtensionSearchCompletionHighlight {
4912
+ color: var(--CompletionHighlightForeground, #e1b974);
4913
+ font-weight: 700;
4914
+ }
4915
+ `;
4901
4916
  };
4902
- const extensionsNode = {
4903
- ariaBusy: false,
4904
- ariaLive: 'polite',
4905
- childCount: 2,
4906
- className: mergeClassNames(Viewlet, Extensions),
4907
- role: None$3,
4908
- type: Div
4917
+
4918
+ const renderCss = newState => {
4919
+ const {
4920
+ uid
4921
+ } = newState;
4922
+ const css = getCss(newState);
4923
+ return [SetCss, uid, css];
4909
4924
  };
4910
- const getContentVirtualDom = (visibleExtensions, message, scrollBarHeight, scrollBarY, focusOutline) => {
4911
- if (message) {
4912
- return getNoExtensionsFoundVirtualDom(message);
4925
+
4926
+ const getSelector = focus => {
4927
+ switch (focus) {
4928
+ case Input:
4929
+ return `[name="${Extensions}"]`;
4930
+ case List$2:
4931
+ return '.ListItems';
4932
+ default:
4933
+ return '';
4913
4934
  }
4914
- return [contentNode, ...getExtensionsVirtualDom(visibleExtensions, focusOutline), ...getScrollBarVirtualDom(scrollBarHeight)];
4915
4935
  };
4916
- const getExtensionsViewVirtualDom = state => {
4917
- const visibleExtensions = getVisible(state);
4936
+ const renderFocus = newState => {
4918
4937
  const {
4919
- completionFocusedIndex,
4920
- completionItems,
4921
4938
  focus,
4922
- focusedIndex,
4923
- inputActions,
4924
- message,
4925
- placeholder,
4926
- scrollBarHeight,
4927
- scrollBarY,
4928
- suggestOpen
4929
- } = state;
4930
- const focusOutline = focusedIndex === -1 && focus === List$2;
4931
- return [extensionsNode, ...getExtensionHeaderVirtualDom(placeholder, inputActions, completionItems, completionFocusedIndex, suggestOpen), ...getContentVirtualDom(visibleExtensions, message, scrollBarHeight, scrollBarY, focusOutline)];
4939
+ uid
4940
+ } = newState;
4941
+ if (!focus) {
4942
+ return [];
4943
+ }
4944
+ const selector = getSelector(focus);
4945
+ return [FocusSelector, uid, selector];
4932
4946
  };
4933
4947
 
4934
- const renderItems2 = newState => {
4948
+ const renderFocusContext = newState => {
4935
4949
  const {
4936
- initial,
4937
4950
  uid
4938
4951
  } = newState;
4939
- if (initial) {
4940
- return [SetDom2, uid, []];
4952
+ if (newState.focus === Input) {
4953
+ return ['Viewlet.setFocusContext', uid, FocusExtensionsInput];
4941
4954
  }
4942
- const dom = getExtensionsViewVirtualDom(newState);
4943
- return [SetDom2, uid, dom];
4955
+ if (newState.focus === List$2) {
4956
+ return ['Viewlet.setFocusContext', uid, FocusExtensions];
4957
+ }
4958
+ return [];
4959
+ };
4960
+
4961
+ const renderHeader = newState => {
4962
+ const actions = getInputActions(newState.searchValue.length > 0);
4963
+ const dom = getExtensionHeaderVirtualDom(newState.placeholder, actions, newState.completionItems, newState.completionFocusedIndex, newState.suggestOpen);
4964
+ return ['setHeaderDom', dom];
4944
4965
  };
4945
4966
 
4946
4967
  // TODO cache rendered dom so that it can be used for dom diffing
@@ -4980,7 +5001,7 @@ const renderScrollBar = newState => {
4980
5001
  };
4981
5002
 
4982
5003
  const renderSearchValue = newState => {
4983
- return [/* method */'Viewlet.setValueByName', newState.uid, Extensions$1, newState.searchValue];
5004
+ return [/* method */'Viewlet.setValueByName', newState.uid, Extensions, newState.searchValue];
4984
5005
  };
4985
5006
 
4986
5007
  const titleFilters = [{
@@ -5102,7 +5123,7 @@ const getIconVirtualDom = (icon, type = Div) => {
5102
5123
  return {
5103
5124
  childCount: 0,
5104
5125
  className: mergeClassNames(MaskIcon, `MaskIcon${icon}`),
5105
- role: None,
5126
+ role: None$1,
5106
5127
  type
5107
5128
  };
5108
5129
  };
@@ -5333,6 +5354,7 @@ const commandMap = {
5333
5354
  'SearchExtensions.enable': wrapCommand(enable$1),
5334
5355
  'SearchExtensions.enableWorkspace': wrapCommand(enableWorkspace),
5335
5356
  'SearchExtensions.filterByBuiltin': wrapAsyncCommand(createFilterCommand(Builtin)),
5357
+ 'SearchExtensions.filterByCategory': wrapAsyncCommand(filterByValueWithContext),
5336
5358
  'SearchExtensions.filterByDisabled': wrapAsyncCommand(createFilterCommand(Disabled$2)),
5337
5359
  'SearchExtensions.filterByEnabled': wrapAsyncCommand(createFilterCommand(Enabled$1)),
5338
5360
  'SearchExtensions.filterByFeatured': wrapAsyncCommand(createFilterCommand(Featured)),
@@ -5353,6 +5375,7 @@ const commandMap = {
5353
5375
  'SearchExtensions.focusPreviousPage': wrapCommand(focusPreviousPage),
5354
5376
  'SearchExtensions.getActions': getActions,
5355
5377
  'SearchExtensions.getCommandIds': getCommandIds,
5378
+ 'SearchExtensions.getComponentDom': getComponentDom,
5356
5379
  'SearchExtensions.getComponentState': getComponentState,
5357
5380
  'SearchExtensions.getKeyBindings': getKeyBindings,
5358
5381
  'SearchExtensions.getMenuEntries': getMenuEntriesList,