[SVN] BUG: Output Text not inserted at caret when input is Scope
Alain O'Dea
alain.odea at gmail.com
Tue Feb 19 02:08:49 UTC 2008
On 18-Feb-08, at 5:30 PM, Allan Odgaard wrote:
> On 18 Feb 2008, at 02:23, Alain O'Dea wrote:
>
>> [...]
>> I can't get this to work. I am left with text appearing after the
>> end of the scope. For clarification the scope I am working with can
>> be many lines. How do I make TextMate insert the output at the
>> caret instead of at the end of the selection/scope?
>
> Are you using “selection or scope” as input and “insert as snippet”
> as output?
>
> You will sometimes get more input that what would be ideal (using a
> scope selector), but there is presently no way around that.
>
> The Objective-C bundle has completion commands that takes current
> scope as input, you may want to look at the se for examples. If you
> still cannot get it to work, please post what you are actually doing.
I am building a code completion system for Erlang in TextMate. The
process is quite involved, but it gets completions extremely quickly.
command_server (command_server.erl) receives a command name followed
by input lines over TCP. If "vars" is sent then command_server invokes
vars:exec/2 (vars.erl) which parses the input and returns the
variables in it. command_server then invokes tm_menu:get_selection/1
(tm_menu.erl) which builds a plist from the variables lis, invokes
tm_dialog, and returns the user's selection once made. command_server
then returns the result to the client over TCP. In theory invoking
Variable Completion (Command) should insert the selected variable at
the caret. Instead it inserts at the end of the scope.
Realizing that TextMate was going to insert at the end of the input I
decided to hack around it using Macros. To this end command_server,
just before responding to the to TCP client, invokes clipboard:copy/1
which uses pbcopy to add the result to the clipboard. As a result I
can invoke Variable Completion into Clipboard (Command) and then Edit-
>Paste to drop the result right at the caret. Unfortunately there
seems to be a race condition on the clipboard and this macro often
drops the previous clipboard contents. This occurs even if the local
clipboard option for the Macro is turned off. If I paste again
manually the correct value is pasted. Using the Paste (Command)
instead of Edit->Paste in the macro works correctly, but with a
noticeable lag between selecting the completion and the subsequent
insertion.
Listings:
=========
erl_vars.py:
#!/usr/bin/env python
import socket
import sys
import os
import time
module = os.getenv('TM_FILEPATH').split('/').pop().split('.').pop(0)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',2345))
s.send('vars\n')
for line in sys.stdin:
s.send(line.rstrip() + os.linesep)
s.send('eof' + os.linesep)
# rendezvous with Erlang server
print s.recv(4096)
Variable Completion (Command):
python erl_vars.py
-input: Selected Text or Scope
-output: Insert as Text
Variable Completion into Clipboard (Command):
python erl_vars.py
-input: Selected Text or Scope
-output: None
Paste (Command):
pbpaste
-input: None
-output: Insert as Text
Paste as Snippet (Command):
pbpaste
-input: None
-output: Insert as Snippet
command_server.erl:
-module(command_server).
-export([start/0]).
start() ->
{ok, Listen} = gen_tcp:listen(2345, [list, {packet, line},
{reuseaddr, true},
{active, true}]),
spawn(fun() -> par_connect(Listen) end).
par_connect(Listen) ->
{ok, Socket} = gen_tcp:accept(Listen),
io:format("Client session started~n"),
spawn(fun() -> par_connect(Listen) end),
loop(Socket).
loop(Socket) ->
receive
{tcp, Socket, ModuleRaw} ->
Module = list_to_atom(trim(ModuleRaw)),
Reply = Module:exec(tm_command, iter(Socket)),
clipboard:copy(Reply),
gen_tcp:send(Socket, list_to_binary(Reply)),
loop(Socket);
{tcp_closed, Socket} ->
io:format("Client session ended~n")
end.
iter(Socket) ->
fun() ->
receive
{tcp, Socket, "eof\n"} ->
eof;
{tcp, Socket, Line} ->
trim(Line);
{tcp_closed, Socket} ->
eof
end
end.
trim(String) ->
{ok, Trimmed, _} = regexp:sub(String, "[\r\n]", ""),
string:strip(Trimmed).
clipboard.erl:
-module(clipboard).
-export([copy/1]).
copy(Data) ->
Clipboard = open_port({spawn, "pbcopy"}, [stream]),
Clipboard ! {self(), {command, Data}},
Clipboard ! {self(), close},
receive
{Clipboard, closed} -> true
end.
tm_menu.erl:
-module(tm_menu).
-export([get_selection/1]).
-include_lib("xmerl/include/xmerl.hrl").
get_selection(MenuItems) ->
%% TODO: need better way to get path for tm_dialog
PlistXml = os:cmd(io_lib:format("/Applications/TextMate.app/
Contents/PlugIns/Dialog.tmplugin/Contents/Resources/tm_dialog -u -p
\"~s\"", [ascii_plist(MenuItems)])),
{PlistDoc, _Rest} = xmerl_scan:string(PlistXml),
[#xmlText{value=Selected}] = xmerl_xpath:string("/plist/dict/
dict[preceding-sibling::key[1]='selectedMenuItem']/string[preceding-
sibling::key[1]='title']/text()", PlistDoc),
Selected.
ascii_plist(MenuItems) ->
lists:flatten(
lists:reverse([");}"|
lists:foldl(
fun(Item, Completions) ->
[io_lib:format("{title = ~s;},", [Item])|
Completions]
end,
["{menuItems = ("],
MenuItems
)
])
).
vars.erl:
-module(vars).
-export([exec/2]).
exec(tm_command, Iter) ->
tm_menu:get_selection(list(Iter)).
list(Iter) ->
sets:to_list(
sets:from_list(
list(Iter, Iter()))).
list(_, eof) ->
[];
list(Iter, Line) ->
matches(Line, list(Iter, Iter())).
matches(Line, MatchesIn) ->
{match, MatchIndices} = regexp:matches(Line, "[A-Z][a-zA-
Z0-9_@]*"),
lists:foldl(
fun({Start, Length}, Matches) ->
[string:substr(Line, Start, Length)|Matches]
end,
MatchesIn, MatchIndices).
More information about the textmate-dev
mailing list