This repository has been archived by the owner on Dec 15, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 78
[DO NOT MERGE] Global LS Command Registry to register/unregister and execute commands #215
Open
BoykoAlex
wants to merge
2
commits into
atom:master
Choose a base branch
from
BoykoAlex:command-registry
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import {LanguageClientConnection} from '../languageclient'; | ||
import {ServerCapabilities} from 'vscode-languageserver-protocol'; | ||
import {DisposableLike} from 'atom'; | ||
import * as UUID from 'uuid/v4'; | ||
|
||
const GLOBAL: any = global; | ||
|
||
export class CommandAdapter implements DisposableLike { | ||
|
||
private registrations: Map<string, string[]>; | ||
|
||
constructor(private connection: LanguageClientConnection) { | ||
this.registrations = new Map(); | ||
connection.onRegisterCommand(registration => { | ||
if (registration.registerOptions && Array.isArray(registration.registerOptions.commands)) { | ||
this.registerCommands(registration.id, registration.registerOptions.commands); | ||
} | ||
}); | ||
connection.onUnregisterCommand(unregisteration => this.unregisterCommands(unregisteration.id)); | ||
} | ||
|
||
initialize(capabilities: ServerCapabilities) { | ||
if (capabilities.executeCommandProvider && Array.isArray(capabilities.executeCommandProvider.commands)) { | ||
this.registerCommands(UUID(), capabilities.executeCommandProvider.commands) | ||
} | ||
} | ||
|
||
registerCommands(id: string, commands: string[]): void{ | ||
const cmdRegistry = this.getLspCommandRegistry(); | ||
const registeredCommands = commands.filter(cmd => { | ||
const handler = (params: any[]) => this.connection.executeCommand({ | ||
command: cmd, | ||
arguments: params | ||
}); | ||
if (cmdRegistry.register(cmd, handler)) { | ||
return true; | ||
} else { | ||
console.error(`Trying to register duplicate command: "${cmd}"`) | ||
} | ||
}); | ||
if (this.registrations.has(id)) { | ||
throw new Error(`Duplicate registration id: ${id}`); | ||
} | ||
this.registrations.set(id, registeredCommands); | ||
} | ||
|
||
executeCommand(id: string, params: any[]): Promise<any> { | ||
return this.getLspCommandRegistry().execute(id, params); | ||
} | ||
|
||
unregisterCommands(id: string) { | ||
if (this.registrations.has(id)) { | ||
const commands = this.registrations.get(id); | ||
const cmdRegistry = this.getLspCommandRegistry(); | ||
if (commands && Array.isArray(commands)) { | ||
commands.forEach(command => cmdRegistry.unregister(command)); | ||
} | ||
this.registrations.delete(id); | ||
} | ||
} | ||
|
||
dispose() { | ||
const cmdRegistry = this.getLspCommandRegistry(); | ||
this.registrations.forEach(commands => commands.forEach(command => cmdRegistry.unregister(command))); | ||
this.registrations.clear(); | ||
} | ||
|
||
private getLspCommandRegistry(): LspCommandRegistry { | ||
if (!GLOBAL.lspCommandRegistry) { | ||
GLOBAL.lspCommandRegistry = new LspCommandRegistryImpl(); | ||
} | ||
return <LspCommandRegistry> GLOBAL.lspCommandRegistry; | ||
} | ||
} | ||
|
||
export interface LspCommandRegistry { | ||
register(command: string, handler: (params: any[]) => Promise<any>): boolean; | ||
execute(command: string, params: any[]): Promise<any>; | ||
unregister(command: string): boolean; | ||
} | ||
|
||
class LspCommandRegistryImpl implements LspCommandRegistry { | ||
|
||
private commandIdToHandler: Map<string, (params: any[]) => Promise<any>>; | ||
|
||
constructor() { | ||
this.commandIdToHandler = new Map(); | ||
} | ||
|
||
register(command: string, handler: (params: any[]) => Promise<any>): boolean { | ||
if (this.commandIdToHandler.has(command)) { | ||
return false; | ||
} else { | ||
this.commandIdToHandler.set(command, handler); | ||
return true; | ||
} | ||
} | ||
|
||
execute(command: string, params: any[]): Promise<any> { | ||
if (this.commandIdToHandler.has(command)) { | ||
const handler = this.commandIdToHandler.get(command); | ||
if (handler) { | ||
return handler(params); | ||
} else { | ||
throw new Error(`Command "${command}" has no handler`); | ||
} | ||
} else { | ||
throw new Error(`Command "${command}" is not registered`); | ||
} | ||
} | ||
|
||
unregister(command: string): boolean { | ||
return this.commandIdToHandler.delete(command); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be per-server? Is there an advantage to making it available globally to all instances of atom-languageclient?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is an advantage to it. If this registry is global then it is possible to make language servers communicate between each other. The communication to JDT LS is great for the case of Spring Tools because it's mostly an add-on over the Java tooling. Information spring tools may want from JDT LS is classpath, search, javadoc etc.
Example of communication between Spring Tools LS and JDT LS
I have extended LSP with
spring/javadoc
request message with java artifact binding key and project uri as parameters for the message.The message is received on my Atom language client extension.
Access global command registry and look for command with id
jdt/javadoc
for example which would be the command registered by JDT LS (ide-java
package)Execute the command and pass the binding key and project URI as parameters to the
jdt/javadoc
commandThe command execution goes to the JDT LS and comes back with the result via LSP messages
The javadoc content is extracted from the result of the command execution and sent as a reply to the initial
spring/javadoc
commandThus, I have javadoc in my spring tools LS from JDT LS at the end of this
There is a related PR for the
ide-java
package that uses the global registry: atom/ide-java#79Nevertheless, I understand your concern with the global registry... It also introduces new API to work with the registry... I wish there was something in Atom core that could be utilized for this purpose.