diff --git a/src/vs/workbench/contrib/positronConnections/browser/components/listConnections.css b/src/vs/workbench/contrib/positronConnections/browser/components/listConnections.css new file mode 100644 index 00000000000..f6d234a624f --- /dev/null +++ b/src/vs/workbench/contrib/positronConnections/browser/components/listConnections.css @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ + +.connections-list-container { + height: 100%; + position: relative; + left: 0.5px; +} + +.connections-list-header { + display: flex; + align-items: center; + border-bottom: 1px solid var(--vscode-positronVariables-border); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.connections-list-header .vertical-splitter { + background-color: var(--vscode-positronVariables-border); +} + +.positron-connections-list .action-bar-button.disabled { + cursor: not-allowed; +} + +.positron-connections-list .action-bar-button.disabled .action-bar-button-text { + color: var(--vscode-positronSideActionBar-disabledForeground); +} + +.connections-list-item { + margin-top: 5px; + display: flex; + align-items: center; +} + +.connections-list-container .col-icon, +.connections-list-container .col-name, +.connections-list-container .col-language, +.connections-list-container .col-status, +.connections-list-container .col-action { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.connections-list-container .col-icon { + flex-shrink: 0; + height: 100%; +} + +.connections-list-container .col-name { + padding-left: 5px; + flex-basis: 35%; + flex-grow: 1; +} + +.connections-list-container .col-status { + padding-left: 5px; + flex-basis: 25%; + flex-shrink: 0; +} + +.connections-list-item .col-status.disabled { + color: var(--vscode-positronSideActionBar-disabledForeground); +} + +.connections-list-container .col-language { + padding-left: 5px; + flex-basis: 20%; + flex-shrink: 0; +} + +.connections-list-container.col-action { + flex-shrink: 0; + height: 100%; +} + +.connections-list-item .col-action { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} + +.connections-list-item.selected { + color: var(--vscode-positronVariables-activeSelectionForeground); + background: var(--vscode-positronVariables-activeSelectionBackground); + outline: 1px; + outline-offset: -1px; +} diff --git a/src/vs/workbench/contrib/positronConnections/browser/components/listConnections.tsx b/src/vs/workbench/contrib/positronConnections/browser/components/listConnections.tsx new file mode 100644 index 00000000000..dade9fee9e6 --- /dev/null +++ b/src/vs/workbench/contrib/positronConnections/browser/components/listConnections.tsx @@ -0,0 +1,168 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ + +import React, { useState, useEffect } from 'react'; +import { ActionBarButton } from 'vs/platform/positronActionBar/browser/components/actionBarButton'; +import { ActionBarRegion } from 'vs/platform/positronActionBar/browser/components/actionBarRegion'; +import { ActionBarSearch } from 'vs/platform/positronActionBar/browser/components/actionBarSearch'; +import { PositronActionBar } from 'vs/platform/positronActionBar/browser/positronActionBar'; +import { PositronActionBarContextProvider } from 'vs/platform/positronActionBar/browser/positronActionBarContext'; +import { ViewsProps } from 'vs/workbench/contrib/positronConnections/browser/positronConnections'; +import { PositronConnectionsServices, usePositronConnectionsContext } from 'vs/workbench/contrib/positronConnections/browser/positronConnectionsContext'; +import { FixedSizeList as List } from 'react-window'; +import 'vs/css!./listConnections'; +import { positronClassNames } from 'vs/base/common/positronUtilities'; +import { languageIdToName } from 'vs/workbench/contrib/positronConnections/browser/components/schemaNavigation'; +import { IPositronConnectionInstance } from 'vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsInstance'; +import { DisposableStore } from 'vs/base/common/lifecycle'; + +export interface ListConnnectionsProps extends ViewsProps { } + +export const ListConnections = (props: React.PropsWithChildren) => { + + const context = usePositronConnectionsContext(); + const { height, setActiveInstanceId } = props; + + const [instances, setInstances] = useState(context.connectionsService.getConnections); + useEffect(() => { + const disposableStore = new DisposableStore(); + disposableStore.add(context.connectionsService.onDidChangeConnections((connections) => { + setInstances(connections); + })); + return () => disposableStore.dispose(); + }, [context.connectionsService]); + + const [selectedInstanceId, setSelectedInstanceId] = useState(undefined); + + const ItemEntry = (props: { index: number; style: any }) => { + const itemProps = instances[props.index]; + + return ( +
setSelectedInstanceId(itemProps.id)} + > +
+
{itemProps.name}
+
+ {itemProps.language_id ? languageIdToName(itemProps.language_id) : ''} +
+
+ {itemProps.active ? 'Connected' : 'Disconected'} +
+
setActiveInstanceId(itemProps.id)} + > +
+
+
+
+ ); + }; + + return ( +
+ { + context.connectionsService.removeConnection(selectedInstanceId); + } : + undefined + } + > + +
+
+
+ +
Connection
+ +
Language
+ +
Status
+ +
+
+ instances[index].id} + > + {ItemEntry} + +
+
+ ); +}; + +const VerticalSplitter = () => { + return ( +
+
+
+ ); +}; + + +const ACTION_BAR_PADDING_LEFT = 8; +const ACTION_BAR_PADDING_RIGHT = 8; +const ACTION_BAR_HEIGHT = 32; + +interface ActionBarProps extends PositronConnectionsServices { + deleteConnectionHandler?: () => void; +} + +const ActionBar = (props: React.PropsWithChildren) => { + + return ( +
+ + + + 'New Connection'} + text='New Connection' + disabled={true} + /> + + + 'Delete Connection'} + text='Delete Connection' + disabled={props.deleteConnectionHandler === undefined} + onPressed={props.deleteConnectionHandler} + /> +
+ +
+
+
+
+
+ ); +}; diff --git a/src/vs/workbench/contrib/positronConnections/browser/components/schemaNavigation.css b/src/vs/workbench/contrib/positronConnections/browser/components/schemaNavigation.css new file mode 100644 index 00000000000..95af3a209c1 --- /dev/null +++ b/src/vs/workbench/contrib/positronConnections/browser/components/schemaNavigation.css @@ -0,0 +1,112 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ + +.connections-items-container { + height: 100%; + position: relative; + left: 0.5px; +} + +.connections-item { + display: flex; + cursor: pointer; + align-items: center; + height: 26px; +} + +.connections-details { + flex-grow: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-family: monospace; + display: flex; + align-items: center; +} + +.connections-dtype { + color: var(--vscode-positronSideActionBar-disabledForeground); + font-weight: 300; +} + +.connections-dtype::before { + content: ":"; + color: var(--vscode-positronVariables-foreground); + margin-left: 3px; + margin-right: 3px; +} + +.connections-language::before { + content: ":"; + color: var(--vscode-positronVariables-foreground); + margin-left: 0px; + margin-right: 6px; +} + +.connections-item .connections-icon { + flex-shrink: 0; + flex-grow: 0; + padding-left: 5px; + padding-right: 5px; +} + +.connections-icon.disabled { + opacity: 0.7; +} + +.connections-item .expand-collapse-area { + width: 26px; + display: flex; + align-items: center; + justify-content: center; +} + +.connections-item.selected { + color: var(--vscode-positronVariables-activeSelectionForeground); + background: var(--vscode-positronVariables-activeSelectionBackground); + outline: 1px; + outline-offset: -1px; +} + +.connection-disabled { + color: var(--vscode-positronSideActionBar-disabledForeground); +} + +.connections-error { + margin-left: 5px; +} + +.connections-error.codicon { + color: var(--vscode-debugConsole-errorForeground); +} + +.connections-instance-details { + display: flex; + align-items: center; + border-bottom: 1px solid var(--vscode-positronVariables-border); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: calc(100% - 2px); +} + +.connections-instance-details .connection-name { + padding-left: 10px; + flex-grow: 1; + flex-basis: 50%; +} + +.connections-instance-details .connection-language { + flex-basis: 20%; +} + +.connections-instance-details .connection-icon { + flex-grow: 0; + flex-shrink: 0; + width: 26px; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/src/vs/workbench/contrib/positronConnections/browser/components/schemaNavigation.tsx b/src/vs/workbench/contrib/positronConnections/browser/components/schemaNavigation.tsx new file mode 100644 index 00000000000..758e4b88540 --- /dev/null +++ b/src/vs/workbench/contrib/positronConnections/browser/components/schemaNavigation.tsx @@ -0,0 +1,304 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ + +import React, { useEffect, useRef, useState, MouseEvent } from 'react'; +import { useStateRef } from 'vs/base/browser/ui/react/useStateRef'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { usePositronConnectionsContext } from 'vs/workbench/contrib/positronConnections/browser/positronConnectionsContext'; +import * as DOM from 'vs/base/browser/dom'; +import { IPositronConnectionEntry } from 'vs/workbench/services/positronConnections/browser/positronConnectionsCache'; +import { ActionBar, ACTION_BAR_HEIGHT as kActionBarHeight } from 'vs/workbench/contrib/positronConnections/browser/components/schemaNavigationActionBar'; +import { FixedSizeList as List } from 'react-window'; +import { positronClassNames } from 'vs/base/common/positronUtilities'; +import 'vs/css!./schemaNavigation'; +import { ViewsProps } from 'vs/workbench/contrib/positronConnections/browser/positronConnections'; + +export interface SchemaNavigationProps extends ViewsProps { } + +export const SchemaNavigation = (props: React.PropsWithChildren) => { + + const context = usePositronConnectionsContext(); + const { height, activeInstanceId } = props; + + // We're required to save the scroll state because browsers will automatically + // scrollTop when an object becomes visible again. + const [, setScrollState, scrollStateRef] = useStateRef(undefined); + const innerRef = useRef(undefined!); + useEffect(() => { + const disposableStore = new DisposableStore(); + disposableStore.add(context.reactComponentContainer.onSaveScrollPosition(() => { + if (innerRef.current) { + setScrollState(DOM.saveParentsScrollTop(innerRef.current)); + } + })); + disposableStore.add(context.reactComponentContainer.onRestoreScrollPosition(() => { + if (scrollStateRef.current) { + if (innerRef.current) { + DOM.restoreParentsScrollTop(innerRef.current, scrollStateRef.current); + } + setScrollState(undefined); + } + })); + return () => disposableStore.dispose(); + }, [context.reactComponentContainer, scrollStateRef, setScrollState]); + + const [selectedId, setSelectedId] = useState(); + const activeInstance = context.connectionsService.getConnections().find(item => item.id === activeInstanceId); + + console.log('connections', context.connectionsService.getConnections()); + console.log('active instance', activeInstance); + console.log('entries', activeInstance?.getEntries()); + + const [childEntries, setEntries] = useState(activeInstance?.getEntries() || []); + + useEffect(() => { + if (!activeInstance) { + return; + } + + const disposableStore = new DisposableStore(); + disposableStore.add(activeInstance.onDidChangeEntries((entries) => { + console.log('Received event', entries); + setEntries(entries); + })); + activeInstance.refreshEntries(); + return () => disposableStore.dispose(); + }, [activeInstance]); + + const entries = childEntries.filter(item => item.level > 0); + + if (!activeInstance) { + // This should not be possible, the active instance must exist. + return ( +
+ context.connectionsService.clearAllConnections()} + backHandler={() => props.setActiveInstanceId(undefined)} + > + +
+ ); + } + + const ItemEntry = (props: ItemEntryProps) => { + const itemProps = entries[props.index]; + + return ( + setSelectedId(itemProps.id)} + style={props.style}> + + ); + }; + + return ( +
+ item.id === selectedId)} + clearAllHandler={() => context.connectionsService.clearAllConnections()} + backHandler={() => props.setActiveInstanceId(undefined)} + > + +
+
+
{activeInstance?.name}
+
{languageIdToName(activeInstance?.language_id)}
+
+ { + activeInstance?.icon ? + activeInstance?.icon : +
+
+ } +
+
+ entries[index].id} + innerRef={innerRef} + > + {ItemEntry} + +
+
+ ); +}; + +interface ItemEntryProps { + index: number; + style: any; +} + +interface PositronConnectionsItemProps { + item: IPositronConnectionEntry; + style?: any; + selected: boolean; + + /** + * What happens when a row is selected? + */ + onSelectedHandler: () => void; +} + +const PositronConnectionsItem = (props: React.PropsWithChildren) => { + + // If the connection is not expandable, we add some more padding. + // Level starts at 1, since 0 is only used for top levek which is shown in the + // secondary action bar. + const padding = (props.item.level - 1) * 10 + (props.item.expanded === undefined ? 26 : 0); + const handleExpand = () => { + if (props.item.onToggleExpandEmitter) { + props.item.onToggleExpandEmitter.fire(); + } + }; + + const icon = (() => { + + if (props.item.icon) { + return props.item.icon; + } + + if (props.item.kind) { + // TODO: we'll probably want backends to implement the casting to a set of known + // types or provide their own icon. + switch (props.item.kind) { + case 'table': + return 'positron-table-connection'; + case 'view': + return 'positron-view-connection'; + case 'database': + return 'positron-database-connection'; + case 'schema': + return 'positron-schema-connection'; + case 'catalog': + return 'positron-catalog-connection'; + case 'field': + switch (props.item.dtype) { + case 'character': + return 'positron-data-type-string'; + case 'integer': + case 'numeric': + return 'positron-data-type-number'; + case 'boolean': + case 'bool': + return 'positron-data-type-boolean'; + default: + return 'positron-data-type-unknown'; + } + } + } + // If kind is not known, then no icon is dplsayed by default. + return ''; + })(); + + const rowMouseDownHandler = (e: MouseEvent) => { + // Consume the event. + e.preventDefault(); + e.stopPropagation(); + + // Handle the event. + switch (e.button) { + // Main button. + case 0: + // TODO: handle ctrl+ click, etc. + props.onSelectedHandler(); + break; + + // Secondary button. + case 2: + // TODO: more options here + props.onSelectedHandler(); + break; + } + }; + + return ( +
+
+ { + props.item.expanded === undefined ? + <> : +
+
+
+
+ } +
+ {props.item.name} + { + props.item.language_id ? + {languageIdToName(props.item.language_id)} : + <> + } + { + props.item.dtype ? + {props.item.dtype} : + <> + } + { + props.item.error ? + : + <> + } +
+
props.item.preview?.()} + > +
+
+
+
+ ); +}; + +export function languageIdToName(id?: string) { + + if (!id) { + return ''; + } + + switch (id) { + case 'python': + return 'Python'; + case 'r': + return 'R'; + default: + return id; + } +} diff --git a/src/vs/workbench/contrib/positronConnections/browser/components/actionBar.tsx b/src/vs/workbench/contrib/positronConnections/browser/components/schemaNavigationActionBar.tsx similarity index 96% rename from src/vs/workbench/contrib/positronConnections/browser/components/actionBar.tsx rename to src/vs/workbench/contrib/positronConnections/browser/components/schemaNavigationActionBar.tsx index 8a17f3b9181..e34c8e54516 100644 --- a/src/vs/workbench/contrib/positronConnections/browser/components/actionBar.tsx +++ b/src/vs/workbench/contrib/positronConnections/browser/components/schemaNavigationActionBar.tsx @@ -37,6 +37,7 @@ interface ActionBarProps { interface ConnectionActionBarProps extends ActionBarProps { selectedEntry: IPositronConnectionEntry | undefined; + backHandler: () => void; clearAllHandler: () => void; } @@ -73,7 +74,12 @@ export const ActionBar = (props: React.PropsWithChildren 'Back'} + onPressed={() => props.backHandler()} + /> + 'Connect'} disabled={connectDisabled} diff --git a/src/vs/workbench/contrib/positronConnections/browser/positronConnections.css b/src/vs/workbench/contrib/positronConnections/browser/positronConnections.css index 9ba2bff26c2..8fcd0e817c9 100644 --- a/src/vs/workbench/contrib/positronConnections/browser/positronConnections.css +++ b/src/vs/workbench/contrib/positronConnections/browser/positronConnections.css @@ -25,82 +25,3 @@ .positron-connections .positron-connections-action-bars { height: min-content; } - -.connections-items-container { - height: 100%; - position: relative; - left: 0.5px; -} - -.connections-item { - display: flex; - cursor: pointer; - align-items: center; - height: 26px; -} - -.connections-details { - flex-grow: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-family: monospace; - display: flex; - align-items: center; -} - -.connections-dtype { - color: var(--vscode-positronSideActionBar-disabledForeground); - font-weight: 300; -} - -.connections-dtype::before { - content: ":"; - color: var(--vscode-positronVariables-foreground); - margin-left: 3px; - margin-right: 3px; -} - -.connections-language::before { - content: ":"; - color: var(--vscode-positronVariables-foreground); - margin-left: 0px; - margin-right: 6px; -} - -.connections-icon { - flex-shrink: 0; - flex-grow: 0; - padding-right: 5px; - padding-left: 5px; -} - -.connections-icon.disabled { - opacity: 0.7; -} - -.connections-item .expand-collapse-area { - width: 26px; - display: flex; - align-items: center; - justify-content: center; -} - -.connections-item.selected { - color: var(--vscode-positronVariables-activeSelectionForeground); - background: var(--vscode-positronVariables-activeSelectionBackground); - outline: 1px; - outline-offset: -1px; -} - -.connection-disabled { - color: var(--vscode-positronSideActionBar-disabledForeground); -} - -.connections-error { - margin-left: 5px; -} - -.connections-error.codicon { - color: var(--vscode-debugConsole-errorForeground); -} diff --git a/src/vs/workbench/contrib/positronConnections/browser/positronConnections.tsx b/src/vs/workbench/contrib/positronConnections/browser/positronConnections.tsx index 0268ca483a0..9060c1083f1 100644 --- a/src/vs/workbench/contrib/positronConnections/browser/positronConnections.tsx +++ b/src/vs/workbench/contrib/positronConnections/browser/positronConnections.tsx @@ -3,43 +3,26 @@ * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. *--------------------------------------------------------------------------------------------*/ -import React, { useEffect, useRef, useState, MouseEvent } from 'react'; - -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ActionBar, ACTION_BAR_HEIGHT as kActionBarHeight } from 'vs/workbench/contrib/positronConnections/browser/components/actionBar'; - -import { FixedSizeList as List } from 'react-window'; - +import React, { useEffect, useState } from 'react'; import 'vs/css!./positronConnections'; -import { IPositronConnectionsService } from 'vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsService'; -import { IReactComponentContainer } from 'vs/base/browser/positronReactRenderer'; +import { SchemaNavigation } from 'vs/workbench/contrib/positronConnections/browser/components/schemaNavigation'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { useStateRef } from 'vs/base/browser/ui/react/useStateRef'; -import * as DOM from 'vs/base/browser/dom'; -import { IPositronConnectionEntry } from 'vs/workbench/services/positronConnections/browser/positronConnectionsCache'; -import { positronClassNames } from 'vs/base/common/positronUtilities'; - -export interface PositronConnectionsProps { - readonly commandService: ICommandService; - readonly configurationService: IConfigurationService; - readonly contextKeyService: IContextKeyService; - readonly contextMenuService: IContextMenuService; - readonly hoverService: IHoverService; - readonly keybindingService: IKeybindingService; - readonly connectionsService: IPositronConnectionsService; - readonly reactComponentContainer: IReactComponentContainer; +import { PositronConnectionsContextProvider, PositronConnectionsServices } from 'vs/workbench/contrib/positronConnections/browser/positronConnectionsContext'; +import { ListConnections } from 'vs/workbench/contrib/positronConnections/browser/components/listConnections'; + +export interface PositronConnectionsProps extends PositronConnectionsServices { } +export interface ViewsProps { + readonly width: number; + readonly height: number; + readonly activeInstanceId: string | undefined; + readonly setActiveInstanceId: (instanceId: string | undefined) => void; } export const PositronConnections = (props: React.PropsWithChildren) => { // This allows us to introspect the size of the component. Which then allows // us to efficiently only render items that are in view. - const [_, setWidth] = React.useState(props.reactComponentContainer.width); + const [width, setWidth] = React.useState(props.reactComponentContainer.width); const [height, setHeight] = React.useState(props.reactComponentContainer.height); useEffect(() => { @@ -51,230 +34,26 @@ export const PositronConnections = (props: React.PropsWithChildren disposableStore.dispose(); }, [props.reactComponentContainer]); - // We're required to save the scroll state because browsers will automatically - // scrollTop when an object becomes visible again. - const [, setScrollState, scrollStateRef] = useStateRef(undefined); - const innerRef = useRef(undefined!); - useEffect(() => { - const disposableStore = new DisposableStore(); - disposableStore.add(props.reactComponentContainer.onSaveScrollPosition(() => { - if (innerRef.current) { - setScrollState(DOM.saveParentsScrollTop(innerRef.current)); - } - })); - disposableStore.add(props.reactComponentContainer.onRestoreScrollPosition(() => { - if (scrollStateRef.current) { - if (innerRef.current) { - DOM.restoreParentsScrollTop(innerRef.current, scrollStateRef.current); - } - setScrollState(undefined); - } - })); - return () => disposableStore.dispose(); - }, [props.reactComponentContainer, scrollStateRef, setScrollState]); - - const [items, setItems] = useState(props.connectionsService.getConnectionEntries); - useEffect(() => { - const disposableStore = new DisposableStore(); - disposableStore.add(props.connectionsService.onDidChangeEntries((entries) => { - setItems(entries); - })); - // First entries refresh - on component mount. - props.connectionsService.refreshConnectionEntries(); - return () => disposableStore.dispose(); - }, [props.connectionsService]); - - const [selectedId, setSelectedId] = useState(); - - const ItemEntry = (props: ItemEntryProps) => { - const itemProps = items[props.index]; + const [activeInstanceId, setActiveInstanceId] = useState(); - return ( - setSelectedId(itemProps.id)} - style={props.style}> - - ); + const viewProps: ViewsProps = { + width, + height, + activeInstanceId, + setActiveInstanceId, }; return (
- item.id === selectedId)} - clearAllHandler={() => props.connectionsService.clearAllConnections()} - > - -
- items[index].id} - innerRef={innerRef} - > - {ItemEntry} - -
-
- ); -}; - -interface ItemEntryProps { - index: number; - style: any; -} - -interface PositronConnectionsItemProps { - item: IPositronConnectionEntry; - style?: any; - selected: boolean; - - /** - * What happens when a row is selected? - */ - onSelectedHandler: () => void; -} - -const PositronConnectionsItem = (props: React.PropsWithChildren) => { - - // If the connection is not expandable, we add some more padding. - const padding = props.item.level * 10 + (props.item.expanded === undefined ? 26 : 0); - const handleExpand = () => { - if (props.item.onToggleExpandEmitter) { - props.item.onToggleExpandEmitter.fire(); - } - }; - - const icon = (() => { - - if (props.item.icon) { - return props.item.icon; - } - - if (props.item.kind) { - // TODO: we'll probably want backends to implement the casting to a set of known - // types or provide their own icon. - switch (props.item.kind) { - case 'table': - return 'positron-table-connection'; - case 'view': - return 'positron-view-connection'; - case 'database': - return 'positron-database-connection'; - case 'schema': - return 'positron-schema-connection'; - case 'catalog': - return 'positron-catalog-connection'; - case 'field': - switch (props.item.dtype) { - case 'character': - return 'positron-data-type-string'; - case 'integer': - case 'numeric': - return 'positron-data-type-number'; - case 'boolean': - case 'bool': - return 'positron-data-type-boolean'; - default: - return 'positron-data-type-unknown'; - } - } - } - // If kind is not known, then no icon is dplsayed by default. - return ''; - })(); - - const rowMouseDownHandler = (e: MouseEvent) => { - // Consume the event. - e.preventDefault(); - e.stopPropagation(); - - // Handle the event. - switch (e.button) { - // Main button. - case 0: - // TODO: handle ctrl+ click, etc. - props.onSelectedHandler(); - break; - - // Secondary button. - case 2: - // TODO: more options here - props.onSelectedHandler(); - break; - } - }; - - return ( -
-
- { - props.item.expanded === undefined ? - <> : -
-
-
-
- } -
- {props.item.name} - { - props.item.language_id ? - {languageIdToName(props.item.language_id)} : - <> - } - { - props.item.dtype ? - {props.item.dtype} : - <> - } + { - props.item.error ? - : - <> + // If no instance is active, just show the list of connections. + // Otherwise, show the schema navigation. + activeInstanceId === undefined ? + : + } -
-
props.item.preview?.()} - > -
+
); }; - -function languageIdToName(id: string) { - switch (id) { - case 'python': - return 'Python'; - case 'r': - return 'R'; - default: - return id; - } -} diff --git a/src/vs/workbench/contrib/positronConnections/browser/positronConnectionsContext.tsx b/src/vs/workbench/contrib/positronConnections/browser/positronConnectionsContext.tsx new file mode 100644 index 00000000000..89b43312af5 --- /dev/null +++ b/src/vs/workbench/contrib/positronConnections/browser/positronConnectionsContext.tsx @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ + +import React, { PropsWithChildren, createContext, useContext } from 'react'; +import { IReactComponentContainer } from 'vs/base/browser/positronReactRenderer'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IPositronConnectionsService } from 'vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsService'; + +export interface PositronConnectionsServices { + readonly commandService: ICommandService; + readonly configurationService: IConfigurationService; + readonly contextKeyService: IContextKeyService; + readonly contextMenuService: IContextMenuService; + readonly hoverService: IHoverService; + readonly keybindingService: IKeybindingService; + readonly connectionsService: IPositronConnectionsService; + readonly reactComponentContainer: IReactComponentContainer; +} + +const PositronConnectionsContext = createContext(undefined!); + +export const PositronConnectionsContextProvider = ( + props: PropsWithChildren +) => { + return ( + + {props.children} + + ); +}; + +export const usePositronConnectionsContext = () => useContext(PositronConnectionsContext); diff --git a/src/vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsInstance.ts b/src/vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsInstance.ts index b762dbfc0bd..15e8c633bcb 100644 --- a/src/vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsInstance.ts +++ b/src/vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsInstance.ts @@ -3,7 +3,8 @@ * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IPositronConnectionEntry } from 'vs/workbench/services/positronConnections/browser/positronConnectionsCache'; export interface ConnectionMetadata { name: string; @@ -27,6 +28,10 @@ export interface IPositronConnectionInstance extends IPositronConnectionItem { connect?(): Promise; disconnect?(): Promise; refresh?(): Promise; + + onDidChangeEntries: Event; + refreshEntries(): Promise; + getEntries(): IPositronConnectionEntry[]; } /*** @@ -50,13 +55,6 @@ export interface IPositronConnectionItem { */ onToggleExpandEmitter?: Emitter; - /** - * Items fire this event whenever their data has changed. - * Eg. The connections is turned off, or some child was expanded. - * It's used to notify the renderer that the item has changed. - */ - onDidChangeDataEmitter: Emitter; - /** * If the item can be previewed, it should implement this method. */ diff --git a/src/vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsService.ts b/src/vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsService.ts index 76f99ce4a88..f76ac5a6c6d 100644 --- a/src/vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsService.ts +++ b/src/vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsService.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IPositronConnectionInstance, IPositronConnectionItem } from 'vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsInstance'; -import { IPositronConnectionEntry } from 'vs/workbench/services/positronConnections/browser/positronConnectionsCache'; +import { IPositronConnectionInstance } from 'vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsInstance'; import { Event } from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; import { INotificationHandle } from 'vs/platform/notification/common/notification'; @@ -17,25 +16,11 @@ export interface IPositronConnectionsService { readonly _serviceBrand: undefined; initialize(): void; addConnection(instance: IPositronConnectionInstance): void; - getConnections(): IPositronConnectionItem[]; + getConnections(): IPositronConnectionInstance[]; closeConnection(id: string): void; + removeConnection(id: string): void; clearAllConnections(): void; - /** - * Returns a flattended list of entries that the service is currently displaying. - */ - getConnectionEntries(): IPositronConnectionEntry[]; - - /** - * Refresh the connections entries cache and fires the onDidChangeEntries event when it's done. - */ - refreshConnectionEntries(): Promise; - - /** - * An event that users can subscribe to receive updates when the flattened list - * of entries changes. - */ - onDidChangeEntries: Event; - + onDidChangeConnections: Event; notify(message: string, severity: Severity): INotificationHandle; } diff --git a/src/vs/workbench/services/positronConnections/browser/mockConnections.ts b/src/vs/workbench/services/positronConnections/browser/mockConnections.ts deleted file mode 100644 index ea8cc49b08b..00000000000 --- a/src/vs/workbench/services/positronConnections/browser/mockConnections.ts +++ /dev/null @@ -1,213 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (C) 2024 Posit Software, PBC. All rights reserved. - * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Emitter, Event } from 'vs/base/common/event'; -import { ConnectionMetadata, IPositronConnectionInstance, IPositronConnectionItem } from 'vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsInstance'; -import { IPositronConnectionsService } from 'vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsService'; - -export class MockedConnectionInstance implements IPositronConnectionInstance { - private _expanded: boolean = false; - - onToggleExpandEmitter: Emitter = new Emitter(); - onToggleExpand: Event = this.onToggleExpandEmitter.event; - children: IPositronConnectionItem[] = []; - metadata: ConnectionMetadata; - error?: string = undefined; - - constructor( - private readonly clientId: string, - readonly onDidChangeDataEmitter: Emitter, - readonly connectionsService: IPositronConnectionsService, - error = 'error initializing' - ) { - this.onToggleExpand(() => { - this._expanded = !this._expanded; - this.onDidChangeDataEmitter.fire(); - }); - - this.error = error; - - this.children = [ - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - new MockedConnectionItem(this.onDidChangeDataEmitter), - ]; - - this.metadata = { - name: 'SQL Lite Connection 1', - language_id: 'mock', - type: this.clientId - }; - } - - getClientId() { - return this.clientId; - } - - async getChildren() { - if (Math.random() > 0.5) { - throw new Error('cannot parse'); - } - return this.children; - } - - async hasChildren() { - return true; - } - - get name() { - return 'SQL Lite Connection 1'; - } - - get kind() { - return 'database'; - } - - get language_id() { - return 'mock'; - } - - get id() { - const host = (this.metadata.host !== undefined) ? this.metadata.host : 'undefined'; - const type = (this.metadata.type !== undefined) ? this.metadata.type : 'undefined'; - const language_id = this.metadata.language_id; - return `host-${host}-type-${type}-language_id-${language_id}`; - } - - async connect() { - // Dummy reconnection. Just creates a new instance with the same id. - this.connectionsService.addConnection(new MockedConnectionInstance( - this.clientId, - this.onDidChangeDataEmitter, - this.connectionsService - )); - } - - get expanded() { - return this._expanded; - } - - _active: boolean = true; - - get active() { - return this._active; - } - - async disconnect() { - this._active = false; - this._expanded = false; - this.error = undefined; - this.onDidChangeDataEmitter.fire(); - } - - async refresh() { - this.children.pop(); - this.onDidChangeDataEmitter.fire(); - } -} - -class MockedConnectionItem implements IPositronConnectionItem { - - expanded_: boolean = false; - active: boolean = true; - id: string = generateUniqueId(); - kind: string = 'table'; - - onToggleExpandEmitter: Emitter = new Emitter(); - onToggleExpand: Event = this.onToggleExpandEmitter.event; - - constructor(readonly onDidChangeDataEmitter: Emitter) { - this.onToggleExpand(() => { - this.expanded_ = !this.expanded_; - this.onDidChangeDataEmitter.fire(); - }); - } - - get name() { - return 'children 1'; - } - - async getChildren() { - return [ - new MockField('mpg', this.onDidChangeDataEmitter), - new MockField('mpa', this.onDidChangeDataEmitter) - ]; - } - - async getIcon() { - return 'database'; - } - - async hasChildren() { - return true; - } - - get expanded() { - return this.expanded_; - } -} - -class MockField implements IPositronConnectionItem { - - active: boolean = true; - id: string = generateUniqueId(); - kind: string = 'field'; - - constructor(readonly _name: string, readonly onDidChangeDataEmitter: Emitter) { - - } - - get name() { - return this._name; - } - - async getIcon() { - return 'database'; - } - - get expanded() { - return undefined; - } - - async hasChildren() { - return false; - } - - async getChildren() { - return []; - } -} - -function generateUniqueId(): string { - return ( - Date.now().toString(36) + Math.random().toString(36).substr(2, 9) - ); -} diff --git a/src/vs/workbench/services/positronConnections/browser/positronConnectionsCache.ts b/src/vs/workbench/services/positronConnections/browser/positronConnectionsCache.ts index 6d6d147c15c..e2ae777795d 100644 --- a/src/vs/workbench/services/positronConnections/browser/positronConnectionsCache.ts +++ b/src/vs/workbench/services/positronConnections/browser/positronConnectionsCache.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import Severity from 'vs/base/common/severity'; import { INotificationHandle } from 'vs/platform/notification/common/notification'; import { IPositronConnectionInstance, IPositronConnectionItem } from 'vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsInstance'; -import { IPositronConnectionsService } from 'vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsService'; +import { IPositronMinimalConnectionService } from 'vs/workbench/services/positronConnections/browser/positronConnectionsInstance'; export interface IPositronConnectionEntry { @@ -24,6 +24,12 @@ export interface IPositronConnectionEntry { */ id: string; + /** + * The id of the root connection. This is the id of the connection + * that is at the top of the hierarchy. + */ + root_id: string; + /** * Wether the connection entry is currently active. */ @@ -88,6 +94,7 @@ class PositronConnectionEntry extends Disposable implements IPositronConnectionE private readonly item: IPositronConnectionItem | IPositronConnectionInstance, private notify: (message: string, severity: Severity) => INotificationHandle, readonly level: number, + readonly root_id: string ) { super(); } @@ -217,7 +224,8 @@ export class PositronConnectionsCache { private _entries: IPositronConnectionEntry[] = []; constructor( - private readonly service: IPositronConnectionsService, + private readonly service: IPositronMinimalConnectionService, + private readonly instance: IPositronConnectionInstance, ) { } get entries(): IPositronConnectionEntry[] { @@ -225,19 +233,22 @@ export class PositronConnectionsCache { } async refreshConnectionEntries() { - const entries = await this.getConnectionsEntries(this.service.getConnections()); + const entries = await this.getConnectionsEntries([this.instance]); this._entries = entries; } - async getConnectionsEntries(items: IPositronConnectionItem[], level = 0) { + async getConnectionsEntries(items: IPositronConnectionItem[], level = 0, root_id: string | undefined = undefined) { const entries: IPositronConnectionEntry[] = []; for (const item of items) { + const id_root = root_id ?? item.id; + const entry = new PositronConnectionEntry( item, (message, severity) => this.service.notify(message, severity), level, + id_root ); entries.push(entry); @@ -260,7 +271,7 @@ export class PositronConnectionsCache { entry.error = err.message; continue; } - const newItems = await this.getConnectionsEntries(children, level + 1); + const newItems = await this.getConnectionsEntries(children, level + 1, id_root); entries.push(...newItems); } } diff --git a/src/vs/workbench/services/positronConnections/browser/positronConnectionsInstance.ts b/src/vs/workbench/services/positronConnections/browser/positronConnectionsInstance.ts index 7cc51248594..e67ccc4b744 100644 --- a/src/vs/workbench/services/positronConnections/browser/positronConnectionsInstance.ts +++ b/src/vs/workbench/services/positronConnections/browser/positronConnectionsInstance.ts @@ -11,16 +11,18 @@ import { ObjectSchema } from 'vs/workbench/services/languageRuntime/common/posit import { IRuntimeSessionService } from 'vs/workbench/services/runtimeSession/common/runtimeSessionService'; import { RuntimeCodeExecutionMode, RuntimeErrorBehavior } from 'vs/workbench/services/languageRuntime/common/languageRuntimeService'; import { ILogService } from 'vs/platform/log/common/log'; +import { IPositronConnectionEntry, PositronConnectionsCache } from 'vs/workbench/services/positronConnections/browser/positronConnectionsCache'; +import { Severity, INotificationHandle } from 'vs/platform/notification/common/notification'; interface PathSchema extends ObjectSchema { dtype?: string; } -interface ConnectionsService { +export interface IPositronMinimalConnectionService { runtimeSessionService: IRuntimeSessionService; logService: ILogService; onDidFocusEmitter: Emitter; - onDidChangeDataEmitter: Emitter; + notify: (message: string, severity: Severity) => INotificationHandle; } class BaseConnectionsInstance extends Disposable { @@ -56,13 +58,15 @@ export class PositronConnectionsInstance extends BaseConnectionsInstance impleme readonly onToggleExpandEmitter: Emitter = new Emitter(); private readonly onToggleExpand: Event = this.onToggleExpandEmitter.event; - readonly onDidChangeDataEmitter: Emitter; + private readonly onDidChangeEntriesEmitter = new Emitter(); + readonly onDidChangeEntries = this.onDidChangeEntriesEmitter.event; - private _expanded: boolean = false; + private _expanded: boolean = true; private _active: boolean = true; private _children: IPositronConnectionItem[] | undefined; + _cache: PositronConnectionsCache; - static async init(metadata: ConnectionMetadata, client: ConnectionsClientInstance, service: ConnectionsService) { + static async init(metadata: ConnectionMetadata, client: ConnectionsClientInstance, service: IPositronMinimalConnectionService) { const object = new PositronConnectionsInstance(metadata, client, service); if (!object.metadata.icon) { try { @@ -79,21 +83,20 @@ export class PositronConnectionsInstance extends BaseConnectionsInstance impleme private constructor( metadata: ConnectionMetadata, private readonly client: ConnectionsClientInstance, - private readonly service: ConnectionsService, + readonly service: IPositronMinimalConnectionService, ) { super(metadata); - - this.onDidChangeDataEmitter = service.onDidChangeDataEmitter; + this._cache = new PositronConnectionsCache(this.service, this); this._register(this.onToggleExpand(() => { this._expanded = !this._expanded; - this.service.onDidChangeDataEmitter.fire(); + this._cache.refreshConnectionEntries(); })); this._register(this.client.onDidClose(() => { this._active = false; this._expanded = false; - this.service.onDidChangeDataEmitter.fire(); + this._cache.refreshConnectionEntries(); })); this._register(this.client.onDidFocus(() => { @@ -101,6 +104,20 @@ export class PositronConnectionsInstance extends BaseConnectionsInstance impleme })); } + getEntries() { + return this._cache.entries; + } + + async refreshEntries() { + try { + await this._cache.refreshConnectionEntries(); + this.onDidChangeEntriesEmitter.fire(this._cache.entries); + } catch (err) { + this.service.notify(`Failed to refresh connection entries: ${err.message}`, Severity.Error); + } + this.onDidChangeEntriesEmitter.fire(this._cache.entries); + } + readonly kind: string = 'database'; async hasChildren() { @@ -115,7 +132,7 @@ export class PositronConnectionsInstance extends BaseConnectionsInstance impleme [item], this.client, this.id, - this.service + this ); })); } @@ -179,7 +196,7 @@ export class PositronConnectionsInstance extends BaseConnectionsInstance impleme return async () => { this._children = undefined; - this.onDidChangeDataEmitter.fire(); + this.refreshEntries(); }; } @@ -191,7 +208,6 @@ export class PositronConnectionsInstance extends BaseConnectionsInstance impleme export class DisconnectedPositronConnectionsInstance extends BaseConnectionsInstance implements IPositronConnectionInstance { constructor( metadata: ConnectionMetadata, - readonly onDidChangeDataEmitter: Emitter, readonly runtimeSessionService: IRuntimeSessionService, ) { super(metadata); @@ -228,6 +244,16 @@ export class DisconnectedPositronConnectionsInstance extends BaseConnectionsInst ); }; } + + getEntries(): IPositronConnectionEntry[] { + return []; + } + + onDidChangeEntries: Event = Event.None; + + async refreshEntries() { + // Do nothing + } } class PositronConnectionItem implements IPositronConnectionItem { @@ -248,10 +274,8 @@ class PositronConnectionItem implements IPositronConnectionItem { onToggleExpandEmitter: Emitter = new Emitter(); private readonly onToggleExpand: Event = this.onToggleExpandEmitter.event; - onDidChangeDataEmitter: Emitter; - - static async init(path: PathSchema[], client: ConnectionsClientInstance, parent_id: string, service: ConnectionsService) { - const object = new PositronConnectionItem(path, client, parent_id, service); + static async init(path: PathSchema[], client: ConnectionsClientInstance, parent_id: string, instance: PositronConnectionsInstance) { + const object = new PositronConnectionItem(path, client, parent_id, instance); let expandable; try { @@ -275,7 +299,7 @@ class PositronConnectionItem implements IPositronConnectionItem { try { object._icon = await object.getIcon(); } catch (err: any) { - service.logService.error(`Failed to get icon for ${object.id}: ${err.message}`); + instance.service.logService.error(`Failed to get icon for ${object.id}: ${err.message}`); } } @@ -290,14 +314,12 @@ class PositronConnectionItem implements IPositronConnectionItem { private readonly path: PathSchema[], private readonly client: ConnectionsClientInstance, private readonly parent_id: string, - private readonly service: ConnectionsService + private readonly instance: PositronConnectionsInstance ) { if (this.path.length === 0) { throw new Error('path must be length > 0'); } - this.onDidChangeDataEmitter = this.service.onDidChangeDataEmitter; - const last_elt = this.path.at(-1)!; this._name = last_elt.name; this._kind = last_elt.kind; @@ -307,7 +329,7 @@ class PositronConnectionItem implements IPositronConnectionItem { if (!(this._expanded === undefined)) { this._expanded = !this._expanded; // Changing the expanded flag will change the data that we want to show. - this.onDidChangeDataEmitter.fire(); + this.instance.refreshEntries(); } }); } @@ -372,7 +394,7 @@ class PositronConnectionItem implements IPositronConnectionItem { [...this.path, item], this.client, this.id, - this.service + this.instance ); })); } diff --git a/src/vs/workbench/services/positronConnections/browser/positronConnectionsService.ts b/src/vs/workbench/services/positronConnections/browser/positronConnectionsService.ts index 1753227d0c3..1e48e324d8a 100644 --- a/src/vs/workbench/services/positronConnections/browser/positronConnectionsService.ts +++ b/src/vs/workbench/services/positronConnections/browser/positronConnectionsService.ts @@ -5,7 +5,6 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IPositronConnectionEntry, PositronConnectionsCache } from 'vs/workbench/services/positronConnections/browser/positronConnectionsCache'; import { ConnectionsClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeConnectionsClient'; import { ConnectionMetadata, IPositronConnectionInstance } from 'vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsInstance'; import { IPositronConnectionsService, POSITRON_CONNECTIONS_VIEW_ID } from 'vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsService'; @@ -22,14 +21,10 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ class PositronConnectionsService extends Disposable implements IPositronConnectionsService { - private readonly _cache: PositronConnectionsCache; readonly _serviceBrand: undefined; - private onDidChangeEntriesEmitter = new Emitter; - onDidChangeEntries: Event = this.onDidChangeEntriesEmitter.event; - - public onDidChangeDataEmitter = new Emitter; - private onDidChangeData = this.onDidChangeDataEmitter.event; + private onDidChangeConnectionsEmitter = new Emitter; + onDidChangeConnections: Event = this.onDidChangeConnectionsEmitter.event; public onDidFocusEmitter = new Emitter; private onDidFocus = this.onDidFocusEmitter.event; @@ -57,11 +52,6 @@ class PositronConnectionsService extends Disposable implements IPositronConnecti this.attachRuntime(runtime); })); - this._cache = new PositronConnectionsCache(this); - this._register(this.onDidChangeData(() => { - this.refreshConnectionEntries(); - })); - const storedConnections: ConnectionMetadata[] = JSON.parse( this.storageService.get('positron-connections', StorageScope.WORKSPACE, '[]') ); @@ -72,7 +62,6 @@ class PositronConnectionsService extends Disposable implements IPositronConnecti const instance = new DisconnectedPositronConnectionsInstance( metadata, - this.onDidChangeDataEmitter, this.runtimeSessionService ); @@ -95,20 +84,6 @@ class PositronConnectionsService extends Disposable implements IPositronConnecti } } - getConnectionEntries() { - const entries = this._cache.entries; - return entries; - } - - async refreshConnectionEntries() { - try { - await this._cache.refreshConnectionEntries(); - this.onDidChangeEntriesEmitter.fire(this._cache.entries); - } catch (err) { - this.notificationService.error(`Failed to refresh connection entries: ${err.message}`); - } - } - getConnections() { return this.connections; } @@ -171,8 +146,7 @@ class PositronConnectionsService extends Disposable implements IPositronConnecti // Whenever a new connection is added we also update the storage this.saveConnectionsState(); - - this.refreshConnectionEntries(); + this.onDidChangeConnectionsEmitter.fire(this.connections); } getConnection(id: string) { @@ -186,6 +160,7 @@ class PositronConnectionsService extends Disposable implements IPositronConnecti if (connection && connection.disconnect) { connection.disconnect(); } + this.onDidChangeConnectionsEmitter.fire(this.connections); // We don't remove the connection from the `_connections` list as // we expect that `connection.disconnect()` will make it inactive. } @@ -195,7 +170,7 @@ class PositronConnectionsService extends Disposable implements IPositronConnecti ids.forEach((id) => { this.removeConnection(id); }); - this.onDidChangeDataEmitter.fire(); + this.onDidChangeConnectionsEmitter.fire(this.connections); } hasConnection(clientId: string) { @@ -210,18 +185,7 @@ class PositronConnectionsService extends Disposable implements IPositronConnecti }); } - private saveConnectionsState() { - this.storageService.store( - 'positron-connections', - this.connections.map((con) => { - return con.metadata; - }), - StorageScope.WORKSPACE, - StorageTarget.USER - ); - } - - private removeConnection(id: string) { + removeConnection(id: string) { const index = this.connections.findIndex((con) => { return con.id === id; }); @@ -237,9 +201,20 @@ class PositronConnectionsService extends Disposable implements IPositronConnecti // if a disconnect method is implemented, we expect it to run onDidChangeDataEmitter // otherwise, we run it ourselves. connection.disconnect(); - } else { - this.onDidChangeDataEmitter.fire(); } + + this.onDidChangeConnectionsEmitter.fire(this.connections); + } + + private saveConnectionsState() { + this.storageService.store( + 'positron-connections', + this.connections.map((con) => { + return con.metadata; + }), + StorageScope.WORKSPACE, + StorageTarget.USER + ); } }