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

Allow Extensions To Contribute Language Specific Settings #26707

Closed
mjbvz opened this issue May 16, 2017 · 30 comments
Closed

Allow Extensions To Contribute Language Specific Settings #26707

mjbvz opened this issue May 16, 2017 · 30 comments
Assignees
Labels
api api-finalization config VS Code configuration, set up issues feature-request Request for new features or functionality on-testplan
Milestone

Comments

@mjbvz
Copy link
Collaborator

mjbvz commented May 16, 2017

Problem
To my knowledge, there is no well supported way for an extension to contribute language specific settings, like so:

    "[markdown]": {
           "amazingExtension.enableUnicornMode": false
     }

This actually works, but has some major issues:

  • There is no way to specify a language specific setting in the contributes section's schema
  • Entering the above configuration produces a warning for users
  • To retrieve the setting, you have to write:
workspace.getConfiguration().get('[markdown]')['amazingExtension.enableUnicornMode']

Proposals

  • Allow extensions to mark settings as being valid language specific settings.
  • Add apis for better working with language specific settings
@mjbvz mjbvz added api feature-request Request for new features or functionality labels May 16, 2017
@jonhoo
Copy link

jonhoo commented May 16, 2017

It'd be really neat if the flow worked the other way: if the extension didn't need to know about what language is currently being used, but instead vscode simply changed the setting appropriately for the extension when a file of a different type was opened. That would mean that things like vim.textwidth could also be set. Of course, this has the drawback of not being able to support multiple languages inside a single file, but it's not clear that that's something the vim extension for example cares about..

@sandy081
Copy link
Member

@mjbvz You can contribute default language specific settings as follows

"configurationDefaults": {
	"[yaml]": {
		"editor.insertSpaces": true,
		"editor.tabSize": 2
	}
}

Above should work.

Regarding API, agreed that we need better support there.

@sandy081 sandy081 added this to the Backlog milestone May 22, 2017
@sandy081
Copy link
Member

@mjbvz Ignore my earlier comment, I misunderstood with configuration defaults.
Yes, Currently only editor settings are supported as lang specific settings.

@DanTup
Copy link
Contributor

DanTup commented May 22, 2017

It'd be really neat if the flow worked the other way: if the extension didn't need to know about what language is currently being used, but instead vscode simply changed the setting appropriately for the extension when a file of a different type was opened. That would mean that things like vim.textwidth could also be set.

I don't think this would work very well as you can have multiple files open side-by-side (there may also be side effects of firing config change events frequently if extensions currently only expect them to happen infrequently). I think it'd be much better to design something sane and have cooperation of extensions than try to make it work transparently.

@jeremychone
Copy link

On a side note, it might be good to add something to the doc. Took me a while to understand how to allow per language customization for my extension. Thanks to @mjbvz for this useful example.

Json validation/intellisense might be hard to solve (I am not familiar with Json schema), but adding a workspace.getConfiguration signature with languageId or Editor as argument would be pretty neat.

@batisteo
Copy link

Something that I would love the most for extensions would be "files.associations":

"files.associations": {
    "**/templates/*.html": "django-html",
    "**/templates/*": "django-txt",
    "**/requirements{/**,*}.{txt,in}": "pip-requirements"
},

@sandy081
Copy link
Member

@batisteo How is your comment is related to this feature request?

@batisteo
Copy link

@sandy081 I was maybe confused with your comment, so if this feature request has nothing to do with contributes.configurationDefaults then my comment is indeed off-topic.

@sandy081
Copy link
Member

@batisteo So you want files.associations setting to be language specific?

@rwe
Copy link
Contributor

rwe commented Feb 7, 2019

An additional specific use-case: I want to disable GitLens's per-line blame annotations when working on Haskell files, because I have other plugins that show type information there.

So I'd like to be able to put in my config something like:

  "gitlens.currentLine.enabled": true,
  "[haskell]": {
    "gitlens.currentLine.enabled": false,
  },

@nmsmith22389
Copy link

An additional specific use-case: I want to disable GitLens's per-line blame annotations when working on Haskell files, because I have other plugins that show type information there.

