Skip to content

Commit

Permalink
Add basic editor identifier list dent command
Browse files Browse the repository at this point in the history
  • Loading branch information
trishume committed May 27, 2018
1 parent 94d2dda commit 1bdc9de
Show file tree
Hide file tree
Showing 3 changed files with 307 additions and 0 deletions.
11 changes: 11 additions & 0 deletions debug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from talon.engine import engine
import json

def listener(topic, m):
if topic == 'cmd' and m['cmd']['cmd'] == 'g.load' and m['success'] == True:
print('[grammar reloaded]')
else:
print(topic, m)

# engine.register('', listener)
# def unload(): engine.unregister('', listener)
71 changes: 71 additions & 0 deletions editor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from talon.voice import Context, Key, Rep, Str, press
from talon import ctrl
import talon

import re
from collections import defaultdict

from . import editor_rpc

ctx = Context('editor', bundle='com.sublimetext.3')
editor = None
symbol_mapping = {}

def insert(s):
Str(s)(None)

def type_symbol(m):
name = str(m._words[1])
symbols = symbol_mapping.get(name)
if not symbols: return
# print(name, " - ", symbols)
# TODO disambiguate multiple answers?
insert(symbols[0])

keymap = {
'test identifiers': 'lol it works',
'dent {editor.symbols}': type_symbol,
}
ctx.keymap(keymap)

def camel_case_split(identifier):
return re.finditer('.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', identifier)

def get_words(symbol):
parts = []
for part in symbol.split('_'):
for match in camel_case_split(part):
part = match.group(0)
parts.append(part.lower())

return ' '.join(parts)

def update_symbols(symbols):
global symbol_mapping
# print("UPDATING", symbols)
mapping = defaultdict(lambda: [])
for symbol in symbols:
if len(symbol) <= 1:
continue
mapping[get_words(symbol)].append(symbol)

# print("UPDATING", mapping)
symbol_mapping = dict(mapping)
ctx.set_list('symbols', mapping.keys())


def on_event(client, cmd, msg):
global editor
editor = editor_rpc.active()
# print("Got message", cmd, msg)

if cmd == "update_symbols":
update_symbols(msg['symbols'])

# if not editor:
# ctx.unload()
# return

# ctx.load()

editor_rpc.register(on_event)
225 changes: 225 additions & 0 deletions editor_rpc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import enum
import json
import os
import queue
import select
import socket
import struct
import threading
import traceback
import xmlrpc.client as xmlrpclib
from xml.sax.saxutils import escape

from talon import rctx

class opaque(object):
def __init__(self, s):
self.desc = s

def __repr__(self):
return self.desc

@staticmethod
def load(marshall, s):
marshall.append(opaque(s))
marshall.type = 'opaque'
marshall._value = 0

@staticmethod
def dump(marshall, value, append):
append('<value><opaque>%s</opaque></value>' % escape(value.desc))

xmlrpclib.Unmarshaller.dispatch['opaque'] = opaque.load
xmlrpclib.Marshaller.dispatch[opaque] = opaque.dump

def xml_loads(data):
p, u = xmlrpclib.getparser()
p.feed(data)
p.close()
return u.close()

class NotConnected(Exception): pass

class ClientTransport(xmlrpclib.Transport):
def __init__(self, s):
self.buf = b''
self.s = s
self.q = queue.Queue()
self.need = -1
self.msg_type = -1
self.lock = threading.Lock()
xmlrpclib.Transport.__init__(self)

def recv_size(self, n):
while len(self.buf) < n:
data = self.s.recv(1024)
if not data:
raise socket.error
self.buf += data
out = self.buf[:n]
self.buf = self.buf[n:]
return out

def recv_data(self):
data = self.s.recv(4096)
if not data:
raise socket.error('end of file')
self.buf += data

def _enough(self):
return (self.need == -1 and len(self.buf) >= 5
or self.need >= 0 and len(self.buf) >= self.need)

def on_data(self):
self.recv_data()
out = []
while self._enough():
if self.need == -1:
self.msg_type, self.need, = struct.unpack('>BI', self.buf[:5])
self.buf = self.buf[5:]
else:
data = self.buf[:self.need]
self.buf = self.buf[self.need:]
if self.msg_type == 0:
self.q.put(data)
elif self.msg_type == 1:
out.append(json.loads(data.decode('utf8')))
self.need = -1
return out

def recv(self):
return self.q.get()

def send(self, body):
self.s.send(struct.pack('>I', len(body)))
self.s.send(body)

def close(self):
try: self.s.shutdown(socket.SHUT_RDWR)
except: pass
try: self.s.close()
except: pass
self.q.put(None)

def single_request(self, host, handler, body, verbose=0):
with self.lock:
try:
self.send(body)
data = self.recv()
except socket.error as e:
self.close()
raise
return xml_loads(data)

class Client(xmlrpclib.ServerProxy):
def __init__(self, s):
self._active = False
self._transport = ClientTransport(s)
xmlrpclib.ServerProxy.__init__(self, 'http://.', transport=self._transport, allow_none=True)

def _close(self):
self._transport.close()

def __bool__(self):
return self._active

def __repr__(self):
return '<Client {:#x}>'.format(id(self))
__str__ = __repr__

class Server(object):
def __init__(self):
self.callbacks = set()
self.clients = {}
self.cb = queue.Queue()

def client(self):
if len(self.clients) > 1:
raise RuntimeError("Multiple connected editors, this shouldn't happen")
for k,v in self.clients.items():
return v
return None

def detach(self, client):
print('detached {}'.format(client))
client._close()
if client == self.client:
self.client = None
del self.clients[client._transport.s]

def register(self, cb):
self.callbacks.add(cb)
rctx.register(lambda: self.callbacks.remove(cb))

def emit(self, client, msg):
cmd = msg.pop('cmd', None)
print('emit', client, cmd, msg)
# if cmd == 'active':
# if msg.get('active'):
# if self.client:
# self.client._active = False
# self.client = client
# client._active = True
# elif self.client == client:
# client._active = False

for cb in self.callbacks:
self.cb.put((cb, (client, cmd, msg)))

def cb_thread(self):
while True:
cb, args = self.cb.get()
try:
cb(*args)
except Exception:
traceback.print_exc()

def serve(self, path):
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if os.path.exists(path):
os.unlink(path)
server.bind(path)
server.listen(1)
while True:
sockets = list(self.clients.keys()) + [server]
ready, _, errors = select.select(sockets, [], sockets, 0.5)
for s in ready:
client = None
if s == server:
sock, addr = server.accept()
client = Client(sock)
print('accepted new connection: {}'.format(client))
try:
self.clients[sock] = client
except Exception:
traceback.print_exc()
else:
try:
client = self.clients[s]
result = client._transport.on_data()
try:
for msg in result:
self.emit(client, msg)
except Exception:
traceback.print_exc()
except socket.error:
self.detach(client)
except Exception:
traceback.print_exc()
self.detach(client)

def spawn(self, path):
t = threading.Thread(target=self.serve, args=(path, ))
t.daemon = True
t.start()
t = threading.Thread(target=self.cb_thread)
t.daemon = True
t.start()

server = Server()
server.spawn('/tmp/talon_editor_socket')
register = server.register

def active(): return server.client()
# def clients(): return list(server.clients.values())

0 comments on commit 1bdc9de

Please sign in to comment.