Hi folks,
I missed a command from BBEdit that let you comment and uncomment text magically for different languages with the same keystroke (Text->Un/Comment), so I wrote a version for TextMate. If there is enough interest, I will release commented source code and make it into a nice .tmbundle. It currently only supports perl, php, html, plist and tex. It is trivial to add support for other languages, which I'll do on request (or you can easily figure out yourself, I think... if you do, send me an e-mail so I can update mine!).
If the selection's first line is a comment; if so, it uncomments the rest of the selection. If not, it comments the whole selection, line by line. It tries to keep the selection's indentation.
It parses the filetype from the filename ending to figure out what are comments. If a language has more than one kind of delimiter pair, it will use the first pair in the list for commenting, and will search for all listed pairs for uncommenting. Notice this last feature improves on BBEdit's Text->Un/Comment functionality. The delimiters are stored in $a in the format "a,b,c"=>[$first, $last], where each of the file endings .a, .b and .c will use that delimiter pair. Roll your own!
This version is actually better than BBEdit's, in my opinion, because 1. it can uncomment many different comment formats for silly languages like PHP that support several; and 2. it places comment delimiters at the original level of indent (and not the first column like BB does); and 3. we can all customize it.
Textmate is powerful!
best wishes, Eric
--
To install it as a command:
Before: do nothing Command: perl -e '$a={"pl,pm"=>["#",""],"plist,c"=>["/*","*/"],"html,htm"=>["<!--","-->"],"tex,ltx"=>["%",""],"php"=>["#","","/*","*/","<!--","-->","//",""]};while(($k,$v)=each(%$a)){foreach(split(/\s*,\s*/,$k)){$c{"$_"}=$v;}}$_=shift@ARGV;($t)=/.(.*?)$/;($s,$f,@etc)=@{$c{$t}};$b=0;while(<STDIN>){push@in,$_;($in)=/^([ \t]*)/;$inl=0;foreach$j(1..(length($in))){$ch=substr($in,$j-1,1);if($ch eq" "){$inl++;}else{unless($inl%4){$inl+=4;}else{$inl+=$inl%4;}}}unless($i){$ind=$in;$indl=$inl;$i++;}else{if($inl<$indl||$in eq""){$ind=$in;$indl=$inl;}}}$i=0;foreach(@in){if(chomp){$n="\n";}unless(/\S/){$o.=$_."\n";next;}unless($b){$b=1;foreach$d(@{$c{$t}}){$i=1-$i;$d=quotemeta($d);if($i){if(/^\s*$d/){$y=1;$s=$d;}}elsif($y){$f=$d;last;}}}if($y){s/^(\s*)$s(\ )?/$1/;s/(\ )?$f(\s*)$/$1/;$o.=$_.$n;}else{s/^$ind//;$o.=$ind.$s." $_ ".$f.$n;}}print$o;' $TM_FILEPATH
STDIN: Selected STDOUT: Replace Selected
On Tue, 21 Dec 2004 23:46:52 +0800, Eric Hsu wrote:
Textmate is powerful!
And you are a genius. Thank-you, Eric; FWIW, I think this is fantastic.
Cheers, Andrew.
Very nicely done (even if it is the sort of perl that sends some people fleeing in horror from the language! ;-) and it definitely does work better than what's in BBEdit.
According to Eric Hsu:
This version is actually better than BBEdit's, in my opinion, because
- it can uncomment many different comment formats for silly
languages like PHP that support several; and 2. it places comment delimiters at the original level of indent (and not the first column like BB does); and 3. we can all customize it.
After adding "rb" to the extention that uses # as a comment char, I see it adds an extra newline at the end of each line...
----- require "uuid" require "config" require "auth/secret" -----
becomes
----- require "uuid" # require "config"
require "auth/secret" ----- Cheers,
echo ('brilliant!'); // :)
really fantastic, thanks Eric!
On 21 Dec 2004, at 12:46, Eric Hsu wrote:
This version is actually better than BBEdit's, in my opinion, because
- it can uncomment many different comment formats for silly languages
like PHP that support several; and 2. it places comment delimiters at the original level of indent (and not the first column like BB does); and 3. we can all customize it.
Eric Hsu wrote:
I missed a command from BBEdit that let you comment and uncomment text magically for different languages with the same keystroke (Text->Un/Comment), so I wrote a version for TextMate.
Fantastic. Thanks for this, Eric.
A useful language to add would be CSS, using C-style multiline comments:
/* This is a comment */
/* so is this */
The file extension to match on, obviously, would be .css.
Thanks again.
drew.
Thanks Eric,
Works as advertised! I'd like to see support for Java ("java"=>["//","","/*","*/"]) and XML (same as html). This command would be even better if instead of looking at the filename extension, it would use the language chosen from menu "View -> Syntax Highlight as", because then it could work with unsaved files. I'm not sure wether that's currently possible, though.
Cheers, -Ralph.
On 21.12.2004, at 16:46, Eric Hsu wrote:
Hi folks,
I missed a command from BBEdit that let you comment and uncomment text magically for different languages with the same keystroke (Text->Un/Comment), so I wrote a version for TextMate. If there is enough interest, I will release commented source code and make it into a nice .tmbundle. It currently only supports perl, php, html, plist and tex. It is trivial to add support for other languages, which I'll do on request (or you can easily figure out yourself, I think... if you do, send me an e-mail so I can update mine!).
If the selection's first line is a comment; if so, it uncomments the rest of the selection. If not, it comments the whole selection, line by line. It tries to keep the selection's indentation.
It parses the filetype from the filename ending to figure out what are comments. If a language has more than one kind of delimiter pair, it will use the first pair in the list for commenting, and will search for all listed pairs for uncommenting. Notice this last feature improves on BBEdit's Text->Un/Comment functionality. The delimiters are stored in $a in the format "a,b,c"=>[$first, $last], where each of the file endings .a, .b and .c will use that delimiter pair. Roll your own!
This version is actually better than BBEdit's, in my opinion, because
- it can uncomment many different comment formats for silly languages
like PHP that support several; and 2. it places comment delimiters at the original level of indent (and not the first column like BB does); and 3. we can all customize it.
Textmate is powerful!
best wishes, Eric
--
To install it as a command:
Before: do nothing Command: perl -e '$a={"pl,pm"=>["#",""],"plist,c"=>["/*","*/"],"html,htm"=>["<!--","--
"],"tex,ltx"=>["%",""],"php"=>["#","","/*","*/","<!--","-->","/
/",""]};while(($k,$v)=each(%$a)){foreach(split(/\s*,\s*/ ,$k)){$c{"$_"}=$v;}}$_=shift@ARGV;($t)=/.(.*?)$/; ($s,$f,@etc)=@{$c{$t}};$b=0;while(<STDIN>){push@in,$_;($in)=/^([ \t]*)/;$inl=0;foreach$j(1..(length($in))){$ch=substr($in,$j-1,1); if($ch eq" "){$inl++;}else{unless($inl%4){$inl+=4;}else{$inl+=$inl%4;}}}unless($i) {$ind=$in;$indl=$inl;$i++;}else{if($inl<$indl||$in eq""){$ind=$in;$indl=$inl;}}}$i=0; foreach(@in){if(chomp){$n="\n";}unless(/\S/){$o.=$_."\n"; next;}unless($b){$b=1;foreach$d(@{$c{$t}}){$i=1-$i;$d=quotemeta($d); if($i){if(/^\s*$d/){$y=1;$s=$d;}}elsif($y){$f=$d;last;}}}if($y){s/ ^(\s*)$s(\ )?/$1/;s/(\ )?$f(\s*)$/$1/;$o.=$_.$n;}else{s/^$ind//;$o.=$ind.$s." $_ ".$f.$n;}}print$o;' $TM_FILEPATH
STDIN: Selected STDOUT: Replace Selected ists.macromates.com/mailman/listinfo/textmate
On Dec 21, 2004, at 18:26, Ralph Pöllath wrote:
[...] better if instead of looking at the filename extension, it would use the language chosen from menu "View -> Syntax Highlight as", because then it could work with unsaved files. I'm not sure wether that's currently possible, though.
It is not -- but (motivated by this) I'll add TM_FILETYPE in next beta which contain the name of the current syntax (unless there are other suggestions).
On Dec 21, 2004, at 16:46, Eric Hsu wrote:
I missed a command from BBEdit that let you comment and uncomment text magically for different languages with the same keystroke (Text->Un/Comment), so I wrote a version for TextMate. [...]
Nice! :)
I changed the extension-grabbing code to this: “($t)=/.([^.]*)$/” to handle paths with multiple dots and I quoted TM_FILEPATH (for paths with spaces).
I also added: "cc,mm"=>["//","","/*","*/"] and m+h to the plist,c types.
So it's now: perl -e '$a={"cc,mm"=>["//","","/*","*/"],"pl,pm"=>["#",""],"plist,c,m,h"=>["/ *","*/"],"html,htm"=>["<!--","--
"],"tex,ltx"=>["%",""],"php"=>["#","","/*","*/","<!--","-->","/
/",""]};while(($k,$v)=each(%$a)){foreach(split(/\s*,\s*/ ,$k)){$c{"$_"}=$v;}}$_=shift@ARGV;($t)=/.([^.]*)$/; ($s,$f,@etc)=@{$c{$t}};$b=0;while(<STDIN>){push@in,$_;($in)=/^([ \t]*)/;$inl=0;foreach$j(1..(length($in))){$ch=substr($in,$j-1,1);if($ch eq" "){$inl++;}else{unless($inl%4){$inl+=4;}else{$inl+=$inl%4;}}}unless($i){ $ind=$in;$indl=$inl;$i++;}else{if($inl<$indl||$in eq""){$ind=$in;$indl=$inl;}}}$i=0; foreach(@in){if(chomp){$n="\n";}unless(/\S/){$o.=$_."\n"; next;}unless($b){$b=1;foreach$d(@{$c{$t}}){$i=1-$i;$d=quotemeta($d); if($i){if(/^\s*$d/){$y=1;$s=$d;}}elsif($y){$f=$d;last;}}}if($y){s/ ^(\s*)$s(\ )?/$1/;s/(\ )?$f(\s*)$/$1/;$o.=$_.$n;}else{s/^$ind//;$o.=$ind.$s." $_ ".$f.$n;}}print$o;' "$TM_FILEPATH"
If there is enough interest, I will release commented source code and make it into a nice .tmbundle.
Sounds like when you do, I should add that bundle to what's included by default with TM (if you don't mind)! :)
In article BB9A086A-5375-11D9-8F1A-000D93589AF6@macromates.com, Allan Odgaard allan@macromates.com wrote:
On Dec 21, 2004, at 16:46, Eric Hsu wrote:
I missed a command from BBEdit that let you comment and uncomment text magically for different languages with the same keystroke (Text->Un/Comment), so I wrote a version for TextMate. [...]
Nice! :)
I changed the extension-grabbing code to this: ³($t)=/.([^.]*)$/² to handle paths with multiple dots and I quoted TM FILEPATH (for paths with spaces).
I also added: "cc,mm"=>["//","","/*","*/"] and m+h to the plist,c types.
So it's now: perl -e '$a={"cc,mm"=>["//","","/*","*/"],"pl,pm"=>["#",""],"plist,c,m,h"=>["/ *","*/"],"html,htm"=>["<!--","--
"],"tex,ltx"=>["%",""],"php"=>["#","","/*","*/","<!--","-->","/
/",""]};while(($k,$v)=each(%$a)){foreach(split(/\s*,\s*/ ,$k)){$c{"$ "}=$v;}}$ =shift@ARGV;($t)=/.([^.]*)$/; ($s,$f,@etc)=@{$c{$t}};$b=0;while(<STDIN>){push@in,$ ;($in)=/^([ \t]*)/;$inl=0;foreach$j(1..(length($in))){$ch=substr($in,$j-1,1);if($ch eq" "){$inl++;}else{unless($inl%4){$inl+=4;}else{$inl+=$inl%4;}}}unless($i){ $ind=$in;$indl=$inl;$i++;}else{if($inl<$indl||$in eq""){$ind=$in;$indl=$inl;}}}$i=0; foreach(@in){if(chomp){$n="\n";}unless(/\S/){$o.=$ ."\n"; next;}unless($b){$b=1;foreach$d(@{$c{$t}}){$i=1-$i;$d=quotemeta($d); if($i){if(/^\s*$d/){$y=1;$s=$d;}}elsif($y){$f=$d;last;}}}if($y){s/ ^(\s*)$s(\ )?/$1/;s/(\ )?$f(\s*)$/$1/;$o.=$ .$n;}else{s/^$ind//;$o.=$ind.$s." $ ".$f.$n;}}print$o;' "$TM FILEPATH"
If there is enough interest, I will release commented source code and make it into a nice .tmbundle.
Sounds like when you do, I should add that bundle to what's included by default with TM (if you don't mind)! :)
It might be worth discussing the feature before making it part of TextMate.
I'm really pleased to see this script, but would like to suggest an alternative implementation: - Separate commands for comment/uncomment, rather than trying to guess which is wanted. The philosophy of "explicit is better than implicit". This has significant advantages. - No ambiguity about what should be done. Many people temporarily comment out blocks of code AND may include the leading comments. Having separate commands makes it painless and reliable. Guessing leads to the wrong thing happening. Lack of this is one of the reasons I dislike BBEdit. - No need for the code to be fancy and try to guess!
Typically one would use a certain command keystroke (e.g. option-;) to comment, and use the same keystroke plus the option key to get uncomment. Thus nothing really extra to memorize.
I will also argue for putting the comment character in the first column (or offering an option for this). I use comment/uncomment solely to temporarily comment out blocks of code, and having the comment char in the 1st column makes such code easy to spot. Indenting the comment char makes it look like any other comment, hiding the block.
Just my two bits. What do others think?
-- Russell
On Dec 21, 2004, at 7:46 AM, Eric Hsu wrote:
This version is actually better than BBEdit's, in my opinion, because
- it can uncomment many different comment formats for silly languages
like PHP that support several; and 2. it places comment delimiters at the original level of indent (and not the first column like BB does); and 3. we can all customize it.
Here's what I use (requires Ruby 1.8). (I typically want to preserve the existing inline comments, so I don't personally find those first two properties desirable.)
file_extension = File.extname(ARGV[0]) file_extension = file_extension[1..file_extension.size]
# default to shell-style comments comment_strings = ['#','']
StringMap = { ['c','cp','cpp','h','m','mm','p','pas', 'java','js','htc','c++','h++'] => ['//',''], # C family and some variants of Pascal ['f','f77'] => ['C ',''], # Fortran ['inf','f90'] => ['!',''], # Inform, modern Fortran ['script','adb','ads','sql','ddl', 'dml'] => ['--',''], # AppleScript, Ada, SQL ['html','xml','plist','php'] => ['<!--','-->'], # XML, HTML ['mli','ml', 'mll','mly'] => ['(*','*)'], # OCaml uses Pascal comments ['bib','ltx','tex','ps','eps'] => ['%',''] # Latex etc }
key = StringMap.keys.detect {|k| k.include?(file_extension)}
(comment_strings = StringMap[key]) if not key.nil?
comment_expr = /^\s*#{comment_strings[0]}(.*)#{comment_strings[1]}/
choose_mode = true uncomment = false
def process_line(instring, uncomment, comment_strings, comment_expr)
if uncomment then # uncomment match = comment_expr.match(instring) if match != nil then puts match[1] else puts instring end else # comment puts comment_strings[0] + instring.chomp + comment_strings[1] end end
# if the first line is commented, assume the user wants to uncomment # and vice versa first_line = $stdin.gets uncomment = comment_expr.match(first_line) uncomment = false if uncomment.nil? process_line(first_line, uncomment, comment_strings, comment_expr)
$stdin.each_line { |instring| process_line(instring, uncomment, comment_strings, comment_expr) }
Chris, For those of us new to Ruby, do we need to wrap your script in :
ruby -e '<your script>'?
Thanks On Dec 21, 2004, at 11:02 AM, Chris Thomas wrote:
On Dec 21, 2004, at 7:46 AM, Eric Hsu wrote:
This version is actually better than BBEdit's, in my opinion, because
- it can uncomment many different comment formats for silly
languages like PHP that support several; and 2. it places comment delimiters at the original level of indent (and not the first column like BB does); and 3. we can all customize it.
Here's what I use (requires Ruby 1.8). (I typically want to preserve the existing inline comments, so I don't personally find those first two properties desirable.)
file_extension = File.extname(ARGV[0]) file_extension = file_extension[1..file_extension.size]
# default to shell-style comments comment_strings = ['#','']
StringMap = { ['c','cp','cpp','h','m','mm','p','pas', 'java','js','htc','c++','h++'] => ['//',''], # C family and some variants of Pascal ['f','f77'] => ['C ',''], # Fortran ['inf','f90'] => ['!',''], # Inform, modern Fortran ['script','adb','ads','sql','ddl', 'dml'] => ['--',''], # AppleScript, Ada, SQL ['html','xml','plist','php'] => ['<!--','-->'], # XML, HTML ['mli','ml', 'mll','mly'] => ['(*','*)'], # OCaml uses Pascal comments ['bib','ltx','tex','ps','eps'] => ['%',''] # Latex etc }
key = StringMap.keys.detect {|k| k.include?(file_extension)}
(comment_strings = StringMap[key]) if not key.nil?
comment_expr = /^\s*#{comment_strings[0]}(.*)#{comment_strings[1]}/
choose_mode = true uncomment = false
def process_line(instring, uncomment, comment_strings, comment_expr)
if uncomment then # uncomment match = comment_expr.match(instring) if match != nil then puts match[1] else puts instring end else # comment puts comment_strings[0] + instring.chomp + comment_strings[1] end end
# if the first line is commented, assume the user wants to uncomment # and vice versa first_line = $stdin.gets uncomment = comment_expr.match(first_line) uncomment = false if uncomment.nil? process_line(first_line, uncomment, comment_strings, comment_expr)
$stdin.each_line { |instring| process_line(instring, uncomment, comment_strings, comment_expr) }
textmate mailing list textmate@lists.macromates.com http://lists.macromates.com/mailman/listinfo/textmate
Long commands like this is what makes life easier with modes like e.g. emacs, where it would just be one command for each mode bound to the same place. The same can be done for compile/make/latex/whatever which is, in emacs, often bound to C-c C-c or similar :-).
On Dec 21, 2004, at 11:43 AM, Sune Foldager wrote:
Long commands like this is what makes life easier with modes like e.g. emacs, where it would just be one command for each mode bound to the same place. The same can be done for compile/make/latex/whatever which is, in emacs, often bound to C-c C-c or similar :-).
Yes. Or, in a mode-based editor, you might have 'switched' variables that could be bound to per-mode strings, so that the logic for a command wouldn't necessarily need to be duplicated for each mode. (You'd also want the ability to override the command on a per-mode basis.)
It's not clear to me that a sophisticated mode implementation could be based on raw shell script, though. Hard to do polymorphism in shell script.
Chris
On Dec 21, 2004, at 22:51, Chris Thomas wrote:
It's not clear to me that a sophisticated mode implementation could be based on raw shell script, though. Hard to do polymorphism in shell script.
Version 1.1b1 will approach modes with the syntax choice (i.e. mode) dependent lookup of snippets/commands/macros. Everything is still accessible, but when using a snippet trigger or snippet/macro/command key equivalent, it will favor any snippet/macro/command found in the same bundle as the current syntax file.
The snippets/commands/macros menus then only show the contents of the 'active' bundle and have sub menus for the other bundles:
I personally do favor this over real modes since I do occasionally need to use HTML snippets in non-HTML documents or some of my C++ macros in a non-C++ context.
Ruby supports -e in the same manner as Perl. However, it's awkward with a multiline script, so most scripts I place in external files. This one I place in a file in my home directory called comment.rb, the TextMate command being thus:
ruby -s "~/comment.rb" "$TM_FILEPATH"
Chris
On Dec 21, 2004, at 11:38 AM, Lang Riley wrote:
Chris, For those of us new to Ruby, do we need to wrap your script in :
ruby -e '<your script>'?
Thanks On Dec 21, 2004, at 11:02 AM, Chris Thomas wrote:
On Dec 21, 2004, at 7:46 AM, Eric Hsu wrote:
This version is actually better than BBEdit's, in my opinion, because
- it can uncomment many different comment formats for silly
languages like PHP that support several; and 2. it places comment delimiters at the original level of indent (and not the first column like BB does); and 3. we can all customize it.
Here's what I use (requires Ruby 1.8). (I typically want to preserve the existing inline comments, so I don't personally find those first two properties desirable.)