So I'd like to be able to put in my config something like:

  "gitlens.currentLine.enabled": true,
  "[haskell]": {
    "gitlens.currentLine.enabled": false,
  },

I'm also looking for a way to do something like this. If anyone has an idea on how to accomplish that for arbitrary extension setting let me know! 😁

@sandy081
Copy link
Member

@eamodio @dbaeumer

Above api is in available as proposed api in master, feel free to try it out for your usecase in your extension and feedback is very much appreciated.

Thanks.

@sandy081
Copy link
Member

sandy081 commented Jan 7, 2020

@eamodio I am still unsure about your use case to access settings only with the language. Can you please elaborate how are you accessing such settings and applying them.

@eamodio
Copy link
Contributor

eamodio commented Jan 7, 2020

@sandy081 In GitLens I have 2 settings that control the scopes for where code lens should be rendered: gitlens.codeLens.scopes and gitlens.codeLens.scopesByLanguage. Both of which are "scope": "resource". gitlens.codeLens.scopes is basically the defaults and gitlens.codeLens.scopesByLanguage is optional overrides per-language.

So when I am providing code lens for a document, I look up the config for the current document uri and check gitlens.codeLens.scopes and then fallback to gitlens.codeLens.scopesByLanguage (See here).

So ideally I'd remove gitlens.codeLens.scopesByLanguage and support gitlens.codeLens.scopes to also be configurable per-language.

So for the reading side -- I think the current proposal works for my usage, but where it gets challenging if the inspection/updating part.

I also currently only support configuring gitlens.codeLens.scopes via GitLens' interactive editor. Only for the user or workspace (see the Save Settings to dropdown at the top).

image

But if gitlens.codeLens.scopes is configurable per-language, I think I would need a way to inspect gitlens.codeLens.scopes without being in the context of any resource or language, and they a way to update the setting with and without affecting the language-specific config. And if I wanted to add support in my UI to add/edit language specific settings, I would need to be able to write those settings again in the context of just a language, not a specific resource.

Does that make sense?

@sandy081
Copy link
Member

sandy081 commented Jan 8, 2020

@eamodio Thanks for the summary.

If I understand correctly, In short your use case is to allow display/configuring a setting per language in Gitlens Settings UI. It means given a setting you want to know, which languages it is being overridden?

Re writing, current API (by making resource optional in the scope) supports writing a setting under a language or not.

/**
 * Read language configuration.
 */
const textDocumentConfiguration = vscode.workspace.getConfiguration('sample', {languageId});
textDocumentConfiguration.get('languageSetting');

/**
 * Write configuration
 */
textDocumentConfiguration.update('languageSetting', false, ConfigurationTarget.Global);

/**
 * Write configuration under language
 */
textDocumentConfiguration.update('languageSetting', false, ConfigurationTarget.Global, true);

sandy081 added a commit that referenced this issue Jan 8, 2020
@eamodio
Copy link
Contributor

eamodio commented Jan 8, 2020

@sandy081 would calling update without the language flag alter any language specific overrides at all? What would I do if I wanted to clear out everything for a setting (regardless of per-language or not)? Also ideally, I would need inspect to return more data if you get the config without a language or anything, so I can know that the setting does indeed have language specific overrides.

@sandy081
Copy link
Member

sandy081 commented Jan 8, 2020

would calling update without the language flag alter any language specific overrides at all?

It will, if the setting is already overridden by the language in the given configuration target.

What would I do if I wanted to clear out everything for a setting (regardless of per-language or not)?

I do not think there is a way to do that even now. You can only remove the setting given the target. Now you can do the same for the language.

Also ideally, I would need inspect to return more data if you get the config without a language or anything, so I can know that the setting does indeed have language specific overrides.

Is this for building the UI to show the value for a given setting for languages?

@eamodio
Copy link
Contributor

eamodio commented Jan 8, 2020

Is this for building the UI to show the value for a given setting for languages?

Yeah, or at least signaling in the UI that the current setting might not always take effect (because I doubt I'll build the full UI for that). Also the inspect would be needed, if I wanted to force-ably reset a setting -- because I'd need to know all the language overrides and then update each

@sandy081
Copy link
Member

sandy081 commented Jan 8, 2020

Makes sense. How about having languages property to inspect result that lists all languages that the setting is configured at?

@eamodio
Copy link
Contributor

eamodio commented Jan 8, 2020

I was more thinking something like:

inspect<T>(section: string): {
	key: string;

	defaultValue?: T;
	globalValue?: T;
	workspaceValue?: T,
	workspaceFolderValue?: T,

	languages?: {
		languageId: string;
		defaultValue?: T;
		globalValue?: T;
		workspaceValue?: T,
		workspaceFolderValue?: T,
	}[];
} | undefined;

or

inspect<T>(section: string): {
	key: string;

	defaultValue?: T;
	globalValue?: T;
	workspaceValue?: T,
	workspaceFolderValue?: T,

	languages?: {
		[languageId: string]: {
			defaultValue?: T;
			globalValue?: T;
			workspaceValue?: T,
			workspaceFolderValue?: T,
		}
	}
} | undefined;

@sandy081
Copy link
Member

sandy081 commented Jan 9, 2020

I think above proposal makes sense if WorkspaceConfiguration object is not scoped. Since this can be scoped to resource or languageId or both, it will take two steps process to access scoped language values. Eg: workspaceConfiguration.inspect(key).languages[scopedLanguage].defaultValue. Also this is not consistent with resource scope. Inspect is not returning workspaceFolderValues for all workspace folders. Hence I would suggest following:

inspect<T>(section: string): {
			key: string;

			defaultValue?: T;
			globalValue?: T;
			workspaceValue?: T,
			workspaceFolderValue?: T,

			defaultLanguageValue?: T;
			userLanguageValue?: T;
			workspaceLanguageValue?: T;
			workspaceFolderLanguageValue?: T;

			languages?: string[];

		} | undefined;

This is consistent with inspecting resource scope. Inspect result provides all languages in which the key is configured at. One can get the language values by scoping WorkspaceConfiguration for the language.

@sandy081
Copy link
Member

After writing doc, preferred to have slim inspectAPI with language values for requested language and language ids under which the configuration is defined at. This structure also makes it easy to explain how an effective value is computed.

Final Solution

Introduced language-overridable scope to define a a language overridable configuration that can be configured in user, workspace and folder settings.

API
export type ConfigurationScope = Uri | TextDocument | WorkspaceFolder | { uri?: Uri, languageId: string };

	/**
	 * An event describing the change in Configuration
	 */
	export interface ConfigurationChangeEvent {

		/**
		 * Returns `true` if the given section is affected in the provided scope.
		 *
		 * @param section Configuration name, supports _dotted_ names.
		 * @param scope A scope in which to check.
		 * @return `true` if the given section is affected in the provided scope.
		 */
		affectsConfiguration(section: string, scope?: ConfigurationScope): boolean;
	}

	export namespace workspace {

		/**
		 * Get a workspace configuration object.
		 *
		 * When a section-identifier is provided only that part of the configuration
		 * is returned. Dots in the section-identifier are interpreted as child-access,
		 * like `{ myExt: { setting: { doIt: true }}}` and `getConfiguration('myExt.setting').get('doIt') === true`.
		 *
		 * When a scope is provided configuraiton confined to that scope is returned. Scope can be a resource or a language identifier or both.
		 *
		 * @param section A dot-separated identifier.
		 * @return The full configuration or a subset.
		 */
		export function getConfiguration(section?: string | undefined, scope?: ConfigurationScope | null): WorkspaceConfiguration;

	}

	/**
	 * Represents the configuration. It is a merged view of
	 *
	 * - *Default Settings*
	 * - *Global (User) Settings*
	 * - *Workspace settings*
	 * - *Workspace Folder settings* - From one of the [Workspace Folders](#workspace.workspaceFolders) under which requested resource belongs to.
	 * - *Language settings* - Settings defined under requested language.
	 *
	 * The *effective* value (returned by [`get`](#WorkspaceConfiguration.get)) is computed by overriding or merging the values in the following order.
	 *
	 * ```
	 * `defaultValue`
	 * `globalValue` (if defined)
	 * `workspaceValue` (if defined)
	 * `workspaceFolderValue` (if defined)
	 * `defaultLanguageValue` (if defined)
	 * `globalLanguageValue` (if defined)
	 * `workspaceLanguageValue` (if defined)
	 * `workspaceLanguageValue` (if defined)
	 * ```
	 * **Note:** Only `object` value types are merged and all other value types are overridden.
	 *
	 * Example 1: Overriding
	 *
	 * ```ts
	 * defaultValue = 'on';
	 * globalValue = 'relative'
	 * workspaceFolderValue = 'off'
	 * value = 'off'
	 * ```
	 *
	 * Example 2: Language Values
	 *
	 * ```ts
	 * defaultValue = 'on';
	 * globalValue = 'relative'
	 * workspaceFolderValue = 'off'
	 * globalLanguageValue = 'on'
	 * value = 'on'
	 * ```
	 *
	 * Example 3: Object Values
	 *
	 * ```ts
	 * defaultValue = { "a": 1, "b": 2 };
	 * globalValue = { "b": 3, "c": 4 };
	 * value = { "a": 1, "b": 3, "c": 4 };
	 * ```
	 *
	 * *Note:* Workspace and Workspace Folder configurations contains `launch` and `tasks` settings. Their basename will be
	 * part of the section identifier. The following snippets shows how to retrieve all configurations
	 * from `launch.json`:
	 *
	 * ```ts
	 * // launch.json configuration
	 * const config = workspace.getConfiguration('launch', vscode.workspace.workspaceFolders[0].uri);
	 *
	 * // retrieve values
	 * const values = config.get('configurations');
	 * ```
	 *
	 * Refer to [Settings](https://code.visualstudio.com/docs/getstarted/settings) for more information.
	 */
	export interface WorkspaceConfiguration {

		/**
		 * Return a value from this configuration.
		 *
		 * @param section Configuration name, supports _dotted_ names.
		 * @return The value `section` denotes or `undefined`.
		 */
		get<T>(section: string): T | undefined;

		/**
		 * Return a value from this configuration.
		 *
		 * @param section Configuration name, supports _dotted_ names.
		 * @param defaultValue A value should be returned when no value could be found, is `undefined`.
		 * @return The value `section` denotes or the default.
		 */
		get<T>(section: string, defaultValue: T): T;

		/**
		 * Check if this configuration has a certain value.
		 *
		 * @param section Configuration name, supports _dotted_ names.
		 * @return `true` if the section doesn't resolve to `undefined`.
		 */
		has(section: string): boolean;

		/**
		 * Retrieve all information about a configuration setting. A configuration value
		 * often consists of a *default* value, a global or installation-wide value,
		 * a workspace-specific value, folder-specific value
		 * and language-specific values (if [WorkspaceConfiguration](#WorkspaceConfiguration) is scoped to a language).
		 *
                 * Also provides all language ids under which the given configuration setting is defined.
                 *
		 * *Note:* The configuration name must denote a leaf in the configuration tree
		 * (`editor.fontSize` vs `editor`) otherwise no result is returned.
		 *
		 * @param section Configuration name, supports _dotted_ names.
		 * @return Information about a configuration setting or `undefined`.
		 */
		inspect<T>(section: string): {
			key: string;

			defaultValue?: T;
			globalValue?: T;
			workspaceValue?: T,
			workspaceFolderValue?: T,

			defaultLanguageValue?: T;
			userLanguageValue?: T;
			workspaceLanguageValue?: T;
			workspaceFolderLanguageValue?: T;

			languageIds?: string[];

		} | undefined;

		/**
		 * Update a configuration value. The updated configuration values are persisted.
		 *
		 * A value can be changed in
		 *
		 * - [Global settings](#ConfigurationTarget.Global): Changes the value for all instances of the editor.
		 * - [Workspace settings](#ConfigurationTarget.Workspace): Changes the value for current workspace, if available.
		 * - [Workspace folder settings](#ConfigurationTarget.WorkspaceFolder): Changes the value for settings from one of the [Workspace Folders](#workspace.workspaceFolders) under which the requested resource belongs to.
		 * - Language settings: Changes the value for the requested languageId.
		 *
		 * *Note:* To remove a configuration value use `undefined`, like so: `config.update('somekey', undefined)`
		 *
		 * @param section Configuration name, supports _dotted_ names.
		 * @param value The new value.
		 * @param configurationTarget The [configuration target](#ConfigurationTarget) or a boolean value.
		 *	- If `true` updates [Global settings](#ConfigurationTarget.Global).
		 *	- If `false` updates [Workspace settings](#ConfigurationTarget.Workspace).
		 *	- If `undefined` or `null` updates to [Workspace folder settings](#ConfigurationTarget.WorkspaceFolder) if configuration is resource specific,
		 * 	otherwise to [Workspace settings](#ConfigurationTarget.Workspace).
		 * @param scopeToLanguage Whether to update the value in the scope of requested languageId or not.
		 *	- If `true` updates the value under the requested languageId.
		 *	- If `undefined` updates the value under the requested languageId only if the configuration is defined for the language.
		 * @throws error while updating
		 *	- configuration which is not registered.
		 *	- window configuration to workspace folder
		 *	- configuration to workspace or workspace folder when no workspace is opened.
		 *	- configuration to workspace folder when there is no workspace folder settings.
		 *	- configuration to workspace folder when [WorkspaceConfiguration](#WorkspaceConfiguration) is not scoped to a resource.
		 */
		update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean, scopeToLanguage?: boolean): Thenable<void>;

		/**
		 * Readable dictionary that backs this configuration.
		 */
		readonly [key: string]: any;
	}

