Skip to content

Instantly share code, notes, and snippets.

@Ishotihadus
Last active October 29, 2025 12:22
Show Gist options
  • Save Ishotihadus/8933ab0b2c347dbbe2a2b2f070d3d50e to your computer and use it in GitHub Desktop.
Save Ishotihadus/8933ab0b2c347dbbe2a2b2f070d3d50e to your computer and use it in GitHub Desktop.
Windows 版 VSCode のショートカットキーを Mac 版とほぼ同じにするスクリプト 生成された keybindings.json を追加する 詳細: https://note.com/ishotihadus/n/ne1171989949a
# frozen_string_literal: true
require 'json'
require 'net/http'
@key_map = {}
# ターミナル用に機械的に生成したもの
@ascii_arrays = [['shift', '2'], ['a'], ['b'], ['c'], ['d'], ['e'], ['f'], ['g'], ['h'], ['i'], ['j'], ['k'], ['l'],
['m'], ['n'], ['o'], ['p'], ['q'], ['r'], ['s'], ['t'], ['u'], ['v'], ['w'], ['x'], ['y'], ['z'],
['['], ['\\'], [']'], ['shift', '6'], ['shift', '-']]
@ascii_arrays.map.with_index do |k, i|
a = %w[ctrl alt shift][i / 12]
b = "f#{13 + (i % 12)}"
@key_map[Set['xxxx'] | Set[*k]] = Set[a, b]
end
@ascii_arrays_shift = [nil,
%w[shift a], %w[shift b], %w[shift c], %w[shift d], %w[shift e], %w[shift f], %w[shift g],
%w[shift h], %w[shift i], %w[shift j], %w[shift k], %w[shift l], %w[shift m], %w[shift n],
%w[shift o], %w[shift p], %w[shift q], %w[shift r], %w[shift s], %w[shift t], %w[shift u],
%w[shift v], %w[shift w], %w[shift x], %w[shift y], %w[shift z],
['shift', '['], ['shift', '\\'], ['shift', ']']]
@ascii_arrays_shift.map.with_index do |k, i|
a = [%w[ctrl shift], %w[alt shift], %w[ctrl alt]][i / 12]
b = "f#{13 + (i % 12)}"
@key_map[Set['xxxx'] | Set[*k]] = Set[a, b]
end
# 手動マッピング
@key_map[Set['xxxx', 'space']] = Set['shift', 'f23']
@key_map[Set['xxxx', 'backspace']] = Set['shift', 'f24']
(0..9).each do |n|
@key_map[Set['xxxx', n.to_s]] = Set['alt', n.to_s]
end
# Mac 用のキーを Windows 用に変換する
def replace(keys)
keys = keys.map do |key|
next 'xxxx' if key == 'ctrl'
next 'ctrl' if key == 'cmd'
key
end
keys = Set.new(keys)
return @key_map[keys] if @key_map.key?(keys)
return keys unless keys.include?('xxxx')
nil
end
# 読み込み
macos = JSON.parse(Net::HTTP.get(URI("https://raw.githubusercontent.com/codebling/vs-code-default-keybindings/refs/heads/master/macos.keybindings.json?t=#{Time.now.to_i}")))
windows = JSON.parse(Net::HTTP.get(URI("https://raw.githubusercontent.com/codebling/vs-code-default-keybindings/refs/heads/master/windows.keybindings.json?t=#{Time.now.to_i}")))
# [[command: str, when: str, args: object], key: Array<Set<string>>]
macos = macos.to_h do |e|
key = e['key'].split.map { |s| Set.new(s.split('+')) }
[[e['command'], e['when'], e['args']], key]
end
windows = windows.to_h do |e|
key = e['key'].split.map { |e| Set.new(e.split('+')) }
[[e['command'], e['when'], e['args']], key]
end
#### MacOS にのみあるやつ
puts '--- Processing MacOS only ---'
add_entries = []
# macosonly = macos.reject { |k, _v| windows.key?(k) }
# macosonly.each do |(command, when_str, args), key_seq|
# entry = { command: command, when: when_str, args: args }
# strokes = key_seq.map { |stroke| replace(stroke) }
# if strokes.all?
# entry[:key] = strokes.map { |s| s.join('+') }.join(' ')
# add_entries << entry.compact
# next
# end
# puts "Cannot map: #{command} @#{when_str}"
# puts " keys: #{key_seq.map { |s| s.join('+') }.join(' ')}"
# end
puts "--- End MacOS only (added #{add_entries.size}) ---"
#### Windows にのみあるやつ(いらない)
puts '--- Windows only (removing) ---'
remove_entries = []
windowsonly = windows.reject { |k, _v| macos.key?(k) }
windowsonly.each do |(command, when_str, args), keys|
# workbench.action.terminal. 以外は残しとく
next unless command.start_with?('workbench.action.terminal.')
entry = { command: command, when: when_str, args: args, keys: keys.map { |s| s.join('+') }.join(' ') }
remove_entries << entry.compact
end
puts "--- End Windows only (removed #{remove_entries.size}) ---"
#### 両方にあるやつ(置き換え)
puts '--- Processing Both OS ---'
replace_entries = []
both = windows.select { |k, _v| macos.key?(k) }
both.each do |(command, when_str, args), win_keys|
mac_keys = macos[[command, when_str, args]]
replaced_mac_keys = mac_keys.map { |stroke| replace(stroke) }
next if win_keys == replaced_mac_keys # 同じならスキップ
# mac のほうに ctrl がある
unless replaced_mac_keys.all?
next if mac_keys == win_keys # そういうこともある
puts "Cannot map: #{command} @#{when_str}"
puts " mac keys: #{mac_keys.map { |s| s.join('+') }.join(' ')}"
puts " win keys: #{win_keys.map { |s| s.join('+') }.join(' ')}"
next
end
replace_entries << { command: command, when: when_str, args: args,
win_key: win_keys.map { |s| s.join('+') }.join(' '),
key: replaced_mac_keys.map { |s| s.join('+') }.join(' ') }
end
puts "--- End Both OS (replaced #{replace_entries.size}) ---"
#### 手動追加
# ターミナル系
@ascii_arrays.each_with_index do |_k, i|
a = %w[ctrl alt shift][i / 12]
b = "f#{13 + (i % 12)}"
add_entries << {
command: 'workbench.action.terminal.sendSequence',
when: 'terminalFocus',
args: { text: i.chr }, key: "#{a}+#{b}"
}
end
add_entries << { command: 'workbench.action.terminal.sendSequence',
when: "terminalFocus && terminalShellType == 'cmd'",
args: { text: "\b" }, keys: 'shift+f24' } # = Ctrl-BS
add_entries << { command: 'workbench.action.terminal.sendSequence',
when: 'terminalFocus',
args: { text: "\1bb" }, key: 'ctrl+left' }
add_entries << { command: 'workbench.action.terminal.sendSequence',
when: 'terminalFocus',
args: { text: "\u001bf" }, key: 'ctrl+right' }
# ターミナルでのコピペ
add_entries << { command: 'workbench.action.terminal.paste',
when: 'terminalFocus', key: 'ctrl+v' }
add_entries << { command: 'workbench.action.terminal.copySelection',
when: 'terminalFocus', key: 'ctrl+c' }
remove_entries << { command: 'workbench.action.terminal.paste',
when: 'terminalFocus && terminalHasBeenCreated || terminalFocus && terminalProcessSupported',
key: 'ctrl+v' }
# テキスト操作系
add_entries << { key: 'ctrl+f14',
command: 'cursorHome',
when: 'textInputFocus' }
add_entries << { key: 'ctrl+shift+f14',
command: 'cursorHomeSelect',
when: 'textInputFocus' }
add_entries << { key: 'ctrl+f15',
command: 'cursorLeft',
when: 'textInputFocus' }
add_entries << { key: 'ctrl+shift+f15',
command: 'cursorLeftSelect',
when: 'textInputFocus' }
add_entries << { key: 'ctrl+f17',
command: 'deleteRight',
when: 'textInputFocus' }
add_entries << { key: 'ctrl+f18',
command: 'cursorEnd',
when: 'textInputFocus' }
add_entries << { key: 'ctrl+shift+f18',
command: 'cursorEndSelect',
when: 'textInputFocus' }
add_entries << { key: 'ctrl+f19',
command: 'cursorRight',
when: 'textInputFocus' }
add_entries << { key: 'ctrl+shift+f19',
command: 'cursorRightSelect',
when: 'textInputFocus' }
add_entries << { key: 'ctrl+f21',
command: 'deleteLeft',
when: 'textInputFocus' }
add_entries << { key: 'alt+f15',
command: 'cursorDown',
when: 'textInputFocus' }
add_entries << { key: 'alt+shift+f15',
command: 'cursorDownSelect',
when: 'textInputFocus' }
add_entries << { key: 'alt+f17',
command: 'cursorUp',
when: 'textInputFocus' }
add_entries << { key: 'alt+shift+f17',
command: 'cursorUpSelect',
when: 'textInputFocus' }
# 雑多なもの
add_entries << { key: 'ctrl+home',
command: 'cursorTop',
when: 'textInputFocus' }
add_entries << { key: 'ctrl+end',
command: 'cursorBottom',
when: 'textInputFocus' }
add_entries << { key: 'ctrl+shift+home',
command: 'cursorTopSelect',
when: 'textInputFocus' }
add_entries << { key: 'ctrl+shift+end',
command: 'cursorBottomSelect',
when: 'textInputFocus' }
#### 出力
out = []
# add
out += add_entries.sort_by { |e| e['command'] }
# remove
rem = remove_entries.sort_by { |e| e['command'] }
out += rem.map { |e| { command: "-#{e[:command]}", when: e[:when], args: e[:args], key: e[:key] } }
# replace
rep = replace_entries.sort_by { |e| e['command'] }
rep.each do |e|
out << { command: "-#{e[:command]}", when: e[:when], args: e[:args], key: e[:win_key] }
out << { command: e[:command], when: e[:when], args: e[:args], key: e[:key] }
end
File.write('keybindings.json', JSON.pretty_generate(out.map(&:compact)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment