@theia/core 1.60.0-next.43 → 1.60.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.
Files changed (60) hide show
  1. package/README.md +6 -6
  2. package/i18n/nls.cs.json +104 -8
  3. package/i18n/nls.de.json +104 -8
  4. package/i18n/nls.es.json +104 -8
  5. package/i18n/nls.fr.json +104 -8
  6. package/i18n/nls.hu.json +104 -8
  7. package/i18n/nls.it.json +104 -8
  8. package/i18n/nls.ja.json +104 -8
  9. package/i18n/nls.json +111 -15
  10. package/i18n/nls.ko.json +104 -8
  11. package/i18n/nls.pl.json +104 -8
  12. package/i18n/nls.pt-br.json +104 -8
  13. package/i18n/nls.ru.json +104 -8
  14. package/i18n/nls.tr.json +104 -8
  15. package/i18n/nls.zh-cn.json +104 -8
  16. package/i18n/nls.zh-tw.json +104 -8
  17. package/lib/browser/catalog.json +150 -26
  18. package/lib/browser/context-menu-renderer.d.ts +2 -1
  19. package/lib/browser/context-menu-renderer.d.ts.map +1 -1
  20. package/lib/browser/context-menu-renderer.js +3 -0
  21. package/lib/browser/context-menu-renderer.js.map +1 -1
  22. package/lib/browser/shell/application-shell.d.ts.map +1 -1
  23. package/lib/browser/shell/application-shell.js +2 -1
  24. package/lib/browser/shell/application-shell.js.map +1 -1
  25. package/lib/browser/shell/tab-bars.d.ts +7 -30
  26. package/lib/browser/shell/tab-bars.d.ts.map +1 -1
  27. package/lib/browser/shell/tab-bars.js +49 -76
  28. package/lib/browser/shell/tab-bars.js.map +1 -1
  29. package/lib/browser/shell/theia-dock-panel.d.ts.map +1 -1
  30. package/lib/browser/shell/theia-dock-panel.js +9 -0
  31. package/lib/browser/shell/theia-dock-panel.js.map +1 -1
  32. package/lib/browser/widget-open-handler.d.ts +1 -1
  33. package/lib/browser/widget-open-handler.d.ts.map +1 -1
  34. package/lib/browser/widget-open-handler.js +2 -2
  35. package/lib/browser/widget-open-handler.js.map +1 -1
  36. package/lib/browser/widgets/widget.d.ts +3 -2
  37. package/lib/browser/widgets/widget.d.ts.map +1 -1
  38. package/lib/browser/widgets/widget.js +1 -3
  39. package/lib/browser/widgets/widget.js.map +1 -1
  40. package/lib/common/path.d.ts.map +1 -1
  41. package/lib/common/path.js +1 -4
  42. package/lib/common/path.js.map +1 -1
  43. package/lib/common/path.spec.js +3 -0
  44. package/lib/common/path.spec.js.map +1 -1
  45. package/lib/node/env-variables/env-variables-server.d.ts.map +1 -1
  46. package/lib/node/env-variables/env-variables-server.js +5 -7
  47. package/lib/node/env-variables/env-variables-server.js.map +1 -1
  48. package/package.json +7 -7
  49. package/src/browser/context-menu-renderer.ts +4 -1
  50. package/src/browser/shell/application-shell.ts +3 -1
  51. package/src/browser/shell/tab-bars.ts +55 -86
  52. package/src/browser/shell/theia-dock-panel.ts +10 -0
  53. package/src/browser/style/index.css +2 -0
  54. package/src/browser/style/tabs.css +2 -2
  55. package/src/browser/style/view-container.css +1 -0
  56. package/src/browser/widget-open-handler.ts +2 -2
  57. package/src/browser/widgets/widget.ts +11 -8
  58. package/src/common/path.spec.ts +4 -0
  59. package/src/common/path.ts +1 -4
  60. package/src/node/env-variables/env-variables-server.ts +5 -6
