sublimelsp / LSP-json

Schema validation/completions for your JSON and Sublime files

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support reading schema files from installed packages

rchl opened this issue · comments

Implement fetching schema files from installed packages that have defined their custom schemas for preferences (and possibly for other types of configurations too).

For example, if package LSP wants to provide schema for its LSP.sublime-settings preferences it can include file LSP.sublime-settings.schema.json (or just LSP.sublime-settings.schema but then the one developing the schema would have to manually associate that extension with JSON syntax) that this package will find and automatically map to LSP.sublime-settings file pattern.

The limitation of that system is that one schema can only be associated with one file pattern.
(There could be a special case for preferences where we would also auto-create patterns with platform suffixes.)

@predragnikolic @rwols Thoughts (especially on naming)?

How about this one?
If we have a file like LSP-json.sublime-settings the schema would be LSP-json.schema.json

The limitation of that system is that one schema can only be associated with one file pattern.

I am kind of ok with that limitation.
There is a way that a schema can reference another schema.

image
Because sublime plugins are zipped, we cannot access files like this.

Unfortunately this approach doesn't work either:
image

But we could upload the shared schema on GitHub for example and use it?
image

I am just listing all possible approaches :)

+1 for LSP.sublime-settings.schema.json but actually ${file_name}.schema.json rather than ${plugin_name}.sublime-settings.schema.json.

Note that some plugins may have multiple settings files.

For example, BracketHighlighter has 3 settings files

  • bh_swapping.sublime-settings
  • bh_wrapping.sublime-settings
  • bh_core.sublime-settings

There is a way that a schema can reference another schema.
...
Unfortunately this approach doesn't work either:

What use case are you thinking of here when giving an example for referencing another schema?
For the case of sublime-settings it shouldn't be needed to reference the base one as JSON server merges them as long as both match the current file.

So given that we have sublime://schemas/sublime-settings schema with *.sublime-settings file pattern, that one will apply to all other settings and doesn't have to be referenced.

EDIT: But then it seems to work here anyway:
ref-schema

+1 for LSP.sublime-settings.schema.json but actually ${file_name}.schema.json rather than ${plugin_name}.sublime-settings.schema.json.

+1

So If I understand @jfcherng's idea correctly, if a user opens a file

/path/to/Packages/User/bh_swapping.sublime-settings

Then vscode-json-languageserver will request a schema for that file. We then respond with

bh_swapping.schema.json

vscode-json-languageserver then asks for this file, and we do a

schemas = sublime.find_resources('bh_swapping.schema.json')

If BracketHighlighter then has a file in its zip, say

Packages/BracketHighlighter/Schemas/bh_swapping.schema.json

Then sublime.find_resources('bh_swapping.schema.json') would contain Packages/BracketHighlighter/Schemas/bh_swapping.schema.json, hopefully as a first match. So we load it with

sublime.load_resource(schemas[0])

and return the content to vscode-json-languageserver. Sounds like a plan :)

Not exactly - the server won't ask for schema content unless we first associate that file path with schema URL.

