yoda-language-server 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +5 -3
- data/README.md +50 -48
- data/client/atom/main.js +13 -3
- data/client/vscode/.vscode/launch.json +7 -4
- data/client/vscode/package-lock.json +585 -1454
- data/client/vscode/package.json +10 -7
- data/client/vscode/src/extension.ts +3 -3
- data/client/vscode/src/test/completion.test.ts +39 -0
- data/client/vscode/src/test/helper.ts +38 -0
- data/client/vscode/src/test/index.ts +5 -3
- data/client/vscode/testFixture/completion.rb +1 -0
- data/exe/yoda +1 -20
- data/lib/yoda.rb +2 -1
- data/lib/yoda/commands.rb +34 -0
- data/lib/yoda/commands/base.rb +10 -0
- data/lib/yoda/commands/complete.rb +36 -0
- data/lib/yoda/commands/file_cursor_parsable.rb +29 -0
- data/lib/yoda/{runner → commands}/infer.rb +4 -9
- data/lib/yoda/commands/setup.rb +37 -0
- data/lib/yoda/errors.rb +34 -0
- data/lib/yoda/evaluation/evaluator.rb +2 -0
- data/lib/yoda/server.rb +60 -15
- data/lib/yoda/server/completion_provider.rb +8 -8
- data/lib/yoda/server/definition_provider.rb +8 -8
- data/lib/yoda/server/hover_provider.rb +6 -6
- data/lib/yoda/server/initialization_provider.rb +85 -0
- data/lib/yoda/server/{client_info.rb → session.rb} +6 -2
- data/lib/yoda/server/signature_provider.rb +6 -6
- data/lib/yoda/store/actions.rb +3 -1
- data/lib/yoda/store/actions/build_core_index.rb +44 -0
- data/lib/yoda/store/actions/import_core_library.rb +14 -9
- data/lib/yoda/store/actions/import_gem.rb +98 -0
- data/lib/yoda/store/actions/import_std_library.rb +35 -0
- data/lib/yoda/store/adapters.rb +1 -0
- data/lib/yoda/store/adapters/memory_adapter.rb +81 -0
- data/lib/yoda/store/objects.rb +4 -0
- data/lib/yoda/store/objects/addressable.rb +0 -12
- data/lib/yoda/store/objects/base.rb +4 -14
- data/lib/yoda/store/objects/merger.rb +4 -4
- data/lib/yoda/store/objects/patchable.rb +19 -0
- data/lib/yoda/store/objects/project_status.rb +169 -0
- data/lib/yoda/store/objects/serializable.rb +39 -0
- data/lib/yoda/store/project.rb +28 -114
- data/lib/yoda/store/project/cache.rb +79 -0
- data/lib/yoda/store/project/library_doc_loader.rb +102 -0
- data/lib/yoda/store/query/find_constant.rb +2 -1
- data/lib/yoda/store/registry.rb +15 -0
- data/lib/yoda/store/yard_importer.rb +58 -28
- data/lib/yoda/typing/evaluator.rb +8 -5
- data/lib/yoda/version.rb +1 -1
- data/package.json +32 -11
- data/scripts/benchmark.rb +1 -1
- data/yoda-language-server.gemspec +1 -0
- metadata +37 -7
- data/client/vscode/src/test/extension.test.ts +0 -22
- data/lib/yoda/runner/setup.rb +0 -26
- data/lib/yoda/store/actions/import_gems.rb +0 -91
data/client/vscode/package.json
CHANGED
@@ -3,9 +3,9 @@
|
|
3
3
|
"displayName": "yoda",
|
4
4
|
"description": "Static analytics tool for Ruby",
|
5
5
|
"version": "0.0.1",
|
6
|
-
"publisher": "
|
6
|
+
"publisher": "tomoasleep",
|
7
7
|
"engines": {
|
8
|
-
"vscode": "^1.
|
8
|
+
"vscode": "^1.23.0"
|
9
9
|
},
|
10
10
|
"categories": [
|
11
11
|
"Other"
|
@@ -27,13 +27,16 @@
|
|
27
27
|
"compile": "tsc -p ./",
|
28
28
|
"watch": "tsc -watch -p ./",
|
29
29
|
"postinstall": "node ./node_modules/vscode/bin/install",
|
30
|
+
"update-vscode": "node ./node_modules/vscode/bin/install",
|
30
31
|
"test": "npm run compile && node ./node_modules/vscode/bin/test"
|
31
32
|
},
|
33
|
+
"dependencies": {
|
34
|
+
"vscode": "^1.1.18",
|
35
|
+
"vscode-languageclient": "^4.1.4"
|
36
|
+
},
|
32
37
|
"devDependencies": {
|
33
|
-
"
|
34
|
-
"
|
35
|
-
"
|
36
|
-
"@types/node": "^7.0.43",
|
37
|
-
"@types/mocha": "^2.2.42"
|
38
|
+
"@types/mocha": "^5.2.0",
|
39
|
+
"@types/node": "^8.0.0",
|
40
|
+
"typescript": "^2.9.2"
|
38
41
|
}
|
39
42
|
}
|
@@ -17,7 +17,7 @@ export function activate(context: vscode.ExtensionContext) {
|
|
17
17
|
console.log('Congratulations, your extension "yoda" is now active!');
|
18
18
|
|
19
19
|
let execOptions = {
|
20
|
-
command:
|
20
|
+
command: 'yoda',
|
21
21
|
args: ['server'],
|
22
22
|
}
|
23
23
|
|
@@ -34,9 +34,9 @@ export function activate(context: vscode.ExtensionContext) {
|
|
34
34
|
}
|
35
35
|
}
|
36
36
|
|
37
|
-
let disposable = new LanguageClient('yoda', 'Yoda
|
37
|
+
let disposable = new LanguageClient('yoda', 'Yoda', serverOptions, clientOptions).start();
|
38
38
|
}
|
39
39
|
|
40
40
|
// this method is called when your extension is deactivated
|
41
41
|
export function deactivate() {
|
42
|
-
}
|
42
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import * as vscode from 'vscode';
|
2
|
+
import * as assert from 'assert';
|
3
|
+
import { getDocUri, activate } from './helper';
|
4
|
+
|
5
|
+
describe('Should do completion', () => {
|
6
|
+
const docUri = getDocUri('completion.rb');
|
7
|
+
|
8
|
+
it('Completes', async () => {
|
9
|
+
await testCompletion(docUri, new vscode.Position(0, 2), {
|
10
|
+
items: [
|
11
|
+
{ label: 'Object', kind: vscode.CompletionItemKind.Class },
|
12
|
+
]
|
13
|
+
});
|
14
|
+
})
|
15
|
+
|
16
|
+
|
17
|
+
});
|
18
|
+
|
19
|
+
async function testCompletion(
|
20
|
+
docUri: vscode.Uri,
|
21
|
+
position: vscode.Position,
|
22
|
+
expectedCompletionList: vscode.CompletionList
|
23
|
+
) {
|
24
|
+
await activate(docUri);
|
25
|
+
|
26
|
+
// Executing the command `vscode.executeCompletionItemProvider` to simulate triggering completion
|
27
|
+
const actualCompletionList = (await vscode.commands.executeCommand(
|
28
|
+
'vscode.executeCompletionItemProvider',
|
29
|
+
docUri,
|
30
|
+
position
|
31
|
+
)) as vscode.CompletionList;
|
32
|
+
|
33
|
+
assert.equal(actualCompletionList.items.length, expectedCompletionList.items.length);
|
34
|
+
expectedCompletionList.items.forEach((expectedItem, i) => {
|
35
|
+
const actualItem = actualCompletionList.items[i];
|
36
|
+
assert.equal(actualItem.label, expectedItem.label);
|
37
|
+
assert.equal(actualItem.kind, expectedItem.kind);
|
38
|
+
});
|
39
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import * as vscode from 'vscode';
|
2
|
+
import * as path from 'path';
|
3
|
+
|
4
|
+
export let doc: vscode.TextDocument;
|
5
|
+
export let editor: vscode.TextEditor;
|
6
|
+
export let documentEol: string;
|
7
|
+
export let platformEol: string;
|
8
|
+
|
9
|
+
export async function activate(docUri: vscode.Uri) {
|
10
|
+
const ext = vscode.extensions.getExtension('tomoasleep.yoda');
|
11
|
+
await ext.activate();
|
12
|
+
try {
|
13
|
+
doc = await vscode.workspace.openTextDocument(docUri);
|
14
|
+
editor = await vscode.window.showTextDocument(doc);
|
15
|
+
await sleep(20000); // Wait for server activation
|
16
|
+
} catch (e) {
|
17
|
+
console.error(e);
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
async function sleep(ms: number) {
|
22
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
23
|
+
}
|
24
|
+
|
25
|
+
export const getDocPath = (p: string) => {
|
26
|
+
return path.resolve(__dirname, '../../testFixture', p);
|
27
|
+
};
|
28
|
+
export const getDocUri = (p: string) => {
|
29
|
+
return vscode.Uri.file(getDocPath(p));
|
30
|
+
};
|
31
|
+
|
32
|
+
export async function setTestContent(content: string): Promise<boolean> {
|
33
|
+
const all = new vscode.Range(
|
34
|
+
doc.positionAt(0),
|
35
|
+
doc.positionAt(doc.getText().length)
|
36
|
+
);
|
37
|
+
return editor.edit(eb => eb.replace(all, content));
|
38
|
+
}
|
@@ -15,8 +15,10 @@ import * as testRunner from 'vscode/lib/testrunner';
|
|
15
15
|
// You can directly control Mocha options by uncommenting the following lines
|
16
16
|
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
|
17
17
|
testRunner.configure({
|
18
|
-
ui: '
|
19
|
-
useColors: true // colored output from test results
|
18
|
+
ui: 'bdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
|
19
|
+
useColors: true, // colored output from test results
|
20
|
+
timeout: 600000,
|
21
|
+
|
20
22
|
});
|
21
23
|
|
22
|
-
module.exports = testRunner;
|
24
|
+
module.exports = testRunner;
|
@@ -0,0 +1 @@
|
|
1
|
+
Object
|
data/exe/yoda
CHANGED
@@ -5,23 +5,4 @@ if Dir.exist?(File.join(__dir__, "..", ".git"))
|
|
5
5
|
end
|
6
6
|
|
7
7
|
require 'yoda'
|
8
|
-
|
9
|
-
|
10
|
-
opt = OptionParser.new("Usage: yoda [options]") do |opt|
|
11
|
-
end
|
12
|
-
|
13
|
-
argv = opt.parse(ARGV)
|
14
|
-
|
15
|
-
case argv.first
|
16
|
-
when 'setup'
|
17
|
-
require 'pry'
|
18
|
-
Pry::rescue { Yoda::Runner::Setup.run(argv[1]) }
|
19
|
-
when 'infer'
|
20
|
-
require 'pry'
|
21
|
-
Pry::rescue { Yoda::Runner::Infer.run(argv[1]) }
|
22
|
-
when 'server'
|
23
|
-
Yoda::Server.new.run
|
24
|
-
else
|
25
|
-
puts opt.help
|
26
|
-
exit 1
|
27
|
-
end
|
8
|
+
Yoda::Commands::Top.start(ARGV)
|
data/lib/yoda.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
module Yoda
|
2
2
|
require "yoda/version"
|
3
|
+
require "yoda/commands"
|
4
|
+
require "yoda/errors"
|
3
5
|
require "yoda/evaluation"
|
4
6
|
require "yoda/model"
|
5
7
|
require "yoda/store"
|
6
8
|
require "yoda/server"
|
7
9
|
require "yoda/parsing"
|
8
10
|
require "yoda/typing"
|
9
|
-
require "yoda/runner"
|
10
11
|
require "yoda/yard_extensions"
|
11
12
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Yoda
|
4
|
+
# Commands module has handler for each cli command.
|
5
|
+
module Commands
|
6
|
+
require 'yoda/commands/base'
|
7
|
+
require 'yoda/commands/file_cursor_parsable'
|
8
|
+
require 'yoda/commands/setup'
|
9
|
+
require 'yoda/commands/infer'
|
10
|
+
require 'yoda/commands/complete'
|
11
|
+
|
12
|
+
class Top < Thor
|
13
|
+
desc 'setup', 'Setup indexes for current Ruby version and project gems'
|
14
|
+
def setup
|
15
|
+
Commands::Setup.run
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'infer POSITION', 'Infer the type of value at the specified position'
|
19
|
+
def infer(position)
|
20
|
+
Commands::Infer.run(position)
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'complete POSITION', 'Provide completion candidates for the specified position'
|
24
|
+
def complete(position)
|
25
|
+
Commands::Complete.run(position)
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'server', 'Start Language Server'
|
29
|
+
def server
|
30
|
+
Server.new.run
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Yoda
|
2
|
+
module Commands
|
3
|
+
class Complete < Base
|
4
|
+
include FileCursorParsable
|
5
|
+
|
6
|
+
attr_reader :filename_with_position
|
7
|
+
|
8
|
+
# @param filename_with_position [String] position representation with the format `path/to/file:line_num:character_num`
|
9
|
+
def initialize(filename_with_position)
|
10
|
+
@filename_with_position = filename_with_position
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
project.build_cache
|
15
|
+
puts create_signature_help(worker.current_node_signature)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# @param signature [Model::NodeSignature, nil]
|
21
|
+
# @return [String, nil]
|
22
|
+
def create_signature_help(signature)
|
23
|
+
return nil unless signature
|
24
|
+
signature.descriptions.map(&:title).join("\n")
|
25
|
+
end
|
26
|
+
|
27
|
+
def worker
|
28
|
+
@worker ||= Evaluation::CurrentNodeExplain.new(project.registry, File.read(filename), position)
|
29
|
+
end
|
30
|
+
|
31
|
+
def project
|
32
|
+
@project ||= Store::Project.new(Dir.pwd)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Yoda
|
2
|
+
module Commands
|
3
|
+
# Provide parsing methods of positon representation with the format `path/to/file:line_num:character_num`
|
4
|
+
module FileCursorParsable
|
5
|
+
private
|
6
|
+
|
7
|
+
# Returns a cursor literal to parse.
|
8
|
+
# @abstract
|
9
|
+
# @return [String]
|
10
|
+
def filename_with_position
|
11
|
+
fail NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [String, nil] represents the filename part.
|
15
|
+
def filename
|
16
|
+
@filename ||= filename_with_position.split(':').first
|
17
|
+
end
|
18
|
+
|
19
|
+
# Parse location part of cursor literal and returns the parsed location.
|
20
|
+
# @return [Parsing::Location]
|
21
|
+
def position
|
22
|
+
@position ||= begin
|
23
|
+
row, column = filename_with_position.split(':').slice(1..2)
|
24
|
+
Parsing::Location.new(row: row.to_i, column: column.to_i)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,20 +1,15 @@
|
|
1
1
|
module Yoda
|
2
|
-
module
|
3
|
-
class Infer
|
2
|
+
module Commands
|
3
|
+
class Infer < Base
|
4
4
|
attr_reader :filename_with_position
|
5
5
|
|
6
|
-
# @param filename_with_position [String]
|
7
|
-
def self.run(filename_with_position)
|
8
|
-
new(filename_with_position).run
|
9
|
-
end
|
10
|
-
|
11
|
-
# @param filename_with_position [String]
|
6
|
+
# @param filename_with_position [String] position representation with the format `path/to/file:line_num:character_num`
|
12
7
|
def initialize(filename_with_position)
|
13
8
|
@filename_with_position = filename_with_position
|
14
9
|
end
|
15
10
|
|
16
11
|
def run
|
17
|
-
project.
|
12
|
+
project.build_cache
|
18
13
|
puts create_signature_help(worker.current_node_signature)
|
19
14
|
end
|
20
15
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Yoda
|
2
|
+
module Commands
|
3
|
+
class Setup < Base
|
4
|
+
# @return [String]
|
5
|
+
attr_reader :dir
|
6
|
+
|
7
|
+
# @return [true, false]
|
8
|
+
attr_reader :force_build
|
9
|
+
|
10
|
+
# @param dir [String]
|
11
|
+
def initialize(dir: nil, force_build: false)
|
12
|
+
@dir = dir || Dir.pwd
|
13
|
+
@force_build = force_build
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
build_core_index
|
18
|
+
if File.exist?(File.expand_path('Gemfile.lock', dir)) || force_build
|
19
|
+
STDERR.puts 'Building index for the current project...'
|
20
|
+
force_build ? project.rebuild_cache(progress: true) : project.build_cache(progress: true)
|
21
|
+
else
|
22
|
+
STDERR.puts 'Skipped building project index because Gemfile.lock is not exist for the current dir'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def project
|
27
|
+
@project ||= Store::Project.new(dir)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def build_core_index
|
33
|
+
Store::Actions::BuildCoreIndex.run unless Store::Actions::BuildCoreIndex.exists?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/yoda/errors.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Yoda
|
2
|
+
# @abstract
|
3
|
+
class BaseError < ::StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class GemImportError < BaseError
|
7
|
+
# @return [String]
|
8
|
+
attr_reader :name, :version
|
9
|
+
|
10
|
+
def initialize(name:, version:)
|
11
|
+
@name = name
|
12
|
+
@version = version
|
13
|
+
super(msg)
|
14
|
+
end
|
15
|
+
|
16
|
+
def msg
|
17
|
+
"Failed to import #{name} #{version}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class CoreImportError < BaseError
|
22
|
+
# @return [String]
|
23
|
+
attr_reader :name
|
24
|
+
|
25
|
+
def initialize(name)
|
26
|
+
@name = name
|
27
|
+
super(msg)
|
28
|
+
end
|
29
|
+
|
30
|
+
def msg
|
31
|
+
"Failed to import Ruby #{name} Library"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -76,6 +76,7 @@ module Yoda
|
|
76
76
|
def evaluation_context
|
77
77
|
@evaluation_context ||= begin
|
78
78
|
fail RuntimeError, "The namespace #{scope.scope_name} (#{scope}) is not registered" unless scope_constant
|
79
|
+
fail RuntimeError, "The namespace for #{scope} (#{scope.scope_name}) is not registered" unless receiver
|
79
80
|
lexical_scope = Typing::LexicalScope.new(scope_constant, scope.ancestor_scopes)
|
80
81
|
context = Typing::Context.new(registry: registry, caller_object: receiver, lexical_scope: lexical_scope)
|
81
82
|
context.env.bind_method_parameters(current_method_signature) if current_method_signature
|
@@ -89,6 +90,7 @@ module Yoda
|
|
89
90
|
@current_method_signature ||= Store::Query::FindSignature.new(registry).select(scope_constant, scope.name.to_s)&.first
|
90
91
|
end
|
91
92
|
|
93
|
+
# @return [Store::Objects::Base, nil]
|
92
94
|
def receiver
|
93
95
|
@receiver ||= begin
|
94
96
|
if scope.kind == :method
|
data/lib/yoda/server.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'language_server-protocol'
|
2
|
+
require 'securerandom'
|
2
3
|
|
3
4
|
module Yoda
|
4
5
|
class Server
|
@@ -6,8 +7,9 @@ module Yoda
|
|
6
7
|
require 'yoda/server/signature_provider'
|
7
8
|
require 'yoda/server/hover_provider'
|
8
9
|
require 'yoda/server/definition_provider'
|
10
|
+
require 'yoda/server/initialization_provider'
|
9
11
|
require 'yoda/server/deserializer'
|
10
|
-
require 'yoda/server/
|
12
|
+
require 'yoda/server/session'
|
11
13
|
|
12
14
|
LSP = ::LanguageServer::Protocol
|
13
15
|
|
@@ -21,8 +23,8 @@ module Yoda
|
|
21
23
|
# @type ::LanguageServer::Protocol::Transport::Stdio::Writer
|
22
24
|
attr_reader :writer
|
23
25
|
|
24
|
-
# @
|
25
|
-
attr_reader :
|
26
|
+
# @return [Responser]
|
27
|
+
attr_reader :session
|
26
28
|
|
27
29
|
# @type CompletionProvider
|
28
30
|
attr_reader :completion_provider
|
@@ -36,17 +38,26 @@ module Yoda
|
|
36
38
|
# @type DefinitionProvider
|
37
39
|
attr_reader :definition_provider
|
38
40
|
|
41
|
+
# @return [Array<Hash>]
|
42
|
+
attr_reader :after_notifications
|
43
|
+
|
39
44
|
def initialize
|
40
45
|
@reader = LSP::Transport::Stdio::Reader.new
|
41
46
|
@writer = LSP::Transport::Stdio::Writer.new
|
47
|
+
@after_notifications = []
|
42
48
|
end
|
43
49
|
|
44
50
|
def run
|
45
51
|
reader.read do |request|
|
46
52
|
begin
|
47
53
|
if result = callback(request)
|
48
|
-
|
54
|
+
if result.is_a?(LanguageServer::Protocol::Interface::ResponseError)
|
55
|
+
send_error(id: request[:id], error: result)
|
56
|
+
else
|
57
|
+
send_response(id: request[:id], result: result)
|
58
|
+
end
|
49
59
|
end
|
60
|
+
process_after_notifications if session&.client_initialized
|
50
61
|
rescue StandardError => ex
|
51
62
|
STDERR.puts ex
|
52
63
|
STDERR.puts ex.backtrace
|
@@ -54,6 +65,30 @@ module Yoda
|
|
54
65
|
end
|
55
66
|
end
|
56
67
|
|
68
|
+
def process_after_notifications
|
69
|
+
while notification = after_notifications.pop
|
70
|
+
send_notification(**notification)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# @param method [String]
|
75
|
+
# @param params [Object]
|
76
|
+
def send_notification(method:, params:)
|
77
|
+
writer.write(method: method, params: params)
|
78
|
+
end
|
79
|
+
|
80
|
+
# @param id [String]
|
81
|
+
# @param result [Object]
|
82
|
+
def send_response(id:, result:)
|
83
|
+
writer.write(id: id, result: result)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param method [String]
|
87
|
+
# @param error [Object]
|
88
|
+
def send_error(id:, error:)
|
89
|
+
writer.write(id: id, error: error)
|
90
|
+
end
|
91
|
+
|
57
92
|
def callback(request)
|
58
93
|
if method_name = resolve(request_registrations, request[:method])
|
59
94
|
send(method_name, self.class.deserialize(request[:params] || {}))
|
@@ -65,10 +100,12 @@ module Yoda
|
|
65
100
|
|
66
101
|
# @param hash [Hash]
|
67
102
|
# @param key [String, Symbol]
|
103
|
+
# @return [Symbol, nil]
|
68
104
|
def resolve(hash, key)
|
69
|
-
key.to_s.split('/').reduce(hash) do |scope, key|
|
105
|
+
resolved = key.to_s.split('/').reduce(hash) do |scope, key|
|
70
106
|
(scope || {})[key.to_sym]
|
71
107
|
end
|
108
|
+
resolved.is_a?(Symbol) && resolved
|
72
109
|
end
|
73
110
|
|
74
111
|
def request_registrations
|
@@ -97,12 +134,13 @@ module Yoda
|
|
97
134
|
end
|
98
135
|
|
99
136
|
def handle_initialize(params)
|
100
|
-
@
|
101
|
-
@completion_provider = CompletionProvider.new(@
|
102
|
-
@hover_provider = HoverProvider.new(@
|
103
|
-
@signature_provider = SignatureProvider.new(@
|
104
|
-
@definition_provider = DefinitionProvider.new(@
|
105
|
-
|
137
|
+
@session = Session.new(params[:root_uri])
|
138
|
+
@completion_provider = CompletionProvider.new(@session)
|
139
|
+
@hover_provider = HoverProvider.new(@session)
|
140
|
+
@signature_provider = SignatureProvider.new(@session)
|
141
|
+
@definition_provider = DefinitionProvider.new(@session)
|
142
|
+
|
143
|
+
(InitializationProvider.new(@session).provide || []).each { |notification| after_notifications.push(notification) }
|
106
144
|
|
107
145
|
LSP::Interface::InitializeResult.new(
|
108
146
|
capabilities: LSP::Interface::ServerCapabilities.new(
|
@@ -113,7 +151,7 @@ module Yoda
|
|
113
151
|
),
|
114
152
|
),
|
115
153
|
completion_provider: LSP::Interface::CompletionOptions.new(
|
116
|
-
resolve_provider:
|
154
|
+
resolve_provider: false,
|
117
155
|
trigger_characters: ['.', '@', '[', ':', '!', '<'],
|
118
156
|
),
|
119
157
|
hover_provider: true,
|
@@ -123,9 +161,16 @@ module Yoda
|
|
123
161
|
),
|
124
162
|
),
|
125
163
|
)
|
164
|
+
rescue => e
|
165
|
+
LanguageServer::Protocol::Interface::ResponseError.new(
|
166
|
+
message: "Failed to initialize yoda: #{e.class} #{e.message}",
|
167
|
+
code: LanguageServer::Protocol::Constant::InitializeError::UNKNOWN_PROTOCOL_VERSION,
|
168
|
+
data: LanguageServer::Protocol::Interface::InitializeError.new(retry: false),
|
169
|
+
)
|
126
170
|
end
|
127
171
|
|
128
172
|
def handle_initialized(_params)
|
173
|
+
session.client_initialized = true
|
129
174
|
end
|
130
175
|
|
131
176
|
def handle_shutdown(_params)
|
@@ -143,19 +188,19 @@ module Yoda
|
|
143
188
|
def handle_text_document_did_open(params)
|
144
189
|
uri = params[:text_document][:uri]
|
145
190
|
text = params[:text_document][:text]
|
146
|
-
|
191
|
+
session.file_store.store(uri, text)
|
147
192
|
end
|
148
193
|
|
149
194
|
def handle_text_document_did_save(params)
|
150
195
|
uri = params[:text_document][:uri]
|
151
196
|
|
152
|
-
|
197
|
+
session.reparse_doc(uri)
|
153
198
|
end
|
154
199
|
|
155
200
|
def handle_text_document_did_change(params)
|
156
201
|
uri = params[:text_document][:uri]
|
157
202
|
text = params[:content_changes].first[:text]
|
158
|
-
|
203
|
+
session.file_store.store(uri, text)
|
159
204
|
end
|
160
205
|
|
161
206
|
def handle_text_document_completion(params)
|