Skip to content

Commit

Permalink
Show unregistered controller identifiers in Tree View
Browse files Browse the repository at this point in the history
  • Loading branch information
marcoroth committed Feb 25, 2024
1 parent a1f746a commit 77ce0b9
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 33 deletions.
98 changes: 75 additions & 23 deletions client/src/controller_tree_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,13 @@ import * as vscode from "vscode"

import { Client } from "./client"

class ControllerTreeItem extends TreeItem {
constructor(identifier: string, path: string) {
super(identifier, TreeItemCollapsibleState.None)
import type { ControllerDefinition, ControllerDefinitionsResponse, ControllerDefinitionsOrigin } from "./requests"

this.tooltip = path
this.id = `${path}-${identifier}`
this.iconPath = new ThemeIcon("outline-view-icon")
this.resourceUri = Uri.parse(`file://${path}`)
type ControllerDefinitionTreeItem = ControllerTreeItem | ControllerDefinitionsStateItem

this.command = {
command: "vscode.open",
title: "Open",
arguments: [this.resourceUri],
}
}
}

export class ControllerTreeView implements TreeDataProvider<ControllerTreeItem>, Disposable {
export class ControllerTreeView implements TreeDataProvider<ControllerDefinitionTreeItem>, Disposable {
private client: Client
private readonly treeView: TreeView<ControllerTreeItem>
private readonly treeView: TreeView<ControllerDefinitionTreeItem>
private readonly subscriptions: Disposable[] = []
private _onDidChangeTreeData: EventEmitter<any> = new EventEmitter<any>()
readonly onDidChangeTreeData: Event<any> = this._onDidChangeTreeData.event
Expand All @@ -60,21 +47,86 @@ export class ControllerTreeView implements TreeDataProvider<ControllerTreeItem>,
this.treeView.dispose()
}

getTreeItem(element: ControllerTreeItem) {
getTreeItem(element: ControllerDefinitionTreeItem) {
return element
}

getChildren(_element?: ControllerTreeItem) {
return this.requestControllerDefinitions()
async getChildren(element?: ControllerDefinitionTreeItem) {
if (element) {
return element.getChildren()
} else {
const response = await this.requestControllerDefinitions()

return [
new ControllerDefinitionsStateItem("Unregistered", [response.unregistered.project, ...response.unregistered.nodeModules]),
new ControllerDefinitionsStateItem("Registered", [response.registered]),
]
}
}

refresh() {
this._onDidChangeTreeData.fire(undefined)
}

private async requestControllerDefinitions(): Promise<ControllerTreeItem[]> {
const controllerDefinitions = await this.client.requestControllerDefinitions()

return controllerDefinitions.map(({ path, identifier }) => new ControllerTreeItem(identifier, path))
private async requestControllerDefinitions(): Promise<ControllerDefinitionsResponse> {
return await this.client.requestControllerDefinitions()
}
}

class ControllerDefinitionsStateItem extends TreeItem {
public children: ControllerDefinitionsOrigin[] = []

constructor(name: string, children: ControllerDefinitionsOrigin[]) {
const collapisbleState = name === "Registered" ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed

super(name, collapisbleState)

this.tooltip = name
this.children = children

const controllersCount = this.children.flatMap(c => c.controllerDefinitions).length
this.description = `(${controllersCount} controller${controllersCount == 1 ? "" : "s"})`
}

getChildren() {
return this.controllerTreeItems.sort((a, b) => a.label.toString().localeCompare(b.label.toString()))
}

private get controllerTreeItems() {
return this.controllerDefinitions.flatMap(([definition, child]) => new ControllerTreeItem(definition, child))
}

private get controllerDefinitions(): [ControllerDefinition, ControllerDefinitionsOrigin][] {
return this.children.map(child => child.controllerDefinitions.map(definition => [definition, child] as [ControllerDefinition, ControllerDefinitionsOrigin])).flat(1)
}
}

class ControllerTreeItem extends TreeItem {
public registered: boolean = false

constructor(item: ControllerDefinition, origin: ControllerDefinitionsOrigin) {
super(item.identifier, TreeItemCollapsibleState.None)

this.id = `${item.path}-${item.identifier}-${item.registered}`
this.tooltip = item.path
this.registered = item.registered
this.iconPath = new ThemeIcon("outline-view-icon")
this.resourceUri = Uri.parse(`file://${item.path}`)
this.contextValue = `controllerDefinition-${item.registered ? "registered" : "unregistered"}`

if (!item.registered) {
this.description = `(${origin.name})`
}

this.command = {
command: "vscode.open",
title: "Open",
arguments: [this.resourceUri],
}
}

getChildren() {
return []
}
}
23 changes: 21 additions & 2 deletions client/src/requests.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
type ControllerDefinition = {
import { Position } from "vscode-languageclient"

export type ControllerDefinition = {
identifier: string
path: string
registered: boolean
position: Position
}

export interface ControllerDefinitionsOrigin {
name: string,
controllerDefinitions: ControllerDefinition[]
}

export interface ProjectControllerDefinitions extends ControllerDefinitionsOrigin {
name: "project",
}

export type ControllerDefinitionsRequest = object
export type ControllerDefinitionsResponse = ControllerDefinition[]
export type ControllerDefinitionsResponse = {
registered: ProjectControllerDefinitions
unregistered: {
project: ProjectControllerDefinitions,
nodeModules: ControllerDefinitionsOrigin[]
}
}
23 changes: 21 additions & 2 deletions server/src/requests.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
type ControllerDefinition = {
import { Position } from "vscode-languageserver"

export type ControllerDefinition = {
identifier: string
path: string
registered: boolean
position: Position
}

export interface ControllerDefinitionsOrigin {
name: string,
controllerDefinitions: ControllerDefinition[]
}

export interface ProjectControllerDefinitions extends ControllerDefinitionsOrigin {
name: "project",
}

export type ControllerDefinitionsRequest = object
export type ControllerDefinitionsResponse = ControllerDefinition[]
export type ControllerDefinitionsResponse = {
registered: ProjectControllerDefinitions
unregistered: {
project: ProjectControllerDefinitions,
nodeModules: ControllerDefinitionsOrigin[]
}
}
63 changes: 57 additions & 6 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ import {
TextDocumentSyncKind,
InitializeResult,
Diagnostic,
Position,
} from "vscode-languageserver/node"

import { Service } from "./service"
import { StimulusSettings } from "./settings"
import { RegisteredController, ControllerDefinition } from "stimulus-parser"

import type { ControllerDefinitionsRequest, ControllerDefinitionsResponse } from "./requests"
import type {
ControllerDefinition as ControllerDefinitionRequestType,
ControllerDefinitionsRequest,
ControllerDefinitionsResponse,
} from "./requests"

let service: Service
const connection = createConnection(ProposedFeatures.all)
Expand Down Expand Up @@ -134,14 +140,59 @@ connection.onCompletionResolve((item) => {
connection.onRequest(
"stimulus-lsp/controllerDefinitions",
async (_request: ControllerDefinitionsRequest): Promise<ControllerDefinitionsResponse> => {
const controllerDefinitions = service.project.registeredControllers.sort((a, b) =>
a.identifier.localeCompare(b.identifier),
)
const sort = (a: ControllerDefinitionRequestType, b: ControllerDefinitionRequestType) =>
a.identifier.localeCompare(b.identifier)

return controllerDefinitions.map(({ path, identifier }) => ({
const mapRegisteredController = ({ path, identifier, classDeclaration: { node } }: RegisteredController) => ({
path,
identifier,
}))
registered: true,
position: Position.create(node?.loc?.start.line || 1, node?.loc?.start.column || 1),
})

const mapControllerDefinition = ({
path,
guessedIdentifier,
classDeclaration: { node },
}: ControllerDefinition) => ({
path,
identifier: guessedIdentifier,
registered: false,
position: Position.create(node?.loc?.start.line || 1, node?.loc?.start.column || 1),
})

const registeredControllerPaths = service.project.registeredControllers.map((c) => c.path)
const unregisteredControllerDefinitions = service.project.controllerDefinitions.filter(
(definition) => !registeredControllerPaths.includes(definition.path),
)

const registered = service.project.registeredControllers.map(mapRegisteredController).sort(sort)
const unregistered = unregisteredControllerDefinitions.map(mapControllerDefinition).sort(sort)

const nodeModules = service.project.detectedNodeModules
.map(({ name, controllerDefinitions }) => ({
name,
controllerDefinitions: controllerDefinitions
.filter((definition) => !registeredControllerPaths.includes(definition.path))
.map(mapControllerDefinition)
.sort(sort),
}))
.filter((m) => m.controllerDefinitions.length > 0)
.sort((a, b) => a.name.localeCompare(b.name))

return {
registered: {
name: "project",
controllerDefinitions: registered,
},
unregistered: {
project: {
name: "project",
controllerDefinitions: unregistered,
},
nodeModules,
},
}
},
)

Expand Down

0 comments on commit 77ce0b9

Please sign in to comment.