import { ILanguageServerManager, LanguageServerManager } from '@jupyterlab/lsp'; import { ISettingRegistry, ISchemaValidator } from '@jupyterlab/settingregistry'; import { TranslationBundle } from '@jupyterlab/translation'; import { FormComponent } from '@jupyterlab/ui-components'; import { ReadonlyPartialJSONObject } from '@lumino/coreutils'; import { IChangeEvent } from '@rjsf/core'; import { FieldProps, ObjectFieldTemplateProps, TemplatesType } from '@rjsf/utils'; import validatorAjv8 from '@rjsf/validator-ajv8'; import React, { useState } from 'react'; import { TLanguageServerId, TLanguageServerSpec } from '../tokens'; import { ServerLinksList } from './utils'; namespace LanguageServerSettingsEditor { export interface IProps extends FieldProps { settingRegistry: ISettingRegistry; languageServerManager: ILanguageServerManager; trans: TranslationBundle; validationErrors: ISchemaValidator.IError[]; } // TODO export type IState = any; } export const renderCollapseConflicts = (props: { conflicts: Record>; trans: TranslationBundle; }) => { const conflicts = Object.entries(props.conflicts).map( ([server, serverConflicts]) => { if (Object.keys(serverConflicts).length === 0) { return null; } const listing = Object.entries(serverConflicts).map(([key, values]) => (
  • {key}: {JSON.stringify(values)}
  • )); return (

    {server}

    ); } ); return (
    {props.trans.__('Multiple distinct values detected for:')} {conflicts} {props.trans.__( 'Retaining the last value for each of the settings. Please remove the additional values in JSON Settings Editor.' )}
    ); }; export const renderLanguageServerSettings = ( props: LanguageServerSettingsEditor.IProps ) => { return ; }; export class LanguageServerSettings extends React.Component< LanguageServerSettingsEditor.IProps, LanguageServerSettingsEditor.IState > { constructor(props: LanguageServerSettingsEditor.IProps) { super(props); this.state = { ...props.formData }; this._objectTemplate = TabbedObjectTemplateFactory({ baseTemplate: this.props.registry.templates.ObjectFieldTemplate, objectSelector: props => { return ( props.title === this.props.schema.title && props.description === this.props.schema.description ); }, trans: this.props.trans, languageServerManager: this.props.languageServerManager }); } render(): JSX.Element { this.props.schema.description = undefined; // hide the boilerplate title/description from schema definitions for (const serverSchema of Object.values(this.props.schema.properties!)) { // note: have to be strings. (serverSchema as any).title = ''; (serverSchema as any).description = ''; } const validationErrors = this.props.validationErrors.map(error => (
  • {error.keyword}: {error.message} in{' '} {error.instancePath} {error.params && 'allowedValues' in error.params ? this.props.trans.__( 'allowed values: %1', JSON.stringify(error.params.allowedValues) ) : null}
  • )); const templates = { ...this.props.registry.templates, ObjectFieldTemplate: this._objectTemplate }; // remove self field to avoid infinite recursion const fields = Object.fromEntries( Object.entries(this.props.registry.fields).filter( f => f[0] != 'language_servers' ) ); return (

    {this.props.trans.__('Language servers')}

    {validationErrors.length > 0 ? (

    {this.props.trans.__( 'Validation of user settings for language server failed' )}

    {this.props.trans.__( 'Your language server settings do not follow current schema. The LSP configuration graphical interface will run in schema-free mode to enable you to continue using the current settings as-is (in case if the schema is outdated). If this is however an earlier configuration mistake (settings were not validated in earlier versions of jupyterlab-lsp), please correct the following validation errors in JSON Settings Editor, save, and reload application:' )}

      {validationErrors}
    ) : null} of objects<; // the issue is in lines: https://github.com/jupyterlab/jupyterlab/blob/c2907074e58725942946a73a823fc60e1795da39/packages/settingeditor/src/SettingsFormEditor.tsx#L254-L272 // this is because 1) the schemaIds does not include the object key // 2) the code assumes all objects on the same have the same defaults // Probably the solution is to perform modification check on the level of ObjectFieldTemplate instead; this should be implemented upstream in JupyterLab. // TODO templates={templates} uiSchema={this.props.uiSchema} fields={fields} formContext={this.props.formContext} liveValidate idPrefix={this.props.idPrefix + '_language_servers'} onChange={this._onChange.bind(this)} />
    ); } private _onChange(event: IChangeEvent): void { if (event.errors.length) { console.error('Errors in form validation:', event.errors); } this.setState(event.formData, () => this.props.onChange(this.state)); } private _objectTemplate: React.FC; } /** * Template for tabbed interface. */ const TabbedObjectTemplateFactory = (options: { baseTemplate: TemplatesType['ObjectFieldTemplate']; languageServerManager: ILanguageServerManager; objectSelector: (props: ObjectFieldTemplateProps) => boolean; trans: TranslationBundle; }): React.FC => { const factory = (props: ObjectFieldTemplateProps) => { if (!options.objectSelector(props)) { const BaseTemplate = options.baseTemplate; return ; } const [tab, setTab] = useState( props.properties.length > 0 ? props.properties[0].name : null ); const renderServerLabels = (options: { properties: typeof props.properties; filter: (name: TLanguageServerId) => boolean; title: string; }) => { const retained = options.properties.filter(property => options.filter(property.name as TLanguageServerId) ); if (retained.length == 0) { return null; } return ( <>

    {options.title}

      {retained.map(property => { return (
    • setTab(property.name)} key={'tab-' + property.name} data-tab={property.name} className={property.name === tab ? 'lsp-selected' : undefined} > {property.name}
    • ); })}
    ); }; const renderServerMetadata = (spec: TLanguageServerSpec) => { const workspaceConfig = spec.workspace_configuration as Record< string, any >; return (

    {spec.display_name}

    {workspaceConfig ? (

    {trans.__('Default values set programatically for: ') + Object.keys(workspaceConfig).join(', ')}

    ) : null}
    ); }; const manager = options.languageServerManager; const trans = options.trans; // TODO: expose `specs` upstream return (
    {renderServerLabels({ properties: props.properties, filter: name => manager.sessions.has(name), title: trans.__('Available') })} {renderServerLabels({ properties: props.properties, filter: name => !manager.sessions.has(name), title: trans.__('Not installed') })}
    {(manager as LanguageServerManager).specs.has( tab as TLanguageServerId ) ? renderServerMetadata( (manager as LanguageServerManager).specs.get( tab as TLanguageServerId )! ) : null} {props.properties .filter(property => property.name === tab) .map(property => property.content)}
    ); }; factory.displayName = 'TabbedObjectTemplate'; return factory; };