dotfiles/talon/user/community/apps/jetbrains/jetbrains.py

376 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import os.path
import tempfile
from pathlib import Path
from typing import Optional
import requests
from talon import Context, Module, actions, app, clip, ui
# Courtesy of https://github.com/anonfunc/talon-user/blob/master/apps/jetbrains.py
# Each IDE gets its own port, as otherwise you wouldn't be able
# to run two at the same time and switch between them.
# Note that MPS and IntelliJ ultimate will conflict...
port_mapping = {
"com.google.android.studio": 8652,
"com.jetbrains.AppCode": 8655,
"com.jetbrains.CLion": 8657,
"com.jetbrains.datagrip": 8664,
"com.jetbrains.goland-EAP": 8659,
"com.jetbrains.goland": 8659,
"com.jetbrains.intellij-EAP": 8653,
"com.jetbrains.intellij.ce": 8654,
"com.jetbrains.intellij": 8653,
"com.jetbrains.PhpStorm": 8662,
"com.jetbrains.pycharm": 8658,
"com.jetbrains.rider": 8660,
"com.jetbrains.rubymine": 8661,
"com.jetbrains.rubymine-EAP": 8661,
"com.jetbrains.WebStorm": 8663,
"google-android-studio": 8652,
"idea64.exe": 8653,
"IntelliJ IDEA": 8653,
"jetbrains-appcode": 8655,
"jetbrains-clion": 8657,
"jetbrains-datagrip": 8664,
"jetbrains-goland-eap": 8659,
"jetbrains-goland": 8659,
"jetbrains-idea-ce": 8654,
"jetbrains-idea-eap": 8653,
"jetbrains-idea": 8653,
"jetbrains-phpstorm": 8662,
"jetbrains-pycharm-ce": 8658,
"jetbrains-pycharm": 8658,
"jetbrains-rider": 8660,
"JetBrains Rider": 8660,
"jetbrains-rubymine": 8661,
"jetbrains-rubymine-eap": 8661,
"jetbrains-studio": 8652,
"jetbrains-webstorm": 8663,
"RubyMine": 8661,
"RubyMine-EAP": 8661,
"PyCharm": 8658,
"pycharm64.exe": 8658,
"WebStorm": 8663,
"webstorm64.exe": 8663,
}
def _get_nonce(port: int, file_prefix: str) -> Optional[str]:
file_name = file_prefix + str(port)
try:
with open(os.path.join(tempfile.gettempdir(), file_name)) as fh:
return fh.read()
except FileNotFoundError:
try:
with open(Path.home() / file_name) as fh:
return fh.read()
except FileNotFoundError:
print(f"Could not find {file_name} in tmp or home")
return None
except OSError as e:
print(e)
return None
def send_idea_command(cmd: str) -> str:
active_app = ui.active_app()
bundle = active_app.bundle or active_app.name
port = port_mapping.get(bundle, None)
if not port:
raise Exception(f"unknown application {bundle}")
nonce = _get_nonce(port, ".vcidea_") or _get_nonce(port, "vcidea_")
if not nonce:
raise FileNotFoundError(f"Couldn't find IDEA nonce file for port {port}")
response = requests.get(
f"http://localhost:{port}/{nonce}/{cmd}",
proxies={"http": None, "https": None},
timeout=(0.05, 3.05),
)
response.raise_for_status()
return response.text
def get_idea_location() -> list[str]:
return send_idea_command("location").split()
ctx = Context()
mod = Module()
mod.apps.jetbrains = "app.name: /jetbrains/"
mod.apps.jetbrains = "app.name: CLion"
mod.apps.jetbrains = "app.name: IntelliJ IDEA"
mod.apps.jetbrains = "app.name: PhpStorm"
mod.apps.jetbrains = "app.name: PyCharm"
mod.apps.jetbrains = "app.name: WebStorm"
mod.apps.jetbrains = "app.name: RubyMine"
mod.apps.jetbrains = "app.name: RubyMine-EAP"
mod.apps.jetbrains = "app.name: DataGrip"
mod.apps.jetbrains = """
os: mac
and app.bundle: com.google.android.studio
"""
# windows
mod.apps.jetbrains = r"app.exe: /^idea64\.exe$/i"
mod.apps.jetbrains = r"app.exe: /^PyCharm64\.exe$/i"
mod.apps.jetbrains = r"app.exe: /^webstorm64\.exe$/i"
mod.apps.jetbrains = """
os: mac
and app.bundle: com.jetbrains.pycharm
os: mac
and app.bundle: com.jetbrains.rider
"""
mod.apps.jetbrains = r"""
os: windows
and app.name: JetBrains Rider
os: windows
and app.exe: /^rider64\.exe$/i
"""
@mod.action_class
class Actions:
def idea(commands: str):
"""Send a command to Jetbrains product"""
command_list = commands.split(",")
try:
for cmd in command_list:
if cmd:
send_idea_command(cmd.strip())
actions.sleep(0.1)
except Exception as e:
app.notify(e)
raise
def idea_grab(times: int):
"""Copies specified number of words to the left"""
old_clip = clip.get()
try:
original_line, original_column = get_idea_location()
for _ in range(times):
send_idea_command("action EditorSelectWord")
send_idea_command("action EditorCopy")
send_idea_command(f"goto {original_line} {original_column}")
send_idea_command("action EditorPaste")
finally:
clip.set(old_clip)
ctx.matches = r"""
app: jetbrains
"""
@ctx.action_class("app")
class AppActions:
def tab_next():
actions.user.idea("action NextTab")
def tab_previous():
actions.user.idea("action PreviousTab")
def tab_close():
actions.user.idea("action CloseContent")
def tab_reopen():
actions.user.idea("action ReopenClosedTab")
@ctx.action_class("code")
class CodeActions:
# talon code actions
def toggle_comment():
actions.user.idea("action CommentByLineComment")
@ctx.action_class("edit")
class EditActions:
# talon edit actions
def copy():
actions.user.idea("action EditorCopy")
def cut():
actions.user.idea("action EditorCut")
def delete():
actions.user.idea("action EditorBackSpace")
def paste():
actions.user.idea("action EditorPaste")
def find_next():
actions.user.idea("action FindNext")
def find_previous():
actions.user.idea("action FindPrevious")
def find(text: str = None):
actions.user.idea("action Find")
if text:
actions.insert(text)
def line_clone():
actions.user.idea("action EditorDuplicate")
def line_swap_down():
actions.user.idea("action MoveLineDown")
def line_swap_up():
actions.user.idea("action MoveLineUp")
def indent_more():
actions.user.idea("action EditorIndentLineOrSelection")
def indent_less():
actions.user.idea("action EditorUnindentSelection")
def select_line(n: int = None):
actions.user.idea("action EditorSelectLine")
def select_word():
actions.user.idea("action EditorSelectWord")
def select_all():
actions.user.idea("action $SelectAll")
def file_start():
actions.user.idea("action EditorTextStart")
def file_end():
actions.user.idea("action EditorTextEnd")
def extend_file_start():
actions.user.idea("action EditorTextStartWithSelection")
def extend_file_end():
actions.user.idea("action EditorTextEndWithSelection")
def extend_word_left():
actions.user.idea("action EditorPreviousWordWithSelection")
def extend_word_right():
actions.user.idea("action EditorNextWordWithSelection")
def jump_line(n: int):
actions.user.idea(f"goto {n} 0")
# move the cursor to the first nonwhite space character of the line
actions.user.idea("action EditorLineEnd")
actions.user.idea("action EditorLineStart")
@ctx.action_class("win")
class WinActions:
def filename() -> str:
title: str = actions.win.title()
result = title.split()
# iterate over reversed result
# to support titles such as
# Class.Library2 a.js [.workspace]
for word in reversed(result):
if not word.startswith("[") and "." in word:
return word
return ""
@ctx.action_class("user")
class UserActions:
def tab_jump(number: int):
# depends on plugin GoToTabs
if number < 10:
actions.user.idea(f"action GoToTab{number}")
def extend_until_line(line: int):
actions.user.idea(f"extend {line}")
def select_range(line_start: int, line_end: int):
# if it's a single line, select the entire thing including the ending new-line5
if line_start == line_end:
actions.user.idea(f"goto {line_start} 0")
actions.user.idea("action EditorSelectLine")
else:
actions.user.idea(f"range {line_start} {line_end}")
def extend_camel_left():
actions.user.idea("action EditorPreviousWordInDifferentHumpsModeWithSelection")
def extend_camel_right():
actions.user.idea("action EditorNextWordInDifferentHumpsModeWithSelection")
def camel_left():
actions.user.idea("action EditorPreviousWordInDifferentHumpsMode")
def camel_right():
actions.user.idea("action EditorNextWordInDifferentHumpsMode")
def command_search(command: str = ""):
actions.user.idea("action GotoAction")
if command != "":
actions.insert(command)
def line_clone(line: int):
actions.user.idea(f"clone {line}")
# multi-cursor tag functions
def multi_cursor_enable():
actions.skip()
def multi_cursor_disable():
actions.key("escape")
def multi_cursor_add_above():
actions.user.idea("action EditorCloneCaretAbove")
def multi_cursor_add_below():
actions.user.idea("action EditorCloneCaretBelow")
def multi_cursor_select_fewer_occurrences():
actions.user.idea("action UnselectPreviousOccurrence")
def multi_cursor_select_more_occurrences():
actions.user.idea("action SelectNextOccurrence")
# def multi_cursor_skip_occurrence():
def multi_cursor_select_all_occurrences():
actions.user.idea("action SelectAllOccurrences")
def multi_cursor_add_to_line_ends():
actions.user.idea("action EditorAddCaretPerSelectedLine")
# splits tag functions
# def split_window_right():
# actions.user.idea("action OpenInRightSplit")
# def split_window_left():
# def split_window_down():
# def split_window_up():
def split_window_vertically():
actions.user.idea("action SplitVertically")
def split_window_horizontally():
actions.user.idea("action SplitHorizontally")
def split_flip():
actions.user.idea("action ChangeSplitOrientation")
def split_maximize():
actions.key("ctrl-shift-f12")
def split_reset():
actions.key("shift-f12")
# def split_window():
def split_clear():
actions.user.idea("action Unsplit")
def split_clear_all():
actions.user.idea("action UnsplitAll")
def split_next():
actions.user.idea("action NextSplitter")
# def split_last():
# def split_number(index: int):