Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop injecting Python interpreter and improve Sphinx Process tree #881

Merged
merged 4 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,6 @@
"$tsc-watch"
]
},
{
"label": "Build Tests",
"type": "npm",
"script": "compile-test",
"isBackground": false,
"options": {
"cwd": "${workspaceRoot}/code"
},
"group": "build",
"presentation": {
"panel": "dedicated",
"reveal": "never",
},
"problemMatcher": [
"$tsc-watch"
]
},
{
"label": "Build Docs",
"type": "shell",
Expand Down
1 change: 1 addition & 0 deletions code/changes/881.enchancement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The Sphinx Process tree view now includes details including `esbonio.sphinx.pythonCommand`, `esbonio.sphinx.buildCommand`, the current builder and output files
7 changes: 7 additions & 0 deletions code/changes/881.misc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
**IMPORTANT!**

The VSCode extension will no longer automatically inject the currently active Python interpreter into the configuration sent to the server.
It is now required for all users to set the `esbonio.sphinx.pythonCommand` option, either in your VSCode settings, or in your project's `pyproject.toml` file.

This makes VSCode's behavior more predicable and brings it in line with how other editors behave.
It also encourages the sharing of project configuration settings, which is particuarly useful if you use an [environment manager](https://docs.esbon.io/en/latest/lsp/howto/use-esbonio-with.html)
5 changes: 1 addition & 4 deletions code/guides/bring-your-own-sphinx.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,4 @@ While the `esbonio` language server is bundled as part of the extension, it does

This is because every Sphinx project is unique with its own set of dependencies and required extensions. In order to correctly understand your project `esbonio` needs to use the same Python environment that you use to build your documentation.

The Esbonio extension supports two mechanisms for selecting your Python environment.

1. If the official Python extension is available, by default Esbonio will attempt to use the same environment you have configured for your workspace.
2. Alternatively, you can use the `esbonio.sphinx.pythonCommand` setting to override this behavior.
You can tell Esbonio which environment to use by setting the `esbonio.sphinx.pythonCommand` option. See [this guide](https://docs.esbon.io/en/latest/lsp/howto/use-esbonio-with.html) for some examples
3 changes: 1 addition & 2 deletions code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/glob": "^8.1.0",
"@types/node": "^18",
"@types/vscode": "1.78.0",
"@vscode/vsce": "^2.31.1",
Expand Down Expand Up @@ -439,7 +438,7 @@
"view/item/context": [
{
"command": "esbonio.sphinx.restart",
"when": "view == sphinxProcesses && viewItem == process",
"when": "view == sphinxProcesses && viewItem == sphinxProcess",
"group": "inline"
}
]
Expand Down
31 changes: 1 addition & 30 deletions code/src/node/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,9 +338,6 @@ export class EsbonioClient {
* transport.
*/
private getLanguageClientOptions(config: vscode.WorkspaceConfiguration): LanguageClientOptions {



let documentSelector = config.get<TextDocumentFilter[]>("server.documentSelector")
if (!documentSelector || documentSelector.length === 0) {
documentSelector = Server.DEFAULT_SELECTOR
Expand All @@ -361,8 +358,7 @@ export class EsbonioClient {
return result
}

result.forEach(async (config, i) => {
await this.injectPython(params, i, config)
result.forEach((config) => {
this.stripNulls(config)
})
return result
Expand Down Expand Up @@ -394,31 +390,6 @@ export class EsbonioClient {
}
}

/**
* Inject the user's configured Python interpreter into the configuration.
*/
private async injectPython(params: ConfigurationParams, index: number, config: any) {
if (params.items[index].section !== "esbonio") {
return
}

if (config?.sphinx?.pythonCommand?.length > 0) {
return
}

// User has not explictly configured a Python command, try and inject the
// Python interpreter they have configured for this resource.
let scopeUri: vscode.Uri | undefined
let scope = params.items[index].scopeUri
if (scope) {
scopeUri = vscode.Uri.parse(scope)
}
let python = await this.python.getCmd(scopeUri)
if (python) {
config.sphinx.pythonCommand = python
}
}

private callHandlers(method: string, params: any) {
this.handlers.get(method)?.forEach(handler => {
try {
Expand Down
2 changes: 1 addition & 1 deletion code/src/node/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export async function activate(context: vscode.ExtensionContext) {

let previewManager = new PreviewManager(logger, context, esbonio)
context.subscriptions.push(vscode.window.registerTreeDataProvider(
'sphinxProcesses', new SphinxProcessProvider(esbonio)
'sphinxProcesses', new SphinxProcessProvider(logger, esbonio)
));

let config = vscode.workspace.getConfiguration("esbonio.server")
Expand Down
169 changes: 151 additions & 18 deletions code/src/node/processTreeView.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as vscode from 'vscode'
import { Notifications, Events } from "../common/constants";
import { OutputChannelLogger } from '../common/log'

import { AppCreatedNotification, ClientCreatedNotification, ClientDestroyedNotification, ClientErroredNotification, EsbonioClient, SphinxClientConfig, SphinxInfo } from './client';

/**
Expand All @@ -13,7 +15,7 @@ export class SphinxProcessProvider implements vscode.TreeDataProvider<ProcessTre
private _onDidChangeTreeData: vscode.EventEmitter<ProcessTreeNode | undefined | null | void> = new vscode.EventEmitter<ProcessTreeNode | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<ProcessTreeNode | undefined | null | void> = this._onDidChangeTreeData.event;

constructor(client: EsbonioClient) {
constructor(private logger: OutputChannelLogger, client: EsbonioClient) {
client.addHandler(
Notifications.SPHINX_CLIENT_CREATED,
(params: ClientCreatedNotification) => this.clientCreated(params)
Expand Down Expand Up @@ -52,14 +54,14 @@ export class SphinxProcessProvider implements vscode.TreeDataProvider<ProcessTre
case 'container':
return { label: element.name, collapsibleState: vscode.TreeItemCollapsibleState.Expanded }

case 'process':
case 'sphinxProcess':
let label = 'Starting...'
let icon: vscode.ThemeIcon | undefined = new vscode.ThemeIcon("sync~spin")
let client = this.sphinxClients.get(element.id)!
let tooltip

if (client.state === 'running') {
label = `Sphinx ${client.app?.version}`
label = `Sphinx v${client.app?.version}`
icon = undefined

} else if (client.state === 'errored') {
Expand All @@ -73,11 +75,59 @@ export class SphinxProcessProvider implements vscode.TreeDataProvider<ProcessTre
iconPath: icon,
tooltip: tooltip,
contextValue: element.kind,
collapsibleState: vscode.TreeItemCollapsibleState.Expanded
}

case 'sphinxBuilder':
return {
label: element.name,
iconPath: new vscode.ThemeIcon('book'),
tooltip: new vscode.MarkdownString("**Build Directory**\n\n" + element.uri.fsPath),
contextValue: element.kind,
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed
}

case 'sphinxCommand':
let cmd: string[] = []
element.command.forEach(c => cmd.push(`- ${c}`))

return {
label: element.command.join(' '),
iconPath: new vscode.ThemeIcon('console'),
tooltip: new vscode.MarkdownString(`**Sphinx Command**\n ${cmd.join('\n ')}`),
contextValue: element.kind,
collapsibleState: vscode.TreeItemCollapsibleState.None
}

case 'property':
return { label: 'Prop', collapsibleState: vscode.TreeItemCollapsibleState.None }
case 'python':
let pyCmd: string[] = []
element.command.forEach(c => pyCmd.push(`- ${c}`))

return {
label: element.command.join(' '),
iconPath: vscode.ThemeIcon.File,
tooltip: new vscode.MarkdownString(`**Python Command**\n ${pyCmd.join('\n ')}`),
resourceUri: vscode.Uri.parse('file:///test.py'), // Needed to pull in the icon for Python
contextValue: element.kind,
collapsibleState: vscode.TreeItemCollapsibleState.None
}

case 'directory':
return {
resourceUri: element.uri,
iconPath: vscode.ThemeIcon.Folder,
contextValue: element.kind,
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
}

case 'file':
return {
resourceUri: element.uri,
iconPath: vscode.ThemeIcon.File,
contextValue: element.kind,
command: { command: 'vscode.open', title: `Open ${element.name}`, arguments: [element.uri], },
collapsibleState: vscode.TreeItemCollapsibleState.None
}
}

}
Expand All @@ -90,34 +140,78 @@ export class SphinxProcessProvider implements vscode.TreeDataProvider<ProcessTre
* @param element The element to return children for
* @returns The given element's children
*/
getChildren(element?: ProcessTreeNode): Thenable<ProcessTreeNode[]> {
async getChildren(element?: ProcessTreeNode): Promise<ProcessTreeNode[]> {
const result: ProcessTreeNode[] = []

if (!element) {
for (let process of this.sphinxClients.values()) {

let cwd = process.config.cwd
let node: ProcssContainerNode = { kind: 'container', name: cwd, path: cwd }
let node: ProcessContainerNode = { kind: 'container', name: cwd, path: cwd }
result.push(node)
}

return Promise.resolve(result)
return result
}

switch (element.kind) {
case 'container':
for (let [id, process] of this.sphinxClients.entries()) {
if (element.name === process.config.cwd) {
let node: SphinxProcessNode = { kind: 'process', id: id }
let node: SphinxProcessNode = { kind: 'sphinxProcess', id: id }
result.push(node)
}
}
break
case 'process':
case 'property':
case 'sphinxProcess':
let client = this.sphinxClients.get(element.id)
if (!client) {
break
}

let pythonNode: PythonCommandNode = { kind: 'python', command: client.config.pythonCommand }
result.push(pythonNode)

let commandNode: SphinxCommandNode = { kind: 'sphinxCommand', command: client.config.buildCommand }
result.push(commandNode)

let app = client.app
if (!app) {
break
}

let builderNode: SphinxBuilderNode = {
kind: 'sphinxBuilder',
name: app.builder_name,
uri: vscode.Uri.file(app.build_dir)
}
result.push(builderNode)

break

// Check the build dir for any files/directories
// TODO: Is there a way to insert VSCode's native file tree here?
// It would save having to reimplement it ourselves.
case 'sphinxBuilder':
case 'directory':
let items = await vscode.workspace.fs.readDirectory(element.uri)
for (let [name, type] of items) {
let node: FileNode | DirNode = {
kind: type === vscode.FileType.Directory ? 'directory' : 'file',
name: name,
uri: vscode.Uri.joinPath(element.uri, name)
}
result.push(node)
}
break

// The following node types have no children
case 'sphinxCommand':
case 'python':
case 'file':
}

return Promise.resolve(result)
return result
}

/**
Expand All @@ -126,6 +220,7 @@ export class SphinxProcessProvider implements vscode.TreeDataProvider<ProcessTre
* @param params Information about the newly created client.
*/
private clientCreated(params: ClientCreatedNotification) {
this.logger.debug(`sphinx/clientCreated: ${JSON.stringify(params.config, undefined, 2)}`)
this.sphinxClients.set(params.id, new SphinxProcess(params.config))
this._onDidChangeTreeData.fire()
}
Expand All @@ -136,6 +231,8 @@ export class SphinxProcessProvider implements vscode.TreeDataProvider<ProcessTre
* @param params Information about the newly created app.
*/
private appCreated(params: AppCreatedNotification) {
this.logger.debug(`sphinx/appCreated: ${JSON.stringify(params.application, undefined, 2)}`)

const client = this.sphinxClients.get(params.id)
if (!client) { return }

Expand Down Expand Up @@ -175,32 +272,68 @@ export class SphinxProcessProvider implements vscode.TreeDataProvider<ProcessTre
}
}

type ProcessTreeNode = ProcssContainerNode | SphinxProcessNode | ProcessPropertyNode
type ProcessTreeNode = ProcessContainerNode | SphinxProcessNode | SphinxBuilderNode | SphinxCommandNode | PythonCommandNode | DirNode | FileNode

/**
* Represents a property of the sphinx process
* Represents a Python command
*/
interface ProcessPropertyNode {
kind: 'property'
interface PythonCommandNode {
kind: 'python'
command: string[]
}

/**
* Represents the sphinx process in the tree view
*/
interface SphinxProcessNode {
kind: 'process'
kind: 'sphinxProcess'
id: string
}

/**
* Represents the builder used by the parent sphinx process.
*/
interface SphinxBuilderNode {
kind: 'sphinxBuilder'
name: string
uri: vscode.Uri
}

/**
* Represents the build command used by the parent sphinx process.
*/
interface SphinxCommandNode {
kind: 'sphinxCommand'
command: string[]
}

/**
* Represents a container for the sphinx process
*/
interface ProcssContainerNode {
interface ProcessContainerNode {
kind: 'container'
name: string
path: string
}

/**
* Represents a directory
*/
interface DirNode {
kind: 'directory'
name: string
uri: vscode.Uri
}

/**
* Represents a file node
*/
interface FileNode {
kind: 'file'
name: string
uri: vscode.Uri
}

class SphinxProcess {

/**
Expand Down