Example

 "configuration": {
      "title": "sample",
      "properties": {
        "sample.languageSetting": {
          "type": "boolean",
          "scope": "language-overridable"
        }
	}
}
/**
 * Read language configuration.
 */
const textDocumentConfiguration = vscode.workspace.getConfiguration('sample', {resource, languageId});
textDocumentConfiguration.get('languageSetting');

/**
 * Write configuration
 */
textDocumentConfiguration.update('languageSetting', false, ConfigurationTarget.Global);

/**
 * Write configuration under language
 */
textDocumentConfiguration.update('languageSetting', false, ConfigurationTarget.Global, true);

/**
 * Listen for configuration
 */
workspace.onDidChangeConfiguration(e => {
   if(e.affectsConfiguration('sample.languageSetting') {
	}
	if(e.affectsConfiguration('sample.languageSetting',  {resource, languageId}) {
	}
});

@DoCode
Copy link

DoCode commented Feb 6, 2020

@sandy081, is this currently in nightly builds (v1.42.0)?

@sandy081
Copy link
Member

sandy081 commented Feb 6, 2020

Yes and will be available in next stable (v1.42.0)

@DoCode
Copy link

DoCode commented Feb 6, 2020

@sandy081, ok thanks!
One Question: How can I get the current setting in my extension (or the global one?
Currently, I read it with

const settingValue = vscode.workspace.getConfiguration('segment1', null).get('settingName', false);

and this doesn't work and read only the global one.

@sandy081
Copy link
Member

sandy081 commented Feb 6, 2020

@DoCode
Copy link

DoCode commented Feb 7, 2020

@sandy081, sorry, but nothing worked for language-specific configuration.
VSC stable and preview (both v1.42.0) raised an error, that a proposed API is used:

Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ...

The code is simple:

. . .
private _onWillSaveTextDocument(event: TextDocumentWillSaveEvent): void {
        const config = workspace.getConfiguration('', event.document);
        const value = config.get<boolean>('value', false);
. . .

@sandy081
Copy link
Member

sandy081 commented Feb 7, 2020

My bad it is still under proposed API check.

#90201

etaoins added a commit to etaoins/vscode-user-config that referenced this issue Feb 28, 2020
@vscodebot vscodebot bot locked and limited conversation to collaborators Mar 5, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api api-finalization config VS Code configuration, set up issues feature-request Request for new features or functionality on-testplan
Projects
None yet
Development

No branches or pull requests