Change:
<!-- Do Not Delete This! bugreporttemplate --<!-- Please read our Rules of Conduct: --<!-- Please search existing issues to avoid creating duplicates. --<!-- Also please test using the latest insiders build to make sure your issue has not already been fixed: --We have started to get reports from users that Toggle Line Comments does the wrong thing: I think this stems from the default Clojure extension which sets to be a single . Calva changes this here: This has been working for years, and still does for many people, but some days ago it stopped working for some. I also notice that the default extension has moved to its own repository, where there is now a PR to fix the line comment configuration: However, I think it is wrong that the default extension sometimes overrides an installed extension here. Then that API can't be trusted at all. I worry about what more configuration is now sometimes being overridden. <!-- Use Help Report Issue to prefill these. --- VSCode Version: 1.52.1 OS Version: N/A Steps to Reproduce: Calva a file the command Toggle Line Comment on an uncommented line Expected: It gets commented with Actual: It gets commented with Please let me know how I can assist in fixing this issue. <!-- Launch with to check. --Does this issue occur when all extensions are disabled?: Not applicable
I am surprised that this ever worked reliably, it must have been some amazing timing luck. defines a language configuration at . Language configuration file handling is implemented by waiting for the language to be first used (like embedded in another language like a markdown fence or a file using the language is opened) and then reading async the language configuration file and applying it. This is done . Whenever the language configuration file finishes reading, it will go and configure the in pretty much the exact same way an extension calling configures it . So deep down there is no difference between those two calls and basically those two calls are racing themselves and it is lucky that until now the call reading the descriptive file was always faster than the imperative call from your extension. If you want to be sure to overwrite the builtin language configuration, you can define your own language configuration file using: But I also see how you would expect that the imperative call would "win" over the descriptive way, so maybe we can do something on our side to give a higher priority to the imperative call way of configuring languages.
Thanks ! This is great information. Calva has been relying on this luck for a very long time. And yes, I actually choose to do it the imperative way very consciously, assuming it was the way to be sure I could override the defaults.