So we would have to find all *.schema.json files up-front (for example on first initialization of the server so that we don't do it on the start of ST) and associate them with the respective settings files.

For example, we would find Packages/LSP/LSP.sublime-settings.schema.json and create a mapping for it:

{
  'LSP.sublime-settings.json': [
    'sublime://Packages/LSP/LSP.sublime-settings.schema.json`
  ]
}

After we do that, the server will ask for content of that sublime://Packages/LSP/LSP-sublime-settings.schema.json schema URL, on opening a given settings file, and we would do the loading of that schema at this point and return to the server.

Note: The mapping might need to be more specific to avoid mixing settings of different packages when they are using Default.sublime-settings name. I hope the path match can include directories...

I see. Presumably you're talking about this line?

https://github.com/sublimelsp/LSP-json/blob/master/plugin.py#L64

If so then yes, we'd need to scan with sublime.find_resources('*.schema.json') up-front.

Yeah, we can do it there if we are able to do everything synchronously. Otherwise there is also a json/schemaAssociations notification that we can send asynchronously after init.

It has to run synchronous or we risk doing a textDocument/didOpen before json/schemaAssociations is sent.

Perhaps I spoke too soon. It might also be possible to notify json/schemaAssociations in on_initialized.

https://github.com/sublimelsp/LSP/blob/c734ad44b6edad95eb5e0954b8ab99c2e90d55c1/plugin/core/windows.py#L545

But in any case, I don't think sublime.find_resources would take so long that we'd need to spawn a thread for it. Creating the thread itself probably takes longer than that. For py 3.8 we definitely need async versions of all the callbacks for ServerHandler / LanguageHandler...

It should be OK to send json/schemaAssociations at any point after init. Server should react with updated diagnostics. That's the point of that notification in VSCode, to be able to asynchronously collect all schemas without blocking anything.

Gathering schemas for single <package>.sublime-settings is one thing, but plugins may also support settings placed to Preferences.sublime-settings in order to make them apply on a view specific or project specific scope.

That means any plugin may provide a scheme for Prerences.sublime-settings with the key/value pairs it wants to add. All of them would need to be merged to one single jsonscheme.

Hence it might make more sense for plugins to provide snippets of key/value pairs to be injected into a scheme rather than complete scheme files.

VS Code does so for language-service settings. The relevant meta information to drive the autocompletion engine ar part of the package.json.

Example:

	"contributes": {
		"configuration": {
			"type": "object",
			"title": "XML configuration",
			"properties": {
				"xml.trace.server": {
					"type": "string",
					"enum": [
						"off",
						"messages",
						"verbose"
					],
					"default": "off",
					"description": "Traces the communication between VS Code and the XML language server.",
					"scope": "window"
				},

Maybe we should add support for such a package meta data file as well?

Maybe something like a sublime-package.json with ...

{
    "jsonschemas": [
        {
            "pattern": "Preferences.sublime-settings",
            "properties": {
                "mypackage.my_setting": {
                    "type": "string",
                    "description": "The tooltip",
                },
            },
        },
        {
            "pattern": "LSP.sublime-settings",
            "properties": {
                "injected_3rd_party_setting": {
                    "type": "string",
                    "description": "The tooltip",
                },
            },
        }
    ],
}

That sounds quite nice and flexible indeed!

Maybe something like a sublime-package.json with ...

Or just package.json which is already a de-facto standard apparently.

Anyone working on this?

Tied to pick up this topic several times, but haven't come up with a serious approach. I am stuck with the question of how to map the scheme id's to the schemes provided by 3rd-party packages properly. The sublime://.... scheme points to a path within LSP-json. How to do that for something like sublime://the-package-name.sublime-settings.json then?

Was thinking about plain res://package/the-package-name.sublime-settings.json but I find it too plain to be future proof.

Busy with some other tasks atm.

A resource identifier consisting of a package name and the path within the package sounds like a proper way to do it. It should be guaranteed to be unique and identifying the proper package. I wonder why do you think this might not be future proof?

Because res://package/the-package-name.sublime-settings.json denotes the real resource path within a package while most other official ids denote urls to websites. Even your sublime:// is translated to a real path. VS Code uses ms.aka.net/blabla as intermediate identifier which is then translated to the real path.

If we use the res://package... it would stop working as soon as someone renames the package. If a package's structure is refactored the relative path of a scheme within the package might change which also renders the id invalid.

If we use the res://package... it would stop working as soon as someone renames the package. If a package's structure is refactored the relative path of a scheme within the package might change which also renders the id invalid.

But the ID of the schema would not need to be stored anywhere, I think. It would be generated automatically from the path on the first initialization of the server. Thus renames or structure changes shouldn't be a problem.

(Unless I'm missing something because my memory of how all this works might be a bit rusty.)

(If we go for having sublime_package.json for defining schemas then we could also match that with sublime-package://... URIs.)

Not sure if it is common practice, but it seems to be possible to add an $id: ... key to a json file, which is picked up by the json-language-server to assign a certain scheme. Such a static assignment would fail upon renames. With proper filename/scheme mappings uppon startup this may not be an issue at all, but who knows whether it would become one in future use cases. Therefore a fixed id would be more safe, I think.

By $id: ..., did you meant $schema? 🙂

Honestly, I don't see why the packages should define ID themselves. At least in VSCode packages don't do it and it seems to be fine.

Propably you are right.

But with regards to adding a package path to the $id, how would you handle multiple packages providing additions for Preferences.sublime-settings, Default.sublime-keymap etc. then? We would need a mechanism to merge multiple schemes into one. Defining the path of a static scheme as id would not be useful then.

Even though it might be very very uncommon any plugin could want to inject its own keys into any other settings file.

I'd propably find a scheme id like sublime://<FileName>.sublime-<...>-schema useful. LSP-json would need to find_resources() and merge everything it finds into one temporary theme file which then is assigned to <FileName>.sublime-<...>. This is the static way. Maybe need/want to use some kind of intermediate meta data file to handle such mappings.