diff --git a/autoload/tsdetect.vim b/autoload/tsdetect.vim index e112761..22c6133 100644 --- a/autoload/tsdetect.vim +++ b/autoload/tsdetect.vim @@ -29,7 +29,7 @@ function! tsdetect#init(once) abort augroup tsdetect#init autocmd! autocmd FileType javascript,javascriptreact,typescript,typescript.tsx,typescriptreact ++nested call detect_filetype() - autocmd BufEnter,BufNewFile,BufWrite * ++nested call detect_buffer(expand('')) + autocmd BufEnter,BufNewFile,BufWritePost * ++nested call detect_buffer(expand('')) augroup END call s:detect_buffer(expand('%')) diff --git a/autoload/tsdetect/coc.vim b/autoload/tsdetect/coc.vim index 9d848d1..ede740a 100644 --- a/autoload/tsdetect/coc.vim +++ b/autoload/tsdetect/coc.vim @@ -1,12 +1,8 @@ -function! tsdetect#coc#setup_switch(switch_mode) abort +function! tsdetect#coc#setup_switch(mode, config_type) abort augroup tsdetect#coc#setup_switch autocmd! - if a:switch_mode == 'auto' - autocmd User tsdetect#detect ++nested call tsdetect#coc#auto#switch() - elseif a:switch_mode == 'auto_workspace_config' - autocmd User tsdetect#detect ++nested call tsdetect#coc#auto_config#switch('auto_workspace_config') - elseif a:switch_mode == 'auto_user_config' - autocmd User tsdetect#detect ++nested call tsdetect#coc#auto_config#switch('auto_user_config') + if a:mode == 'auto' + execute printf("autocmd User tsdetect#detect ++nested call tsdetect#coc#auto#switch_%s()", a:config_type) endif augroup END endfunction diff --git a/autoload/tsdetect/coc/auto.vim b/autoload/tsdetect/coc/auto.vim index 14b54aa..40f1a09 100644 --- a/autoload/tsdetect/coc/auto.vim +++ b/autoload/tsdetect/coc/auto.vim @@ -1,45 +1,41 @@ -let s:did_configured = 0 +let s:ephemeral_did_configured = 0 -function! tsdetect#coc#auto#switch_deno() abort - doautocmd User tsdetect#coc#auto#swtich#deno#before - let s:did_configured = 1 - let g:tsdetect#coc#auto#switched_deno = 1 - let g:tsdetect#coc#auto#switched_node = 0 - call coc#config('deno.enable', v:true) - call coc#config('tsserver.enable', v:false) - CocRestart - doautocmd User tsdetect#coc#auto#swtich#deno#after -endfunction +let s:ephemeral_condition = "if s:ephemeral_did_configured" +let s:permanent_condition = "if get(g:, 'tsdetect#coc#auto#switched_%s_%s', 0)" -function! tsdetect#coc#auto#switch_node() abort - doautocmd User tsdetect#coc#auto#swtich#node#after - let s:did_configured = 1 - let g:tsdetect#coc#auto#switched_deno = 0 - let g:tsdetect#coc#auto#switched_node = 1 - doautocmd User tsdetect#coc#auto#swtich#node#after -endfunction - -function! tsdetect#coc#auto#switch_deno_if_necessary() abort - if s:did_configured - return - endif - call tsdetect#coc#auto#switch_deno() -endfunction - -function! tsdetect#coc#auto#switch_node_if_necessary() abort - if s:did_configured - return - endif - call tsdetect#coc#auto#switch_node() -endfunction - -function! tsdetect#coc#auto#switch() abort - if !exists('b:tsdetect_is_node') - return - endif - if b:tsdetect_is_node - call tsdetect#coc#auto#switch_node_if_necessary() - else - call tsdetect#coc#auto#switch_deno_if_necessary() - endif -endfunction +for [s:config_type, s:condition] in [ + \ ["ephemeral", s:ephemeral_condition], + \ ["workspace", s:permanent_condition], + \ ["user", s:permanent_condition], + \ ] + for [s:node, s:deno] in [["node", "deno"], ["deno", "node"]] + execute join([ + \ printf("function! tsdetect#coc#auto#switch_%s_%s() abort", s:config_type, s:node), + \ printf(" doautocmd User tsdetect#coc#auto#swtich#%s#%s#before", s:config_type, s:node), + \ s:config_type == 'ephemeral' ? "let s:ephemeral_did_configured = 1" : "", + \ printf(" let g:tsdetect#coc#auto#switched_%s_%s = 1", s:config_type, s:node), + \ printf(" let g:tsdetect#coc#auto#switched_%s_%s = 0", s:config_type, s:deno), + \ printf(" CocCommand tsdetect.internal.%s.%s.initializeWorkspace", s:config_type, s:node), + \ printf("endfunction"), + \ printf(""), + \ printf("function! tsdetect#coc#auto#switch_%s_%s_if_necessary() abort", s:config_type, s:node), + \ s:config_type == 'ephemeral' ? s:ephemeral_condition : printf(s:permanent_condition, s:config_type, s:node), + \ printf(" return"), + \ printf(" endif"), + \ printf(" call tsdetect#coc#auto#switch_%s_%s()", s:config_type, s:node), + \ printf("endfunction"), + \ ], "\n") + endfor + execute join([ + \ printf("function! tsdetect#coc#auto#switch_%s() abort", s:config_type), + \ printf(" if !exists('b:tsdetect_is_node')"), + \ printf(" return"), + \ printf(" endif"), + \ printf(" if b:tsdetect_is_node"), + \ printf(" call tsdetect#coc#auto#switch_%s_node_if_necessary()", s:config_type), + \ printf(" else"), + \ printf(" call tsdetect#coc#auto#switch_%s_deno_if_necessary()", s:config_type), + \ printf(" endif"), + \ printf("endfunction"), + \ ], "\n") +endfor diff --git a/autoload/tsdetect/coc/auto_config.vim b/autoload/tsdetect/coc/auto_config.vim deleted file mode 100644 index 0dbb6d7..0000000 --- a/autoload/tsdetect/coc/auto_config.vim +++ /dev/null @@ -1,38 +0,0 @@ -function! tsdetect#coc#auto_config#switch_deno(mode) abort - execute printf('doautocmd User tsdetect#coc#%s#swtich#deno#before', a:mode) - execute printf('let g:tsdetect#coc#auto_config#%s_switched_deno = 1', a:mode) - execute printf('let g:tsdetect#coc#auto_config#%s_switched_node = 0', a:mode) - execute printf('CocCommand tsdetect.%s.deno.initializeWorkspace', a:mode) -endfunction - -function! tsdetect#coc#auto_config#switch_node(mode) abort - execute printf('doautocmd User tsdetect#coc#%s#swtich#node#before', a:mode) - execute printf('let g:tsdetect#coc#auto_config#%s_switched_deno = 0', a:mode) - execute printf('let g:tsdetect#coc#auto_config#%s_switched_node = 1', a:mode) - execute printf('CocCommand tsdetect.%s.node.initializeWorkspace', a:mode) -endfunction - -function! tsdetect#coc#auto_config#switch_deno_if_necessary(mode) abort - if get(g:, printf('tsdetect#coc#auto_config#%s_switched_deno', a:mode), 0) - return - endif - call tsdetect#coc#auto_config#switch_deno(a:mode) -endfunction - -function! tsdetect#coc#auto_config#switch_node_if_necessary(mode) abort - if get(g:, printf('tsdetect#coc#auto_config#%s_switched_node', a:mode), 0) - return - endif - call tsdetect#coc#auto_config#switch_node(a:mode) -endfunction - -function! tsdetect#coc#auto_config#switch(mode) abort - if !exists('b:tsdetect_is_node') - return - endif - if b:tsdetect_is_node - call tsdetect#coc#auto_config#switch_node_if_necessary(a:mode) - else - call tsdetect#coc#auto_config#switch_deno_if_necessary(a:mode) - endif -endfunction diff --git a/autoload/tsdetect/util.vim b/autoload/tsdetect/util.vim index 1f87017..94bc449 100644 --- a/autoload/tsdetect/util.vim +++ b/autoload/tsdetect/util.vim @@ -2,12 +2,13 @@ function! tsdetect#util#is_node_shebang() abort let l:first_line = getline(1) " Shebang is used and 'deno' is included - if l:first_line =~# '^#!.*\' + if l:first_line =~? '^#!\(.*[^a-z]\)\?deno\([^a-z]\|$\)' return 0 endif " Shebang is used and 'node' is included - if l:first_line =~# '^#!.*\' + " Also supports like ts-node and ts-node-script + if l:first_line =~? '^#!\(.*[^a-z]\)\?node\([^a-z]\|$\)' return 1 endif diff --git a/package.json b/package.json index 2e29054..30bf71c 100644 --- a/package.json +++ b/package.json @@ -26,43 +26,55 @@ "onLanguage:typescriptreact", "onLanguage:javascript", "onLanguage:javascriptreact", - "onCommand:tsdetect.deno.initializeWorkspace", - "onCommand:tsdetect.node.initializeWorkspace", - "onCommand:tsdetect.auto_user_config.deno.initializeWorkspace", - "onCommand:tsdetect.auto_user_config.node.initializeWorkspace", - "onCommand:tsdetect.auto_workspace_config.deno.initializeWorkspace", - "onCommand:tsdetect.auto_workspace_config.node.initializeWorkspace" + "onCommand:tsdetect.manual.deno.initializeWorkspace", + "onCommand:tsdetect.manual.node.initializeWorkspace", + "onCommand:tsdetect.internal.ephemeral.deno.initializeWorkspace", + "onCommand:tsdetect.internal.ephemeral.node.initializeWorkspace", + "onCommand:tsdetect.internal.workspace.deno.initializeWorkspace", + "onCommand:tsdetect.internal.workspace.node.initializeWorkspace", + "onCommand:tsdetect.internal.user.deno.initializeWorkspace", + "onCommand:tsdetect.internal.user.node.initializeWorkspace" ], "contributes": { "commands": [ { - "command": "tsdetect.deno.initializeWorkspace", + "command": "tsdetect.manual.deno.initializeWorkspace", "title": "Initialize Deno Workspace Configuration", "category": "Deno Language Server" }, { - "command": "tsdetect.node.initializeWorkspace", + "command": "tsdetect.manual.node.initializeWorkspace", "title": "Initialize Node Workspace Configuration", "category": "TypeScript Language Server" }, { - "command": "tsdetect.auto_user_config.deno.initializeWorkspace", - "title": "Initialize Deno Silently In User Confiig", + "command": "tsdetect.internal.ephemeral.deno.initializeWorkspace", + "title": "Initialize Deno Silently Without Saving Config", "category": "Deno Language Server" }, { - "command": "tsdetect.auto_user_config.node.initializeWorkspace", - "title": "Initialize Node Silently In User Confiig", + "command": "tsdetect.internal.ephemeral.node.initializeWorkspace", + "title": "Initialize Node Silently Without Saving Config", "category": "TypeScript Language Server" }, { - "command": "tsdetect.auto_workspace_config.deno.initializeWorkspace", - "title": "Initialize Deno Silently In Workspace Confiig", + "command": "tsdetect.internal.user.deno.initializeWorkspace", + "title": "Initialize Deno Silently In User Config", "category": "Deno Language Server" }, { - "command": "tsdetect.auto_workspace_config.node.initializeWorkspace", - "title": "Initialize Node Silently In Workspace Confiig", + "command": "tsdetect.internal.user.node.initializeWorkspace", + "title": "Initialize Node Silently In User Config", + "category": "TypeScript Language Server" + }, + { + "command": "tsdetect.internal.workspace.deno.initializeWorkspace", + "title": "Initialize Deno Silently In Workspace Config", + "category": "Deno Language Server" + }, + { + "command": "tsdetect.internal.workspace.node.initializeWorkspace", + "title": "Initialize Node Silently In Workspace Config", "category": "TypeScript Language Server" } ], @@ -76,16 +88,71 @@ "markdownDescription": "How to setup `deno.enabled` and `tsserver.enabled`.\n\n- `\"auto\"` (default): Automatically switching `deno.enabled` and `tsserver.enabled` based on file environment. This is determined whether there is a node_modules directory in one of ancestor directories.\n- `\"auto_user_config\"`: TODO.\n- `\"auto_workspace_config\"`: TODO.\n- `\"manual\"`: coc-tsdetect won't do nothing.\n", "enum": [ "auto", - "auto_user_config", - "auto_workspace_config", "manual" ], "examples": [ "auto", - "auto_user_config", - "auto_workspace_config", "manual" ] + }, + "tsdetect.configType": { + "type": "string", + "default": "ephemeral", + "markdownDescription": "Determine how configuration changed automatically.\n\n", + "enum": [ + "ephemeral", + "workspace", + "user" + ], + "examples": [ + "ephemeral", + "workspace", + "user" + ] + }, + "tsdetect.controlTrimSameExts": { + "type": "boolean", + "default": true, + "markdownDescription": "Whether to control coc.source.file.trimSameExts.", + "examples": [ + true, + false + ] + }, + "tsdetect.controlTrimSameExtsBase": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "markdownDescription": "When controlling coc.source.file.trimSameExts, these values are used as base.", + "examples": [ + [] + ] + }, + "tsdetect.controlTrimSameExtsNode": { + "type": "array", + "items": { + "type": "string" + }, + "default": [".ts", ".js"], + "markdownDescription": "Extensions added when using Node environment to `tsdetect.controlTrimSameExtsBase`.", + "examples": [ + [], + [".ts", ".js"], + [".tsx", ".ts", ".js"] + ] + }, + "tsdetect.controlTrimSameExtsDeno": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "markdownDescription": "Extensions added when using Deno environment to `tsdetect.controlTrimSameExtsBase`. Maybe you would love to keep it default.", + "examples": [ + [] + ] } } } diff --git a/src/commands.ts b/src/commands.ts index 09f7e5b..10a0c05 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,4 +1,5 @@ import { workspace, commands, window } from "coc.nvim"; +import { ConfigType, getSettings } from "./settings"; export const initializeDenoWorkspace = async () => { const config = workspace.getConfiguration("tsdetect"); @@ -25,3 +26,21 @@ export const initializeNodeWorkspace = async () => { await window.showInformationMessage("Deno workspace settings configured!"); }; + +export const configSwitch = async ( + target: "node" | "deno", + setConfig: (ns: string, key: string, value: unknown) => Promise, +) => { + const settings = getSettings(); + await setConfig("tsserver", "enable", target === "node"); + await setConfig("deno", "enable", target === "deno"); + if (settings.controlTrimSameExts) { + await setConfig("coc.source.file", "trimSameExts", [ + ...settings.controlTrimSameExtsBase, + ...(target === "node" + ? settings.controlTrimSameExtsNode + : settings.controlTrimSameExtsDeno), + ]); + } + await commands.executeCommand("editor.action.restart"); +}; diff --git a/src/entrypoints/coc.ts b/src/entrypoints/coc.ts index 2f93c16..649426e 100644 --- a/src/entrypoints/coc.ts +++ b/src/entrypoints/coc.ts @@ -1,44 +1,12 @@ import { workspace, ExtensionContext, commands } from "coc.nvim"; import assert from "assert"; -import { initializeDenoWorkspace, initializeNodeWorkspace } from "../commands"; - -const EXTENSION_NS = "tsdetect"; - -export interface Settings { - mode: "auto" | "auto_user_config" | "auto_workspace_config" | "manual"; -} -export const settingsKeys = ["mode"]; - -const getSettings = (): Settings => { - const settings = workspace.getConfiguration(EXTENSION_NS); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const result: any = Object.create(null); - settingsKeys.forEach((key) => { - const value = settings.inspect(key); - assert(value); - result[key] = - value.workspaceValue ?? value.globalValue ?? value.defaultValue; - }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return result; -}; - -export const switchUseConfig = async ( - target: "node" | "deno", - mode: "auto_workspace_config" | "auto_user_config", -) => { - const tsserverConfig = workspace.getConfiguration("tsserver"); - tsserverConfig.update( - "enable", - target === "node", - mode === "auto_user_config", - ); - - const denoConfig = workspace.getConfiguration("deno"); - denoConfig.update("enable", target === "deno", mode === "auto_user_config"); - - await commands.executeCommand("editor.action.restart"); -}; +import { getSetConfig } from "../set_config"; +import { + configSwitch, + initializeDenoWorkspace, + initializeNodeWorkspace, +} from "../commands"; +import { ConfigType, EXTENSION_NS, getSettings } from "../settings"; const initialize = async (_context: ExtensionContext) => { const settings = getSettings(); @@ -46,19 +14,20 @@ const initialize = async (_context: ExtensionContext) => { const setup = workspace.nvim.call("tsdetect#coc#setup_switch", [ settings.mode, + settings.configType, ]); - if ( - settings.mode === "auto_user_config" || - settings.mode === "auto_workspace_config" - ) { + if (settings.mode === "auto") { + proms.push( + workspace.nvim.call(`tsdetect#coc#auto#switch_${settings.configType}`), + ); proms.push( - workspace.nvim.call("tsdetect#coc#auto_config#switch", [settings.mode]), + setup.then(() => + workspace.nvim.call("tsdetect#init", [ + settings.configType === "ephemeral", + ]), + ), ); - proms.push(setup.then(() => workspace.nvim.call("tsdetect#init", [0]))); - } else if (settings.mode === "auto") { - proms.push(workspace.nvim.call("tsdetect#coc#auto#switch")); - proms.push(setup.then(() => workspace.nvim.call("tsdetect#init", [1]))); } else { proms.push(setup); } @@ -67,6 +36,7 @@ const initialize = async (_context: ExtensionContext) => { }; export const activate = async (context: ExtensionContext) => { + // Setup vim runtime settings as vim plugin. const rtp = await workspace.nvim.getOption("runtimepath"); assert(typeof rtp === "string"); const paths = rtp.split(","); @@ -76,18 +46,22 @@ export const activate = async (context: ExtensionContext) => { ); } await workspace.nvim.command("runtime plugin/tsdetect.vim"); + + // Setup manual commands. context.subscriptions.push( commands.registerCommand( - `${EXTENSION_NS}.deno.initializeWorkspace`, + `${EXTENSION_NS}.manual.deno.initializeWorkspace`, initializeDenoWorkspace, ), ); context.subscriptions.push( commands.registerCommand( - `${EXTENSION_NS}.node.initializeWorkspace`, + `${EXTENSION_NS}.manual.node.initializeWorkspace`, initializeNodeWorkspace, ), ); + + // Re-initialize every time configurations are changed. context.subscriptions.push( workspace.onDidChangeConfiguration(async (evt) => { if (evt.affectsConfiguration(EXTENSION_NS)) { @@ -95,15 +69,21 @@ export const activate = async (context: ExtensionContext) => { } }), ); - (["auto_user_config", "auto_workspace_config"] as const).forEach((mode) => { - (["deno", "node"] as const).forEach((target) => { - context.subscriptions.push( - commands.registerCommand( - `${EXTENSION_NS}.${mode}.${target}.initializeWorkspace`, - () => switchUseConfig(target, mode), - ), - ); - }); - }); + + // Setup commands for automatic configuration. + (["ephemeral", "workspace", "user"] as const).forEach( + (configType: ConfigType) => { + (["deno", "node"] as const).forEach((target) => { + context.subscriptions.push( + commands.registerCommand( + `${EXTENSION_NS}.internal.${configType}.${target}.initializeWorkspace`, + () => configSwitch(target, getSetConfig(configType)), + ), + ); + }); + }, + ); + + // Initialize after launched coc-tsdetect. await initialize(context); }; diff --git a/src/set_config.ts b/src/set_config.ts new file mode 100644 index 0000000..b248c60 --- /dev/null +++ b/src/set_config.ts @@ -0,0 +1,41 @@ +import { workspace } from "coc.nvim"; +import { ConfigType } from "./settings"; + +export const setConfigEphemeral = async ( + ns: string, + key: string, + value: unknown, +) => { + await workspace.nvim.call("coc#config", [`${ns}.${key}`, value]); +}; + +export const setConfigUser = async ( + ns: string, + key: string, + value: unknown, +) => { + const config = workspace.getConfiguration(ns); + config.update(key, value, true); +}; + +export const setConfigWorkspace = async ( + ns: string, + key: string, + value: unknown, +) => { + const config = workspace.getConfiguration(ns); + config.update(key, value); +}; + +export const getSetConfig = (configType: ConfigType) => { + switch (configType) { + case "ephemeral": + return setConfigEphemeral; + case "workspace": + return setConfigWorkspace; + case "user": + return setConfigUser; + default: + throw new Error("Illegal configType."); + } +}; diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..0465eee --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,37 @@ +import { workspace } from "coc.nvim"; +import assert from "assert"; + +export const EXTENSION_NS = "tsdetect"; + +export type ConfigType = "ephemeral" | "user" | "workspace"; + +export interface Settings { + mode: "auto" | "manual"; + configType: ConfigType; + controlTrimSameExts: boolean; + controlTrimSameExtsBase: string[]; + controlTrimSameExtsNode: string[]; + controlTrimSameExtsDeno: string[]; +} +export const settingsKeys = [ + "mode", + "configType", + "controlTrimSameExts", + "controlTrimSameExtsBase", + "controlTrimSameExtsNode", + "controlTrimSameExtsDeno", +]; + +export const getSettings = (): Settings => { + const settings = workspace.getConfiguration(EXTENSION_NS); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const result: any = Object.create(null); + settingsKeys.forEach((key) => { + const value = settings.inspect(key); + assert(value); + result[key] = + value.workspaceValue ?? value.globalValue ?? value.defaultValue; + }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return result; +};