@@ -20,7 +20,7 @@ import { VirtualElement, h, VirtualDOM, ElementInlineStyle } from '@lumino/virtu
20
20
  import { Disposable, DisposableCollection, MenuPath, notEmpty, SelectionService, CommandService, nls, ArrayUtils } from '../../common';
21
21
  import { ContextMenuRenderer } from '../context-menu-renderer';
22
22
  import { Signal, Slot } from '@lumino/signaling';
23
- import { Message, MessageLoop } from '@lumino/messaging';
23
+ import { Message } from '@lumino/messaging';
24
24
  import { ArrayExt } from '@lumino/algorithm';
25
25
  import { ElementExt } from '@lumino/domutils';
26
26
  import { TabBarToolbarRegistry, TabBarToolbar } from './tab-bar-toolbar';
@@ -697,9 +697,8 @@ export interface TabBarPrivateMethods {
697
697
  */
698
698
  export class ScrollableTabBar extends TabBar<Widget> {
699
699
 
700
- protected scrollBar?: PerfectScrollbar;
700
+ protected scrollBar: PerfectScrollbar | undefined;
701
701
 
702
- protected scrollBarFactory: () => PerfectScrollbar;
703
702
  protected pendingReveal?: Promise<void>;
704
703
  protected isMouseOver = false;
705
704
  protected needsRecompute = false;
@@ -712,11 +711,27 @@ export class ScrollableTabBar extends TabBar<Widget> {
712
711
  protected openTabsContainer: HTMLDivElement;
713
712
  protected openTabsRoot: Root;
714
713
 
715
- constructor(options?: TabBar.IOptions<Widget> & PerfectScrollbar.Options, dynamicTabOptions?: ScrollableTabBar.Options) {
714
+ constructor(options?: TabBar.IOptions<Widget>, protected readonly scrollbarOptions?: PerfectScrollbar.Options, dynamicTabOptions?: ScrollableTabBar.Options) {
716
715
  super(options);
717
- this.scrollBarFactory = () => new PerfectScrollbar(this.scrollbarHost, options);
718
716
  this._dynamicTabOptions = dynamicTabOptions;
719
- this.rewireDOM();
717
+ this.topRow = document.createElement('div');
718
+ this.topRow.classList.add('theia-tabBar-tab-row');
719
+ this.node.appendChild(this.topRow);
720
+
721
+ const contentNode = this.contentNode;
722
+ if (!contentNode) {
723
+ throw new Error('tab bar does not have the content node.');
724
+ }
725
+ this.node.removeChild(contentNode);
726
+ this.contentContainer = document.createElement('div');
727
+ this.contentContainer.classList.add(ScrollableTabBar.Styles.TAB_BAR_CONTENT_CONTAINER);
728
+ this.contentContainer.appendChild(contentNode);
729
+ this.topRow.appendChild(this.contentContainer);
730
+
731
+ this.openTabsContainer = document.createElement('div');
732
+ this.openTabsContainer.classList.add('theia-tabBar-open-tabs');
733
+ this.openTabsRoot = createRoot(this.openTabsContainer);
734
+ this.topRow.appendChild(this.openTabsContainer);
720
735
  }
721
736
 
722
737
  set dynamicTabOptions(options: ScrollableTabBar.Options | undefined) {
@@ -753,39 +768,7 @@ export class ScrollableTabBar extends TabBar<Widget> {
753
768
  (this as unknown as TabBarPrivateMethods)._releaseMouse();
754
769
  }
755
770
 
756
- /**
757
- * Restructures the DOM defined in Lumino.
758
- *
759
- * By default the tabs (`li`) are contained in the `this.contentNode` (`ul`) which is wrapped in a `div` (`this.node`).
760
- * Instead of this structure, we add a container for the `this.contentNode` and for the toolbar.
761
- * The scrollbar will only work for the `ul` part but it does not affect the toolbar, so it can be on the right hand-side.
762
- */
763
- protected rewireDOM(): void {
764
- const contentNode = this.node.getElementsByClassName(ScrollableTabBar.Styles.TAB_BAR_CONTENT)[0];
765
- if (!contentNode) {
766
- throw new Error(`'this.node' does not have the content as a direct child with class name '${ScrollableTabBar.Styles.TAB_BAR_CONTENT}'.`);
767
- }
768
- this.node.removeChild(contentNode);
769
- this.contentContainer = document.createElement('div');
770
- this.contentContainer.classList.add(ScrollableTabBar.Styles.TAB_BAR_CONTENT_CONTAINER);
771
- this.contentContainer.appendChild(contentNode);
772
-
773
- this.topRow = document.createElement('div');
774
- this.topRow.classList.add('theia-tabBar-tab-row');
775
- this.topRow.appendChild(this.contentContainer);
776
-
777
- this.openTabsContainer = document.createElement('div');
778
- this.openTabsContainer.classList.add('theia-tabBar-open-tabs');
779
- this.openTabsRoot = createRoot(this.openTabsContainer);
780
- this.topRow.appendChild(this.openTabsContainer);
781
-
782
- this.node.appendChild(this.topRow);
783
- }
784
-
785
771
  protected override onAfterAttach(msg: Message): void {
786
- if (!this.scrollBar) {
787
- this.scrollBar = this.scrollBarFactory();
788
- }
789
772
  this.node.addEventListener('mouseenter', () => { this.isMouseOver = true; });
790
773
  this.node.addEventListener('mouseleave', () => {
791
774
  this.isMouseOver = false;
@@ -795,14 +778,12 @@ export class ScrollableTabBar extends TabBar<Widget> {
795
778
  });
796
779
 
797
780
  super.onAfterAttach(msg);
781
+ this.scrollBar = new PerfectScrollbar(this.contentContainer, this.scrollbarOptions);
798
782
  }
799
783
 
800
784
  protected override onBeforeDetach(msg: Message): void {
801
785
  super.onBeforeDetach(msg);
802
- if (this.scrollBar) {
803
- this.scrollBar.destroy();
804
- this.scrollBar = undefined;
805
- }
786
+ this.scrollBar?.destroy();
806
787
  }
807
788
 
808
789
  protected override onUpdateRequest(msg: Message): void {
@@ -826,7 +807,7 @@ export class ScrollableTabBar extends TabBar<Widget> {
826
807
  } else {
827
808
  this.needsRecompute = false;
828
809
  if (this.orientation === 'horizontal') {
829
- let availableWidth = this.scrollbarHost.clientWidth;
810
+ let availableWidth = this.contentNode.clientWidth;
830
811
  let effectiveWidth = availableWidth;
831
812
  if (!this.openTabsContainer.classList.contains('lm-mod-hidden')) {
832
813
  availableWidth += this.openTabsContainer.getBoundingClientRect().width;
@@ -890,7 +871,7 @@ export class ScrollableTabBar extends TabBar<Widget> {
890
871
  window.requestAnimationFrame(() => {
891
872
  const tab = this.contentNode.children[index] as HTMLElement;
892
873
  if (tab && this.isVisible) {
893
- const parent = this.scrollbarHost;
874
+ const parent = this.contentNode;
894
875
  if (this.orientation === 'horizontal') {
895
876
  const scroll = parent.scrollLeft;
896
877
  const left = tab.offsetLeft;
@@ -924,25 +905,6 @@ export class ScrollableTabBar extends TabBar<Widget> {
924
905
  this.pendingReveal = result;
925
906
  return result;
926
907
  }
927
-
928
- /**
929
- * Overrides the `contentNode` property getter in LuminoJS' TabBar.
930
- */
931
- // @ts-expect-error TS2611 `TabBar<T>.contentNode` is declared as `readonly contentNode` but is implemented as a getter.
932
- get contentNode(): HTMLUListElement {
933
- return this.node.getElementsByClassName(ToolbarAwareTabBar.Styles.TAB_BAR_CONTENT)[0] as HTMLUListElement;
934
- }
935
-
936
- /**
937
- * Overrides the scrollable host from the parent class.
938
- */
939
- protected get scrollbarHost(): HTMLElement {
940
- return this.tabBarContainer;
941
- }
942
-
943
- protected get tabBarContainer(): HTMLElement {
944
- return this.node.getElementsByClassName(ToolbarAwareTabBar.Styles.TAB_BAR_CONTENT_CONTAINER)[0] as HTMLElement;
945
- }
946
908
  }
947
909
 
948
910
  export namespace ScrollableTabBar {
@@ -953,7 +915,6 @@ export namespace ScrollableTabBar {
953
915
  }
954
916
  export namespace Styles {
955
917
 
956
- export const TAB_BAR_CONTENT = 'lm-TabBar-content';
957
918
  export const TAB_BAR_CONTENT_CONTAINER = 'lm-TabBar-content-container';
958
919
 
959
920
  }
@@ -978,32 +939,54 @@ export class ToolbarAwareTabBar extends ScrollableTabBar {
978
939
  protected toolbar: TabBarToolbar | undefined;
979
940
  protected breadcrumbsContainer: HTMLElement;
980
941
  protected readonly breadcrumbsRenderer: BreadcrumbsRenderer;
942
+ protected dockPanel: TheiaDockPanel;
981
943
 
982
944
  constructor(
983
945
  protected readonly tabBarToolbarRegistry: TabBarToolbarRegistry,
984
946
  protected readonly tabBarToolbarFactory: () => TabBarToolbar,
985
947
  protected readonly breadcrumbsRendererFactory: BreadcrumbsRendererFactory,
986
- options?: TabBar.IOptions<Widget> & PerfectScrollbar.Options,
948
+ options?: TabBar.IOptions<Widget>,
949
+ scrollbarOptions?: PerfectScrollbar.Options,
987
950
  dynamicTabOptions?: ScrollableTabBar.Options
988
951
  ) {
989
- super(options, dynamicTabOptions);
952
+ super(options, scrollbarOptions, dynamicTabOptions);
953
+
990
954
  this.breadcrumbsRenderer = this.breadcrumbsRendererFactory();
991
- this.addBreadcrumbs();
955
+ this.breadcrumbsContainer = document.createElement('div');
956
+ this.breadcrumbsContainer.classList.add('theia-tabBar-breadcrumb-row');
957
+ this.breadcrumbsContainer.appendChild(this.breadcrumbsRenderer.host);
958
+ this.node.appendChild(this.breadcrumbsContainer);
959
+
992
960
  this.toolbar = this.tabBarToolbarFactory();
993
961
  this.toDispose.push(this.tabBarToolbarRegistry.onDidChange(() => this.update()));
994
962
  this.toDispose.push(this.breadcrumbsRenderer);
963
+
964
+ if (!this.breadcrumbsRenderer.active) {
965
+ this.breadcrumbsContainer.style.setProperty('display', 'none');
966
+ } else {
967
+ this.node.classList.add('theia-tabBar-multirow');
968
+ }
995
969
  this.toDispose.push(this.breadcrumbsRenderer.onDidChangeActiveState(active => {
996
- this.node.classList.toggle('theia-tabBar-multirow', active);
997
- if (this.parent) {
998
- MessageLoop.sendMessage(this.parent, new Message('fit-request'));
970
+ if (active) {
971
+ this.breadcrumbsContainer.style.removeProperty('display');
972
+ this.node.classList.add('theia-tabBar-multirow');
973
+ } else {
974
+ this.breadcrumbsContainer.style.setProperty('display', 'none');
975
+ this.node.classList.remove('theia-tabBar-multirow');
976
+ }
977
+ if (this.dockPanel) {
978
+ this.dockPanel.fit();
999
979
  }
1000
980
  }));
1001
- this.node.classList.toggle('theia-tabBar-multirow', this.breadcrumbsRenderer.active);
1002
981
  const handler = () => this.updateBreadcrumbs();
1003
982
  this.currentChanged.connect(handler);
1004
983
  this.toDispose.push(Disposable.create(() => this.currentChanged.disconnect(handler)));
1005
984
  }
1006
985
 
986
+ setDockPanel(panel: TheiaDockPanel): void {
987
+ this.dockPanel = panel;
988
+ }
989
+
1007
990
  protected async updateBreadcrumbs(): Promise<void> {
1008
991
  const current = this.currentTitle?.owner;
1009
992
  const uri = NavigatableWidget.is(current) ? current.getResourceUri() : undefined;
@@ -1062,20 +1045,6 @@ export class ToolbarAwareTabBar extends ScrollableTabBar {
1062
1045
  protected isOver(event: Event, element: Element): boolean {
1063
1046
  return element && event.target instanceof Element && element.contains(event.target);
1064
1047
  }
1065
-
1066
- /**
1067
- * Restructures the DOM defined in Lumino.
1068
- *
1069
- * By default the tabs (`li`) are contained in the `this.contentNode` (`ul`) which is wrapped in a `div` (`this.node`).
1070
- * Instead of this structure, we add a container for the `this.contentNode` and for the toolbar.
1071
- * The scrollbar will only work for the `ul` part but it does not affect the toolbar, so it can be on the right hand-side.
1072
- */
1073
- protected addBreadcrumbs(): void {
1074
- this.breadcrumbsContainer = document.createElement('div');
1075
- this.breadcrumbsContainer.classList.add('theia-tabBar-breadcrumb-row');
1076
- this.breadcrumbsContainer.appendChild(this.breadcrumbsRenderer.host);
1077
- this.node.appendChild(this.breadcrumbsContainer);
1078
- }
1079
1048
  }
1080
1049
 
1081
1050
  /**
@@ -1256,7 +1225,7 @@ export class SideTabBar extends ScrollableTabBar {
1256
1225
  return;
1257
1226
  }
1258
1227
 
1259
- if ((newOverflowingTabs.length !== this.tabsOverflowData?.titles.length ?? 0) ||
1228
+ if ((newOverflowingTabs.length !== (this.tabsOverflowData?.titles.length ?? 0)) ||
1260
1229
  newOverflowingTabs.find((newTitle, i) => newTitle !== this.tabsOverflowData?.titles[i]) !== undefined) {
1261
1230
  this.tabsOverflowData = { titles: newOverflowingTabs, startIndex };
1262
1231
  this.tabsOverflowChanged.emit(this.tabsOverflowData);
@@ -21,6 +21,7 @@ import { Disposable, DisposableCollection } from '../../common/disposable';
21
21
  import { UnsafeWidgetUtilities } from '../widgets';
22
22
  import { CorePreferences } from '../core-preferences';
23
23
  import { Emitter, Event, environment } from '../../common';
24
+ import { ToolbarAwareTabBar } from './tab-bars';
24
25
 
25
26
  export const MAXIMIZED_CLASS = 'theia-maximized';
26
27
  export const ACTIVE_TABBAR_CLASS = 'theia-tabBar-active';
@@ -63,6 +64,15 @@ export class TheiaDockPanel extends DockPanel {
63
64
  this.markAsCurrent(args.currentTitle || undefined);
64
65
  super['_onCurrentChanged'](sender, args);
65
66
  };
67
+
68
+ this['_createTabBar'] = () => {
69
+ // necessary for https://github.com/eclipse-theia/theia/issues/15273
70
+ const tabBar = super['_createTabBar']();
71
+ if (tabBar instanceof ToolbarAwareTabBar) {
72
+ tabBar.setDockPanel(this);
73
+ }
74
+ return tabBar;
75
+ };
66
76
  this['_onTabActivateRequested'] = (sender: TabBar<Widget>, args: TabBar.ITabActivateRequestedArgs<Widget>) => {
67
77
  this.markAsCurrent(args.title);
68
78
  super['_onTabActivateRequested'](sender, args);
@@ -221,6 +221,8 @@ blockquote {
221
221
 
222
222
  .lm-Widget {
223
223
  font-size: var(--theia-ui-font-size1);
224
+ /** We override the contain of lm-Widget to make sure Monaco autocomplete etc. is correctly applied */
225
+ contain: none !important;
224
226
  }
225
227
 
226
228
  .lm-Widget.lm-mod-hidden {
@@ -459,7 +459,7 @@
459
459
  }
460
460
 
461
461
  .theia-tabBar-breadcrumb-row {
462
- min-width: 100%;
462
+ width: 100%;
463
463
  }
464
464
 
465
465
  .lm-TabBar.theia-tabBar-multirow[data-orientation="horizontal"] {
@@ -470,7 +470,7 @@
470
470
  .lm-TabBar[data-orientation="horizontal"] .theia-tabBar-tab-row {
471
471
  display: flex;
472
472
  flex-flow: row nowrap;
473
- min-width: 100%;
473
+ width: 100%;
474
474
  }
475
475
 
476
476
  .lm-TabBar[data-orientation="vertical"] .theia-tabBar-tab-row {
@@ -73,6 +73,7 @@
73
73
  z-index: 10;
74
74
  color: var(--theia-sideBarSectionHeader-foreground);
75
75
  font-weight: 700;
76
+ overflow: hidden;
76
77
  }
77
78
 
78
79
  .lm-Widget > .theia-view-container-part-header {
@@ -90,10 +90,10 @@ export abstract class WidgetOpenHandler<W extends BaseWidget> implements OpenHan
90
90
  */
91
91
  async open(uri: URI, options?: WidgetOpenerOptions): Promise<W> {
92
92
  const widget = await this.getOrCreateWidget(uri, options);
93
- await this.doOpen(widget, options);
93
+ await this.doOpen(widget, uri, options);
94
94
  return widget;
95
95
  }
96
- protected async doOpen(widget: W, options?: WidgetOpenerOptions): Promise<void> {
96
+ protected async doOpen(widget: W, uri: URI, options?: WidgetOpenerOptions): Promise<void> {
97
97
  const op: WidgetOpenerOptions = {
98
98
  mode: 'activate',
99
99
  ...options
@@ -207,10 +207,11 @@ export class BaseWidget extends Widget implements PreviewableWidget {
207
207
  this.toDisposeOnDetach.push(addEventListener(element, type, listener, useCapture));
208
208
  }
209
209
 
210
- protected addKeyListener<K extends keyof HTMLElementEventMap>(
210
+ protected addKeyListener<K extends keyof HTMLElementEventMap = never>(
211
211
  element: HTMLElement,
212
212
  keysOrKeyCodes: KeyCode.Predicate | KeysOrKeyCodes,
213
- action: (event: KeyboardEvent) => boolean | void | Object, ...additionalEventTypes: K[]): void {
213
+ action: EventHandler<K | 'keydown'>,
214
+ ...additionalEventTypes: K[]): void {
214
215
  this.toDisposeOnDetach.push(addKeyListener(element, keysOrKeyCodes, action, ...additionalEventTypes));
215
216
  }
216
217
 
@@ -259,6 +260,8 @@ export function createIconButton(...classNames: string[]): HTMLSpanElement {
259
260
 
260
261
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
261
262
  export type EventListener<K extends keyof HTMLElementEventMap> = (this: HTMLElement, event: HTMLElementEventMap[K]) => any;
263
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
264
+ export type EventHandler<K extends keyof HTMLElementEventMap> = (event: HTMLElementEventMap[K]) => any;
262
265
  export interface EventListenerObject<K extends keyof HTMLElementEventMap> {
263
266
  handleEvent(evt: HTMLElementEventMap[K]): void;
264
267
  }
@@ -277,10 +280,12 @@ export function addEventListener<K extends keyof HTMLElementEventMap>(
277
280
  );
278
281
  }
279
282
 
280
- export function addKeyListener<K extends keyof HTMLElementEventMap>(
283
+ export function addKeyListener<K extends keyof HTMLElementEventMap = never>(
281
284
  element: HTMLElement,
282
285
  keysOrKeyCodes: KeyCode.Predicate | KeysOrKeyCodes,
283
- action: (event: KeyboardEvent) => boolean | void | Object, ...additionalEventTypes: K[]): Disposable {
286
+ action: EventHandler<K | 'keydown'>,
287
+ ...additionalEventTypes: K[]): Disposable {
288
+ type HandledEvent = Parameters<typeof action>[0];
284
289
 
285
290
  const toDispose = new DisposableCollection();
286
291
  const keyCodePredicate = (() => {
@@ -293,7 +298,7 @@ export function addKeyListener<K extends keyof HTMLElementEventMap>(
293
298
  toDispose.push(addEventListener(element, 'keydown', e => {
294
299
  const kc = KeyCode.createKeyCode(e);
295
300
  if (keyCodePredicate(kc)) {
296
- const result = action(e);
301
+ const result = action(e as HandledEvent);
297
302
  if (typeof result !== 'boolean' || result) {
298
303
  e.stopPropagation();
299
304
  e.preventDefault();
@@ -302,9 +307,7 @@ export function addKeyListener<K extends keyof HTMLElementEventMap>(
302
307
  }));
303
308
  for (const type of additionalEventTypes) {
304
309
  toDispose.push(addEventListener(element, type, e => {
305
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
306
- const event = (type as any)['keydown'];
307
- const result = action(event);
310
+ const result = action(e as HandledEvent);
308
311
  if (typeof result !== 'boolean' || result) {
309
312
  e.stopPropagation();
310
313
  e.preventDefault();
@@ -271,6 +271,10 @@ describe('Path', () => {
271
271
  });
272
272
  });
273
273
 
274
+ it('Should not produce joined paths with double initial //', () => {
275
+ expect(new Path('/').join('/something/absolute').toString()).eq('/something/absolute');
276
+ });
277
+
274
278
  const linuxHome = '/home/test-user';
275
279
  const windowsHome = '/C:/Users/test-user';
276
280
 
@@ -215,10 +215,7 @@ export class Path {
215
215
  if (!relativePath) {
216
216
  return this;
217
217
  }
218
- if (this.raw.endsWith(Path.separator)) {
219
- return new Path(this.raw + relativePath);
220
- }
221
- return new Path(this.raw + Path.separator + relativePath);
218
+ return new Path(this.raw + Path.separator + relativePath).normalize();
222
219
  }
223
220
 
224
221
  /**
@@ -55,19 +55,18 @@ export class EnvVariablesServerImpl implements EnvVariablesServer {
55
55
  const dataFolderPath = join(BackendApplicationPath, 'data');
56
56
  const userDataPath = join(dataFolderPath, 'user-data');
57
57
  const dataFolderExists = this.pathExistenceCache[dataFolderPath] ??= await pathExists(dataFolderPath);
58
+ let theiaConfigDir: string;
58
59
  if (dataFolderExists) {
59
60
  const userDataExists = this.pathExistenceCache[userDataPath] ??= await pathExists(userDataPath);
60
- if (userDataExists) {
61
- process.env.THEIA_CONFIG_DIR = userDataPath;
62
- } else {
61
+ if (!userDataExists) {
63
62
  await mkdir(userDataPath);
64
- process.env.THEIA_CONFIG_DIR = userDataPath;
65
63
  this.pathExistenceCache[userDataPath] = true;
66
64
  }
65
+ theiaConfigDir = userDataPath;
67
66
  } else {
68
- process.env.THEIA_CONFIG_DIR = join(homedir(), BackendApplicationConfigProvider.get().configurationFolder);
67
+ theiaConfigDir = join(homedir(), BackendApplicationConfigProvider.get().configurationFolder);
69
68
  }
70
- return FileUri.create(process.env.THEIA_CONFIG_DIR).toString();
69
+ return FileUri.create(theiaConfigDir).toString();
71
70
  }
72
71
 
73
72
  async getExecPath(): Promise<string> {