def summon(self, subl_view): if 'highlight' not in G.PERMS: return buf = get_buf(subl_view) if buf: msg.debug('summoning selection in subl_view ', buf['path'], ', buf id ', buf['id']) c = [[x.a, x.b] for x in subl_view.sel()] if self.joined_workspace: self.send({ 'id': buf['id'], 'name': 'highlight', 'ranges': c, 'ping': True, 'summon': True, 'following': False, }) return path = subl_view.file_name() if not utils.is_shared(path): sublime.error_message('Can\'t summon because %s is not in shared path %s.' % (path, G.PROJECT_PATH)) return share = sublime.ok_cancel_dialog('This file isn\'t shared. Would you like to share it?', 'Share') if share: sel = [[x.a, x.b] for x in subl_view.sel()] self.create_buf_cbs[utils.to_rel_path(path)] = lambda buf_id: send_summon(buf_id, sel) self.upload(path)
def set_text(self, text): msg.debug('\n\nabout to patch %s %s' % (str(self), self.vim_buf.name)) try: self.vim_buf[:] = text.encode('utf-8').split('\n') except Exception as e: msg.error("couldn't apply patches because: %s!\nThe unencoded text was: %s" % (str(e), text)) raise
def _connect(self, attempts=0): if attempts > 500: msg.error('Connection attempt timed out.') return self.reconnect() if not self.sock: msg.debug('_connect: No socket') return try: self.sock.connect((self.host, self.port)) select.select([self.sock], [self.sock], [], 0) except socket.error as e: if e.errno == iscon_errno: pass elif e.errno in connect_errno: return utils.set_timeout(self._connect, 20, attempts + 1) else: msg.error('Error connecting:', e) return self.reconnect() if self.secure: sock_debug('SSL-wrapping socket') self.sock = ssl.wrap_socket(self.sock, ca_certs=self.cert_path, cert_reqs=ssl.CERT_REQUIRED, do_handshake_on_connect=False) self.on_connect() self.call_select = True self.select()
def _on_highlight(self, data): buf_id = data['id'] user_id = data['user_id'] username = data.get('username', 'an unknown user') ping = G.STALKER_MODE or data.get('ping', False) previous_highlight = self.user_highlights.get(user_id) buf = self.bufs[buf_id] view = self.get_view(buf_id) if not view: if not ping: return view = self.create_view(buf) if not view: return data['path'] = buf['path'] self.user_highlights[user_id] = data if ping: try: offset = data['ranges'][0][0] except IndexError as e: msg.debug('could not get offset from range %s' % e) else: if data.get('ping'): msg.log('You have been summoned by %s' % (username)) view.focus() view.set_cursor_position(offset) if G.SHOW_HIGHLIGHTS: if previous_highlight and previous_highlight['id'] == data['id']: view.clear_highlight(user_id) view.highlight(data['ranges'], user_id)
def on_activated(self, view, agent): buf = get_buf(view) if not buf: return msg.debug('activated view ', buf['path'], ' buf id ', buf['id']) self.on_modified(view, agent, True) self.on_selection_modified(view)
def set_cursor_position(self, offset): line_num, col = self._offset_to_vim(offset) command = 'setpos(".", [%s, %s, %s, %s])' % (self.native_id, line_num, col, 0) msg.debug("setting pos: %s" % command) rv = int(vim.eval(command)) if rv != 0: msg.debug('SHIIIIIIIIT %s' % rv)
def clear_highlights(self, view): buf = get_buf(view) if not buf: return msg.debug('clearing highlights in ', buf['path'], ', buf id ', buf['id']) for user_id, username in self.workspace_info['users'].items(): view.erase_regions('floobits-highlight-%s' % user_id)
def clear_highlights(self, view): buf = get_buf(view) if not buf: return msg.debug("clearing highlights in %s, buf id %s" % (buf["path"], buf["id"])) for user_id, username in self.workspace_info["users"].items(): view.erase_regions("floobits-highlight-%s" % user_id)
def open_workspace_window2(abs_path, cb): if sublime.platform() == 'linux': subl = open('/proc/self/cmdline').read().split(chr(0))[0] elif sublime.platform() == 'osx': floorc = utils.load_floorc_json() subl = floorc.get('SUBLIME_EXECUTABLE') if not subl: settings = sublime.load_settings('Floobits.sublime-settings') subl = settings.get('sublime_executable', '/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl') if not os.path.exists(subl): return sublime.error_message('''Can't find your Sublime Text executable at %s. Please add "sublime_executable": "/path/to/subl" to your ~/.floorc.json and restart Sublime Text''' % subl) elif sublime.platform() == 'windows': subl = sys.executable else: raise Exception('WHAT PLATFORM ARE WE ON?!?!?') command = [subl] if get_workspace_window(abs_path) is None: command.append('--new-window') command.append('--add') command.append(G.PROJECT_PATH) msg.debug('command:', command) p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) poll_result = p.poll() msg.debug('poll:', poll_result) cb()
def put(self, item): if not item: return self.sock_q.put(json.dumps(item) + "\n") qsize = self.sock_q.qsize() if qsize > 0: msg.debug("%s items in q" % qsize)
def connect(self, cb=None): self.stop(False) self.empty_selects = 0 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.secure: if ssl: cert_path = os.path.join(G.COLAB_DIR, "startssl-ca.pem") with open(cert_path, "wb") as cert_fd: cert_fd.write(cert.CA_CERT.encode("utf-8")) self.sock = ssl.wrap_socket(self.sock, ca_certs=cert_path, cert_reqs=ssl.CERT_REQUIRED) else: msg.log("No SSL module found. Connection will not be encrypted.") if self.port == G.DEFAULT_PORT: self.port = 3148 # plaintext port msg.debug("Connecting to %s:%s" % (self.host, self.port)) try: self.sock.connect((self.host, self.port)) if self.secure and ssl: self.sock.do_handshake() except socket.error as e: msg.error("Error connecting:", e) self.reconnect() return self.sock.setblocking(0) msg.debug("Connected!") self.reconnect_delay = self.INITIAL_RECONNECT_DELAY self.send_auth() if cb: cb()
def clear_highlight(self, user_id): msg.debug('clearing selections for user %s in view %s' % (user_id, self.vim_buf.name)) if user_id not in self.current_highlights: return for hl in self.current_highlights[user_id]: vim.command(":silent! :call matchdelete(%s)" % (hl,)) del self.current_highlights[user_id]
def rename(self, name): msg.debug("renaming %s" % name) self._emacs.send({ 'name': 'rename', 'new_name': name, 'full_path': self.full_path })
def on_highlight(self, data): # floobits.highlight(data['id'], region_key, data['username'], data['ranges'], data.get('ping', False)) #buf_id, region_key, username, ranges, ping=False): ping = data.get('ping', False) if self.follow_mode: ping = True buf = self.FLOO_BUFS[data['id']] view = self.get_view(data['id']) if not view: if not ping: return view = self.create_view(buf) if not view: return if ping: try: offset = data['ranges'][0][0] except IndexError as e: msg.debug('could not get offset from range %s' % e) else: msg.log('You have been summoned by %s' % (data.get('username', 'an unknown user'))) view.focus() view.set_cursor_position(offset) if G.SHOW_HIGHLIGHTS: view.highlight(data['ranges'], data['user_id'])
def _scan_dir(ig): path = ig.path if not utils.is_shared(path): msg.error('Skipping adding %s because it is not in shared path %s.' % (path, G.PROJECT_PATH)) return ignored = ig.is_ignored(path) if ignored: msg.log('Not creating buf: %s' % (ignored)) return msg.debug('create_buf: path is %s' % path) if not os.path.isdir(path): yield path return try: paths = os.listdir(path) except Exception as e: msg.error('Error listing path %s: %s' % (path, unicode(e))) return for p in paths: p_path = os.path.join(path, p) if p[0] == '.': if p not in ignore.HIDDEN_WHITELIST: msg.log('Not creating buf for hidden path %s' % p_path) continue ignored = ig.is_ignored(p_path) if ignored: msg.log('Not creating buf: %s' % (ignored)) continue yield p_path
def open_workspace_window2(abs_path, cb): if sublime.platform() == "linux": subl = open("/proc/self/cmdline").read().split(chr(0))[0] elif sublime.platform() == "osx": floorc = utils.load_floorc_json() subl = floorc.get("SUBLIME_EXECUTABLE") if not subl: settings = sublime.load_settings("Floobits.sublime-settings") subl = settings.get( "sublime_executable", "/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl" ) if not os.path.exists(subl): return sublime.error_message( """Can't find your Sublime Text executable at %s. Please add "sublime_executable": "/path/to/subl" to your ~/.floorc.json and restart Sublime Text""" % subl ) elif sublime.platform() == "windows": subl = sys.executable else: raise Exception("WHAT PLATFORM ARE WE ON?!?!?") command = [subl] if get_workspace_window(abs_path) is None: command.append("--new-window") command.append("--add") command.append(G.PROJECT_PATH) msg.debug("command:", command) p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) poll_result = p.poll() msg.debug("poll:", poll_result) cb()
def put(self, item): if not item: return self.sock_q.put(json.dumps(item) + '\n') qsize = self.sock_q.qsize() if qsize > 0: msg.debug('%s items in q' % qsize)
def on_rename_buf(self, data): buf = self.FLOO_BUFS[int(data['id'])] # This can screw up if someone else renames the buffer around the same time as us. Oh well. buf = self.get_buf_by_path(utils.get_full_path(data['path'])) if not buf: return super(Protocol, self).on_rename_buf(data) msg.debug('We already renamed %s. Skipping' % buf['path'])
def _set_highlight(self, ranges, user_id): msg.debug('highlighting ranges %s' % (ranges)) if vim.current.buffer.number != self.vim_buf.number: return region = user_id_to_region(user_id) hl_rule = HL_RULES[user_id % len(HL_RULES)] vim.command(":silent! highlight %s %s" % (region, hl_rule)) self.clear_highlight(user_id) for _range in ranges: start_row, start_col = self._offset_to_vim(_range[0]) end_row, end_col = self._offset_to_vim(_range[1]) if start_row == end_row and start_col == end_col: if end_col >= len(self.vim_buf[end_row - 1]): end_row += 1 end_col = 1 else: end_col += 1 vim_region = "matchadd('{region}', '\%{start_row}l\%{start_col}v\_.*\%{end_row}l\%{end_col}v', 100)".\ format(region=region, start_row=start_row, start_col=start_col, end_row=end_row, end_col=end_col) msg.debug("vim_region: %s" % (vim_region,)) self.current_highlights[user_id].append(vim.eval(vim_region)) redraw()
def _on_delete_buf(self, req): buf = self.get_buf_by_path(req["path"]) if not buf: msg.debug("No buffer for path %s" % req["path"]) return msg.log("deleting buffer ", buf["path"]) self.send_to_floobits({"name": "delete_buf", "id": buf["id"]})
def update_view(self, buf, view=None): msg.debug('updating view for buf %s' % buf['id']) view = view or self.get_view(buf['id']) if not view: msg.log('view for buf %s not found. not updating' % buf['id']) return self.MODIFIED_EVENTS.put(1) view.set_text(buf['buf'])
def put(item): if not item: return msg.debug('writing %s: %s' % (item.get('name', 'NO NAME'), item)) SOCKET_Q.append(json.dumps(item) + '\n') qsize = len(SOCKET_Q) if qsize > 0: msg.debug('%s items in q' % qsize)
def maybe_selection_changed(self, vim_buf, is_ping): buf = self.get_buf(vim_buf) if not buf: msg.debug('no buffer found for view %s' % vim_buf.number) return view = self.get_view(buf['id']) msg.debug("selection changed: %s %s %s" % (vim_buf.number, buf['id'], view)) self.SELECTION_CHANGED.append([view, is_ping])
def on_load(self, view): msg.debug('Sublime loaded %s' % self.name(view)) buf = get_buf(view) if buf: f = ON_LOAD.get(buf['id']) if f: del ON_LOAD[buf['id']] f()
def set_text(self, text): msg.debug('\n\nabout to patch %s %s' % (str(self), self.vim_buf.name)) try: msg.debug("now buf is loadedish? %s" % vim.eval('bufloaded(%s)' % self.native_id)) self.vim_buf[:] = text.encode('utf-8').split('\n') except Exception as e: msg.error("couldn't apply patches because: %s!\nThe unencoded text was: %s" % (str(e), text)) raise
def complete_signup(self): if not self.start_ticker(): return msg.debug('Completing signup.') if not utils.has_browser(): msg.log('You need a modern browser to complete the sign up. Go to https://floobits.com to sign up.') return VUI.pinocchio()
def check_credentials(): msg.debug('Print checking credentials.') if utils.can_auth(): return if not utils.has_browser(): msg.log('You need a Floobits account to use the Floobits plugin. Go to https://floobits.com to sign up.') return yield VUI.create_or_link_account, None, G.DEFAULT_HOST, False
def maybe_selection_changed(self, vim_buf, is_ping): buf = self.get_buf_by_path(vim_buf.name) if not buf: msg.debug('no buffer found for view %s' % vim_buf.number) return view = self.get_view(buf['id']) msg.debug("selection changed: %s %s %s" % (vim_buf.number, buf['id'], view)) self.selection_changed.append([vim_buf, buf, is_ping])
def is_shared(self, p): if not self.agent.is_ready(): msg.debug('agent is not ready. %s is not shared' % p) return False p = utils.unfuck_path(p) # TODO: tokenize on path seps and then look for .. if utils.to_rel_path(p).find("../") == 0: return False return True
def clear_highlights(view): if not G.AGENT: return buf = get_buf(view) if not buf: return msg.debug('clearing highlights in %s, buf id %s' % (buf['path'], buf['id'])) for user_id, username in G.AGENT.workspace_info['users'].items(): view.erase_regions('floobits-highlight-%s' % user_id)
def check_and_join_workspace(self, args): if not self.start_ticker(): return workspace_url = args[0] self.set_globals() try: r = api.get_workspace_by_url(workspace_url) except Exception as e: return editor.error_message('Error joining %s: %s' % (workspace_url, str(e))) if r.code >= 400: return editor.error_message('Error joining %s: %s' % (workspace_url, r.body)) msg.debug('Workspace %s exists' % workspace_url) return self.join_workspace(workspace_url)
def get_view_by_path(self, path): """Warning: side effects!""" if not path: return None buf = self.get_buf_by_path(path) if not buf: msg.debug("buf not found for path %s" % path) return None view = self.get_view(buf['id']) if not view: msg.debug("view not found for %s %s" % (buf['id'], buf['path'])) return None return view
def _on_highlight(self, req): view = self.get_view_by_path(req['full_path']) if not view: return highlight_json = { 'id': view.buf['id'], 'name': 'highlight', 'ranges': req['ranges'], 'following': bool(req['following']), 'ping': req.get("ping"), } msg.debug("sending highlight upstream %s" % highlight_json) self.send_to_floobits(highlight_json)
def create_view(self, buf): path = buf['path'] utils.save_buf(buf) vb = self.get_vim_buf_by_path(path) if vb: return View(vb) vim.command(':edit! %s' % path) vb = self.get_vim_buf_by_path(path) if vb is None: msg.debug( 'vim buffer is none even though we tried to open it: %s' % path) return return View(vb)
def _on_buffer_list_change(self, req): added = req.get('added') or {} for path, text in added.items(): buf = self.get_buf_by_path(path) buf_id = buf and int(buf.get('id')) d = buf and 'buf' in buf and self.agent.on_load.get(buf_id) if d: self.emacs_bufs[path][0] = buf['buf'] else: self.emacs_bufs[path][0] = text if not buf: msg.debug('no buf for path %s' % path) if 'create_buf' in G.PERMS and utils.is_shared(path) and G.IGNORE and not G.IGNORE.is_ignored(path): self.agent._upload(path, text=text) elif path in self.emacs_bufs: del self.emacs_bufs[path] continue view = self.views.get(buf_id) if view is None: self.get_view(buf_id) elif view.is_loading(): view._emacs_buf = self.emacs_bufs[path] else: msg.debug('view for buf %s already exists. this is not good. we got out of sync' % buf['path']) if d: del self.agent.on_load[buf_id] for _, f in d.items(): f() deleted = req.get('deleted') or [] for path in deleted: if self.emacs_bufs.get(path) is None: msg.debug('emacs deleted %s but we already deleted it from emacs_bufs' % path) if path in self.emacs_bufs: del self.emacs_bufs[path] buf = self.get_buf_by_path(path) if buf and buf['id'] in self.views: del self.views[buf['id']] seen = set() current = req.get('current') or [] for path in current: if self.emacs_bufs.get(path) is None: msg.debug('We should have buffer %s in emacs_bufs but we don\'t' % path) else: seen.add(path) for buf_id, view in self.views.items(): if utils.get_full_path(view.buf['path']) not in seen: msg.debug('We should not have buffer %s in our views but we do.' % view.buf['path'])
def apply_patches(self, buf, patches, username): cursor_offset = self.get_cursor_offset() msg.debug('cursor offset is %s bytes' % cursor_offset) self.set_text(patches[0]) for patch in patches[2]: offset = patch[0] length = patch[1] patch_text = patch[2] if cursor_offset > offset: new_offset = len(patch_text) - length cursor_offset += new_offset self.set_cursor_position(cursor_offset)
def summon(self, view): buf = get_buf(view) if buf: msg.debug('summoning selection in view %s, buf id %s' % (buf['path'], buf['id'])) self.selection_changed.append((view, buf, True)) else: path = view.file_name() if not utils.is_shared(path): editor.error_message('Can\'t summon because %s is not in shared path %s.' % (path, G.PROJECT_PATH)) return share = editor.ok_cancel_dialog('This file isn\'t shared. Would you like to share it?', 'Share') if share: sel = [[x.a, x.b] for x in view.sel()] self.create_buf_cbs[utils.to_rel_path(path)] = lambda buf_id: send_summon(buf_id, sel) self.upload(path)
def tick(self): reported = set() while self.views_changed: v, buf = self.views_changed.pop() if not self.joined_workspace: msg.debug('Not connected. Discarding view change.') continue if 'patch' not in G.PERMS: continue if 'buf' not in buf: msg.debug('No data for buf %s %s yet. Skipping sending patch' % (buf['id'], buf['path'])) continue view = View(v, buf) if view.is_loading(): msg.debug( 'View for buf %s is not ready. Ignoring change event' % buf['id']) continue if view.native_id in reported: continue reported.add(view.native_id) patch = utils.FlooPatch(view.get_text(), buf) # Update the current copy of the buffer buf['buf'] = patch.current buf['md5'] = hashlib.md5(patch.current.encode('utf-8')).hexdigest() self.send(patch.to_json()) reported = set() while self.selection_changed: v, buf, summon = self.selection_changed.pop() if not self.joined_workspace: msg.debug('Not connected. Discarding selection change.') continue # consume highlight events to avoid leak if 'highlight' not in G.PERMS: continue view = View(v, buf) vb_id = view.native_id if vb_id in reported: continue reported.add(vb_id) highlight_json = { 'id': buf['id'], 'name': 'highlight', 'ranges': view.get_selections(), 'ping': summon, 'summon': summon, 'following': G.FOLLOW_MODE, } self.send(highlight_json) self._status_timeout += 1 if self._status_timeout > (2000 / G.TICK_TIME): self.update_status_msg()
def poll_for_move(): msg.debug('poll_for_move') win.focus_view(view) win.set_view_index(view, win.num_groups() - 1, 0) if not get_view_in_group(view.buffer_id(), focus_group): return utils.set_timeout(poll_for_move, 20) msg.debug('found view, now moving ', view.name(), win.num_groups() - 1) swap_regions(view) view.show(regions[0]) win.focus_view(view) utils.set_timeout(win.focus_group, 0, 0) try: del self.temp_ignore_highlight[buf_id] except Exception: pass
def buf_enter(self): buf = G.AGENT.get_buf_by_path(self.vim.current.buffer.name) if not buf: return buf_id = buf['id'] d = G.AGENT.on_load.get(buf_id) if d: del G.AGENT.on_load[buf_id] try: d['patch']() except Exception as e: msg.debug('Error running on_load patch handler for buf %s: %s' % (buf_id, str(e))) # NOTE: we call highlight twice in follow mode... thats stupid for user_id, highlight in G.AGENT.user_highlights.items(): if highlight['id'] == buf_id: G.AGENT._on_highlight(highlight)
def rename(self, name): msg.debug('renaming %s to %s' % (self.vim_buf.name, name)) current = vim.current.buffer text = self.get_text() old_name = self.vim_buf.name old_number = self.native_id with open(name, 'wb') as fd: fd.write(text.encode('utf-8')) vim.command('edit! %s' % name) self.vim_buf = vim.current.buffer vim.command('edit! %s' % current.name) vim.command('bdelete! %s' % old_number) try: utils.rm(old_name) except Exception as e: msg.debug("couldn't delete %s... maybe thats OK?" % str(e))
def handle(self, data): name = data.get('name') if not name: return msg.error('no name in data?!?') func = getattr(self, "on_%s" % (name), None) if not func: return msg.debug('unknown name', name, 'data:', data) func(data)
def delete_buf(buf_id): # TODO: somehow tell the user about this view = get_view(buf_id) try: if view: view.set_scratch(True) G.WORKSPACE_WINDOW.focus_view(view) G.WORKSPACE_WINDOW.run_command("close_file") except Exception as e: msg.debug('Error closing view: %s' % unicode(e)) try: buf = BUFS.get(buf_id) if buf: del PATHS_TO_IDS[buf['path']] del BUFS[buf_id] except KeyError: msg.debug('KeyError deleting buf id %s' % buf_id)
def maybe_new_file(self): path = self.vim.current.buffer.name if path is None or path == '': msg.debug('get:buf buffer has no filename') return if not os.path.exists(path): return if not utils.is_shared(path): msg.debug('get_buf: %s is not shared' % path) return buf = G.AGENT.get_buf_by_path(path) if not buf: if not G.IGNORE: msg.warn('G.IGNORE is not set. Uploading anyway.') G.AGENT.upload(path) if G.IGNORE and not G.IGNORE.is_ignored(path, None, True): G.AGENT.upload(path)
def push(): reported = set() while Listener.views_changed: view, buf = Listener.views_changed.pop() if not G.JOINED_WORKSPACE: msg.debug('Not connected. Discarding view change.') continue if view.is_loading(): msg.debug( 'View for buf %s is not ready. Ignoring change event' % buf['id']) continue if 'patch' not in G.PERMS: continue vb_id = view.buffer_id() if vb_id in reported: continue if 'buf' not in buf: msg.debug('No data for buf %s %s yet. Skipping sending patch' % (buf['id'], buf['path'])) continue reported.add(vb_id) patch = utils.FlooPatch(get_text(view), buf) # Update the current copy of the buffer buf['buf'] = patch.current buf['md5'] = hashlib.md5(patch.current.encode('utf-8')).hexdigest() G.AGENT.put(patch.to_json()) reported = set() while Listener.selection_changed: view, buf, summon = Listener.selection_changed.pop() if not G.JOINED_WORKSPACE: msg.debug('Not connected. Discarding selection change.') continue # consume highlight events to avoid leak if 'highlight' not in G.PERMS: continue vb_id = view.buffer_id() if vb_id in reported: continue reported.add(vb_id) sel = view.sel() highlight_json = { 'id': buf['id'], 'name': 'highlight', 'ranges': [[x.a, x.b] for x in sel], 'ping': summon, 'summon': summon, } G.AGENT.put(highlight_json)
def push(self): reported = set() while self.BUFS_CHANGED: buf_id = self.BUFS_CHANGED.pop() view = self.get_view(buf_id) buf = view.buf if view.is_loading(): msg.debug('View for buf %s is not ready. Ignoring change event' % buf['id']) continue if 'patch' not in self.perms: continue vb_id = view.native_id if vb_id in reported: continue if 'buf' not in buf: msg.debug('No data for buf %s %s yet. Skipping sending patch' % (buf['id'], buf['path'])) continue reported.add(vb_id) patch = utils.FlooPatch(view.get_text(), buf) # Update the current copy of the buffer buf['buf'] = patch.current buf['md5'] = hashlib.md5(patch.current.encode('utf-8')).hexdigest() self.agent.put(patch.to_json()) while self.SELECTION_CHANGED: view, ping = self.SELECTION_CHANGED.pop() # consume highlight events to avoid leak if 'highlight' not in self.perms: continue vb_id = view.native_id if vb_id in reported: continue reported.add(vb_id) highlight_json = { 'id': view.buf['id'], 'name': 'highlight', 'ranges': view.get_selections(), 'ping': ping, } self.agent.put(highlight_json)
def _on_delete_buf(self, data): # TODO: somehow tell the user about this buf_id = data['id'] view = self.get_view(buf_id) try: if view: view = view.view view.set_scratch(True) G.WORKSPACE_WINDOW.focus_view(view) G.WORKSPACE_WINDOW.run_command("close_file") except Exception as e: msg.debug('Error closing view: %s' % unicode(e)) try: buf = self.bufs.get(buf_id) if buf: del self.paths_to_ids[buf['path']] del self.bufs[buf_id] except KeyError: msg.debug('KeyError deleting buf id %s' % buf_id) super(self.__class__, self)._on_delete_buf(data)
def _on_rename_buf(self, req): old_path = utils.to_rel_path(req['old_path']) buf = self.get_buf_by_path(old_path) if not buf: msg.debug('No buffer for path %s' % req['path']) return path = utils.to_rel_path(req['path']) if not utils.is_shared(path): msg.log('New path %s is not shared. Discarding rename event.' % path) return buf_id = buf['id'] self.send_to_floobits({ 'name': 'rename_buf', 'id': buf['id'], 'path': path, }) # KANS: is this right? old code... old_path = self.agent.bufs[buf_id]['path'] del self.agent.paths_to_ids[old_path] self.agent.paths_to_ids[path] = buf_id self.agent.bufs[buf_id]['path'] = path
def on_modified(self, view, agent): buf = is_view_loaded(view) if not buf: return text = get_text(view) if buf['encoding'] != 'utf8': return msg.warn('Floobits does not support patching binary files at this time') text = text.encode('utf-8') view_md5 = hashlib.md5(text).hexdigest() if view_md5 == G.VIEW_TO_HASH.get(view.buffer_id()): return G.VIEW_TO_HASH[view.buffer_id()] = view_md5 msg.debug('changed view %s buf id %s' % (buf['path'], buf['id'])) self.disable_follow_mode(2000) buf['forced_patch'] = False agent.views_changed.append((view, buf))
def _on_highlight(self, data): buf_id = data['id'] user_id = data['user_id'] username = data.get('username', 'an unknown user') ping = data.get('ping', False) if ping or not data.get('following'): self.last_highlight = data self.last_highlight_by_user[username] = data if not ping and G.FOLLOW_MODE: if not G.FOLLOW_USERS: ping = True else: ping = username in G.FOLLOW_USERS previous_highlight = self.user_highlights.get(user_id) buf = self.bufs.get(buf_id) if not buf: return view = self.get_view(buf_id) if not view: if not ping: return view = self.create_view(buf) if not view: return data['path'] = buf['path'] self.user_highlights[user_id] = data if ping: try: offset = data['ranges'][0][0] except IndexError as e: msg.debug('could not get offset from range %s' % e) else: if data.get('ping'): msg.log('You have been summoned by %s' % (username)) view.focus() view.set_cursor_position(offset) if G.SHOW_HIGHLIGHTS: if previous_highlight and previous_highlight['id'] == data['id']: view.clear_highlight(user_id) view.highlight(data['ranges'], user_id)
def delete_buf(self, path, unlink=False): if not utils.is_shared(path): msg.error('Skipping deleting ', path, ' because it is not in shared path ', G.PROJECT_PATH, '.') return if os.path.isdir(path): for dirpath, dirnames, filenames in os.walk(path): # TODO: rexamine this assumption # Don't care about hidden stuff dirnames[:] = [d for d in dirnames if d[0] != '.'] for f in filenames: f_path = os.path.join(dirpath, f) if f[0] == '.': msg.log('Not deleting buf for hidden file ', f_path) else: self.delete_buf(f_path, unlink) return buf_to_delete = self.get_buf_by_path(path) if buf_to_delete is None: msg.error(path, ' is not in this workspace') if unlink: try: path = utils.get_full_path(path) msg.log('deleting ', utils.to_rel_path(path)) utils.rm(path) except Exception as e: msg.debug('Error deleting ', path, ': ', str_e(e)) return msg.log('deleting buffer ', utils.to_rel_path(path)) event = { 'name': 'delete_buf', 'id': buf_to_delete['id'], 'unlink': unlink, } def done(d): self._on_delete_buf(event) self.send(event, done)
def connect(self, cb=None): self.stop(False) self.empty_selects = 0 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.secure: if ssl: with open(self.cert_path, 'wb') as cert_fd: cert_fd.write(cert.CA_CERT.encode('utf-8')) self.sock = ssl.wrap_socket(self.sock, ca_certs=self.cert_path, cert_reqs=ssl.CERT_REQUIRED) else: msg.debug( 'No SSL module found. Connection will not be encrypted.') if self.port == G.DEFAULT_PORT: self.port = 3148 # plaintext port msg.debug('Connecting to %s:%s' % (self.host, self.port)) try: self.sock.connect((self.host, self.port)) if self.secure and ssl: self.sock.do_handshake() except socket.error as e: msg.error('Error connecting:', e) self.reconnect() return self.sock.setblocking(0) msg.debug('Connected!') self.reconnect_delay = self.INITIAL_RECONNECT_DELAY self.send_auth() if cb: cb()
def tick(self): reported = set() while self.bufs_changed: buf_id = self.bufs_changed.pop() view = self.get_view(buf_id) if view is None: msg.debug('View for buf %s no longer exists. Ignoring change event' % buf_id) continue buf = view.buf if view.is_loading(): msg.debug('View for buf %s is not ready. Ignoring change event' % buf['id']) continue if 'patch' not in G.PERMS: continue vb_id = view.native_id if vb_id in reported: continue if 'buf' not in buf: msg.debug('No data for buf %s %s yet. Skipping sending patch' % (buf['id'], buf['path'])) continue reported.add(vb_id) patch = utils.FlooPatch(view.get_text(), view.buf) # Update the current copy of the buffer buf['buf'] = patch.current buf['md5'] = hashlib.md5(patch.current.encode('utf-8')).hexdigest() self.send_to_floobits(patch.to_json())
def _scan_dir(ig): path = ig.path if not utils.is_shared(path): msg.error( 'Skipping adding %s because it is not in shared path %s.' % (path, G.PROJECT_PATH)) return if os.path.islink(path): msg.error('Skipping adding %s because it is a symlink.' % path) return ignored = ig.is_ignored(path) if ignored: msg.log('Not creating buf: %s' % (ignored)) return msg.debug('create_buf: path is %s' % path) if not os.path.isdir(path): yield path return try: paths = os.listdir(path) except Exception as e: msg.error('Error listing path %s: %s' % (path, unicode(e))) return for p in paths: p_path = os.path.join(path, p) if p[0] == '.': if p not in ignore.HIDDEN_WHITELIST: msg.log('Not creating buf for hidden path %s' % p_path) continue ignored = ig.is_ignored(p_path) if ignored: msg.log('Not creating buf: %s' % (ignored)) continue yield p_path
def highlight(self, ranges, user_id): msg.debug('highlighting ranges %s' % (ranges)) return # TODO: figure out how to highlight a region region = "floobitsuser%s" % str(user_id) for _range in ranges: start_row, start_col = self._offset_to_vim(_range[0]) end_row, end_col = self._offset_to_vim(_range[1]) vim.command(":syntax clear %s" % region) vim.command(":highlight %s guibg=#33ff33" % region) if start_row == end_row and start_col == end_col: end_col += 1 # vim_region = ":syntax region {region} start=/\%{start_col}v.\%{start_row}l/ end=/\%{end_col}v.\%{end_row}l/".\ # format(region=region, start_col=start_col, start_row=start_row, end_col=end_col, end_row=end_row) # msg.debug("highlight command: %s" % vim_region) # vim.command(vim_region) # matchadd({group}, {pattern}[, {priority}[, {id}]]) vim.command(":call matchdelete({user_id})".format(user_id=user_id)) matchadd = ":call matchadd('{region}', '\%{start_col}v.\%{start_row}l', 10000, {user_id})" \ .format(region=region, start_col=start_col, start_row=start_row, user_id=user_id) msg.debug("highlight command: %s" % matchadd) vim.command(matchadd)
def on_modified(self, view, agent, activated=False, *args): buf = is_view_loaded(view) if not buf: return text = get_text(view) if buf['encoding'] != 'utf8': return msg.warn( 'Floobits does not support patching binary files at this time') text = text.encode('utf-8') view_md5 = hashlib.md5(text).hexdigest() bid = view.buffer_id() buf['forced_patch'] = False if view_md5 == G.VIEW_TO_HASH.get(bid): self._highlights.add(bid) return G.VIEW_TO_HASH[view.buffer_id()] = view_md5 msg.debug('changed view ', buf['path'], ' buf id ', buf['id']) if not activated: self.disable_follow_mode(2000) agent.views_changed.append(('patch', view, buf))
def get_buf(self, vim_buf): """None- no sharing, False- should be but isn't """ if vim_buf.name is None: msg.debug('get:buf buffer has no filename') return None if not utils.is_shared(vim_buf.name): msg.debug('get_buf: %s is not shared' % vim_buf.name) return None buf = self.get_buf_by_path(vim_buf.name) if buf: return buf msg.debug('get_buf: no buf has path %s' % vim_buf.name) return False
def on_room_info(self, data): # Success! Reset counter self.workspace_info = data self.perms = data['perms'] if 'patch' not in data['perms']: msg.log('We don\'t have patch permission. Setting buffers to read-only') utils.mkdir(G.PROJECT_PATH) floo_json = { 'url': utils.to_workspace_url({ 'host': self.agent.host, 'owner': self.agent.owner, 'port': self.agent.port, 'workspace': self.agent.workspace, 'secure': self.agent.secure, }) } with open(os.path.join(G.PROJECT_PATH, '.floo'), 'w') as floo_fd: floo_fd.write(json.dumps(floo_json, indent=4, sort_keys=True)) for buf_id, buf in data['bufs'].iteritems(): buf_id = int(buf_id) # json keys must be strings buf_path = utils.get_full_path(buf['path']) new_dir = os.path.dirname(buf_path) utils.mkdir(new_dir) self.FLOO_BUFS[buf_id] = buf try: buf_fd = open(buf_path, 'r') buf_buf = buf_fd.read().decode('utf-8') md5 = hashlib.md5(buf_buf.encode('utf-8')).hexdigest() if md5 == buf['md5']: msg.debug('md5 sums match. not getting buffer') buf['buf'] = buf_buf else: raise Exception('different md5') except Exception: try: open(buf_path, "a").close() except Exception as e: msg.debug("couldn't touch file: %s becuase %s" % (buf_path, e)) self.agent.send_get_buf(buf_id) msg.debug(G.PROJECT_PATH) self.agent.on_auth()
def tick(self): if 'patch' not in G.PERMS: self.views_changed = [] elif not self.joined_workspace: msg.debug('Not connected. Discarding view change.') self.views_changed = [] else: reported = set() to_send = [] while self.views_changed: name, v, buf = self.views_changed.pop() if 'buf' not in buf: msg.debug('No data for buf ', buf['id'], ' ', buf['path'], ' yet. Skipping sending patch') continue view = View(v, buf) if view.is_loading(): msg.debug('View for buf ', buf['id'], ' is not ready. Ignoring change event.') continue if view.native_id in reported: continue reported.add((name, view.native_id)) if name == 'patch': patch = utils.FlooPatch(view.get_text(), buf) # Update the current copy of the buffer buf['buf'] = patch.current buf['md5'] = patch.md5_after self.send(patch.to_json()) continue if name == 'saved': to_send.append({'name': 'saved', 'id': buf['id']}) continue msg.warn('Discarding unknown event in views_changed:', name) for s in to_send: self.send(s) self._status_timeout += 1 if self._status_timeout > (2000 / G.TICK_TIME): self.update_status_msg()
def on_close(self, view, agent): msg.debug('Sublime closed view %s' % self.name(view))