Discussion:
public readonly indentationRules: IndentationRule | undefined; public readonly foldingRules: FoldingRules; <del> constructor(languageIdentifier: LanguageIdentifier, previous: RichEditSupport | undefined, rawConf: LanguageConfiguration) { </del> <ins> constructor(languageIdentifier: LanguageIdentifier, rawConf: LanguageConfiguration) { </ins> this._languageIdentifier = languageIdentifier; this._brackets = null; this._electricCharacter = null; <del> let prev: LanguageConfiguration | null = null; if (previous) { prev = previous._conf; } this._conf = RichEditSupport._mergeConf(prev, rawConf); </del> <ins> this._conf = rawConf; </ins> this._onEnterSupport = (this._conf.brackets || this._conf.indentationRules || this._conf.onEnterRules ? new OnEnterSupport(this._conf) : null); this.comments = RichEditSupport._handleComments(this._conf); this.characterPair = new CharacterPairSupport(this._conf); this.wordDefinition = this._conf.wordPattern || DEFAULT_WORD_REGEXP; this.indentationRules = this._conf.indentationRules; if (this._conf.indentationRules) { this.indentRulesSupport = new IndentRulesSupport(this._conf.indentationRules); } else { this.indentRulesSupport = null; } this.foldingRules = this._conf.folding || {}; }

return this._onEnterSupport.onEnter(autoIndent, previousLineText, beforeEnterText, afterEnterText); } <del> private static _mergeConf(prev: LanguageConfiguration | null, current: LanguageConfiguration): LanguageConfiguration { return { comments: (prev ? current.comments || prev.comments : current.comments), brackets: (prev ? current.brackets || prev.brackets : current.brackets), wordPattern: (prev ? current.wordPattern || prev.wordPattern : current.wordPattern), indentationRules: (prev ? current.indentationRules || prev.indentationRules : current.indentationRules), onEnterRules: (prev ? current.onEnterRules || prev.onEnterRules : current.onEnterRules), autoClosingPairs: (prev ? current.autoClosingPairs || prev.autoClosingPairs : current.autoClosingPairs), surroundingPairs: (prev ? current.surroundingPairs || prev.surroundingPairs : current.surroundingPairs), autoCloseBefore: (prev ? current.autoCloseBefore || prev.autoCloseBefore : current.autoCloseBefore), folding: (prev ? current.folding || prev.folding : current.folding), __electricCharacterSupport: (prev ? current.__electricCharacterSupport || prev.__electricCharacterSupport : current.__electricCharacterSupport), }; } </del> private static _handleComments(conf: LanguageConfiguration): ICommentsConfiguration | null { let commentRule = conf.comments; if (!commentRule) {

) { } } <del> export class LanguageConfigurationRegistryImpl { </del> <ins> class LanguageConfigurationEntry { constructor( public readonly configuration: LanguageConfiguration, public readonly priority: number, public readonly order: number ) { } </ins> <del> private readonly _entries = new Map<LanguageId, RichEditSupport | undefined>(); </del> <ins> public static cmp(a: LanguageConfigurationEntry, b: LanguageConfigurationEntry) { if (a.priority === b.priority) { // higher order last return a.order - b.order; } // higher priority last return a.priority - b.priority; } } </ins> <del> private readonly _onDidChange = new Emitter<LanguageConfigurationChangeEvent>(); public readonly onDidChange: Event<LanguageConfigurationChangeEvent> = this._onDidChange.event; </del> <ins> class LanguageConfigurationEntries { </ins> <del> public register(languageIdentifier: LanguageIdentifier, configuration: LanguageConfiguration): IDisposable { let previous = this._getRichEditSupport(languageIdentifier.id); let current = new RichEditSupport(languageIdentifier, previous, configuration); this._entries.set(languageIdentifier.id, current); this._onDidChange.fire(new LanguageConfigurationChangeEvent(languageIdentifier)); </del> <ins> private readonly _entries: LanguageConfigurationEntry[]; private _order: number; private _resolved: RichEditSupport | null = null; constructor( public readonly languageIdentifier: LanguageIdentifier ) { this._entries = []; this._order = 0; this._resolved = null; } public register(configuration: LanguageConfiguration, priority: number): IDisposable { const entry = new LanguageConfigurationEntry(configuration, priority, ++this._order); this._entries.push(entry); this._resolved = null; </ins> return toDisposable(() => { <del> if (this._entries.get(languageIdentifier.id) === current) { this._entries.set(languageIdentifier.id, previous); this._onDidChange.fire(new LanguageConfigurationChangeEvent(languageIdentifier)); </del> <ins> for (let i = 0; i < this._entries.length; i++) { if (this._entries[i] === entry) { this._entries.splice(i, 1); this._resolved = null; break; } </ins> } }); } <del> private _getRichEditSupport(languageId: LanguageId): RichEditSupport | undefined { return this._entries.get(languageId); </del> <ins> public getRichEditSupport(): RichEditSupport | null { if (!this._resolved) { const config = this._resolve(); if (config) { this._resolved = new RichEditSupport(this.languageIdentifier, config); } } return this._resolved; </ins> } <del> public getIndentationRules(languageId: LanguageId) { const value = this._entries.get(languageId); if (!value) { </del> <ins> private _resolve(): LanguageConfiguration | null { if (this._entries.length === 0) { </ins> return null; } <ins> this._entries.sort(LanguageConfigurationEntry.cmp); const result: LanguageConfiguration = {}; for (const entry of this._entries) { const conf = entry.configuration; result.comments = conf.comments || result.comments; result.brackets = conf.brackets || result.brackets; result.wordPattern = conf.wordPattern || result.wordPattern; result.indentationRules = conf.indentationRules || result.indentationRules; result.onEnterRules = conf.onEnterRules || result.onEnterRules; result.autoClosingPairs = conf.autoClosingPairs || result.autoClosingPairs; result.surroundingPairs = conf.surroundingPairs || result.surroundingPairs; result.autoCloseBefore = conf.autoCloseBefore || result.autoCloseBefore; result.folding = conf.folding || result.folding; result.__electricCharacterSupport = conf.__electricCharacterSupport || result.__electricCharacterSupport; } return result; } } </ins> <del> return value.indentationRules || null; </del> <ins> export class LanguageConfigurationRegistryImpl { private readonly _entries2 = new Map<LanguageId, LanguageConfigurationEntries>(); private readonly _onDidChange = new Emitter<LanguageConfigurationChangeEvent>(); public readonly onDidChange: Event<LanguageConfigurationChangeEvent> = this._onDidChange.event; /** * @param priority Use a higher number for higher priority */ public register(languageIdentifier: LanguageIdentifier, configuration: LanguageConfiguration, priority: number = 0): IDisposable { let entries = this._entries2.get(languageIdentifier.id); if (!entries) { entries = new LanguageConfigurationEntries(languageIdentifier); this._entries2.set(languageIdentifier.id, entries); } const disposable = entries.register(configuration, priority); this._onDidChange.fire(new LanguageConfigurationChangeEvent(languageIdentifier)); return toDisposable(() => { disposable.dispose(); this._onDidChange.fire(new LanguageConfigurationChangeEvent(languageIdentifier)); }); } private _getRichEditSupport(languageId: LanguageId): RichEditSupport | null { const entries = this._entries2.get(languageId); return entries ? entries.getRichEditSupport() : null; } public getIndentationRules(languageId: LanguageId): IndentationRule | null { const value = this._getRichEditSupport(languageId); return value ? value.indentationRules || null : null; </ins> } // begin electricCharacter

public getWordDefinitions(): [LanguageId, RegExp][] { let result: [LanguageId, RegExp][] = []; <del> this._entries.forEach((value, language) => { </del> <ins> for (const [language, entries] of this._entries2) { const value = entries.getRichEditSupport(); </ins> if (value) { result.push([language, value.wordDefinition]); } <del> }); </del> <ins> } </ins> return result; }

folding: { offSide: true } <del> }); </del> <ins> }, 0); </ins>

if (!languageIdentifier) { throw new Error(`Cannot set configuration for unknown language ${languageId}`); } <del> return LanguageConfigurationRegistry.register(languageIdentifier, configuration); </del> <ins> return LanguageConfigurationRegistry.register(languageIdentifier, configuration, 100); </ins> } /**

*--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; <del> import { StandardTokenType } from 'vs/editor/common/modes'; </del> <ins> import { LanguageIdentifier, StandardTokenType } from 'vs/editor/common/modes'; </ins> import { StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; <ins> import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; </ins> suite('StandardAutoClosingPairConditional', () => {

assert.strictEqual(v.isOK(StandardTokenType.String), false); assert.strictEqual(v.isOK(StandardTokenType.RegEx), false); }); <ins> test('language configurations priorities', () => { const id = new LanguageIdentifier('testLang1', 15); const d1 = LanguageConfigurationRegistry.register(id, { comments: { lineComment: '1' } }, 100); const d2 = LanguageConfigurationRegistry.register(id, { comments: { lineComment: '2' } }, 10); assert.strictEqual(LanguageConfigurationRegistry.getComments(id.id)?.lineCommentToken, '1'); d1.dispose(); d2.dispose(); }); </ins> });

const languageIdentifier = this._modeService.getLanguageIdentifier(languageId); if (languageIdentifier) { <del> this._registrations.set(handle, LanguageConfigurationRegistry.register(languageIdentifier, configuration)); </del> <ins> this._registrations.set(handle, LanguageConfigurationRegistry.register(languageIdentifier, configuration, 100)); </ins> } }

richEditConfig.onEnterRules = onEnterRules; } <del> LanguageConfigurationRegistry.register(languageIdentifier, richEditConfig); </del> <ins> LanguageConfigurationRegistry.register(languageIdentifier, richEditConfig, 50); </ins> } private _parseRegex(languageIdentifier: LanguageIdentifier, confPath: string, value: string | IRegExp) {