Sublime Workflow for replacing quotes - replace

I use text editor Sublime Text 3 to edit code, and very often I'll have a string literal wrapped in double quotes, that I want to change to single quotes, or vise versa. Right now I scroll to each quotation mark, and replace it with the one I want. Is there a faster workflow for this? Say, highlighting the word or a hotkey or something? I would find it super useful.

If you have a large number of such strings in a file and you want to convert all of them at once, you could use a regex find/replace operation to find and replace them all. You would use Find > Replace... or Find > Find in files... to search for a matching regex that captures the text in the quotes.
For example you could use \"([^"\n]*)\" as a search term and '\1' as the replacement text to swap all double quoted strings for single quotes.
You can't bind something like that to a key directly because Find/Replace can't be used in a Macro, but you could use the RegReplace package to do this if you want to go that route.
You can potentially speed up the workflow that you're currently using by taking advantage of multiple cursors, if you're not already doing that.
You could for example select the first quote, then press Ctrl+D or Option+D to select the other one. Now that you have two cursors, press Backspace to delete both quotes and press the new quote character to insert the new ones.
This can't be macro-ized and bound to a key because the find_under_expand command can't be used in a macro, though.
For a full key press solution, as far as I'm aware you would need a plugin of some sort to do this for you. One such example appears to be ChangeQuotes, although I've never personally used it.
It's also possible to write your own small plugin such as the following:
import sublime
import sublime_plugin
class SwapQuotesCommand(sublime_plugin.TextCommand):
pairs = ["'", '"']
def run(self, edit):
self.view.run_command("expand_selection", {"to": "scope"})
for sel in self.view.sel():
self.toggle(edit, sel)
def toggle(self, edit, region):
begin = self.view.substr(region.begin())
end = self.view.substr(region.end() - 1)
if begin == end and begin in self.pairs:
index = self.pairs.index(begin) + 1
new = self.pairs[index % len(self.pairs)]
for point in (region.begin(), region.end() - 1):
self.view.replace(edit, sublime.Region(point, point+1), new)
This expands the selection in all of the cursors out by the current scope, and then if both ends of the selection are a matching quote, the quote in use is swapped.
In use, you would use a key binding such as the following, which includes a context to make the key only trigger while the cursor is inside of a string so that it doesn't mess up your selection in cases where it definitely won't work.
{
"keys": ["ctrl+shift+'"], "command": "swap_quotes",
"context": [
{ "key": "selector", "operator": "equal", "operand": "string.quoted", "match_all": true }
]
},

Related

eval certain regex from file to replace chars in string

I'm new to ruby so please excuse my ignorance :)
I just learned about eval and I read about its dark sides.
what I've read so far:
When is eval in Ruby justified?
Is 'eval' supposed to be nasty?
Ruby Eval and the Execution of Ruby Code
so what I have to do is to read a file in which there are some text such as /e/ 3 which will replace each e with a 3 after evaluation.
so here what i did so far:(working but..)
def evaluate_lines
result="elt"
IO.foreach("test.txt") do |reg|
reg=reg.chomp.delete(' ')
puts reg
result=result.gsub(eval(reg[0..2]),"#{reg[3..reg.length]}" )
p result
end
end
contents of the test.txt file
/e/ 3
/l/ 1
/t/ 7
/$/ !
/$/ !!
this only works because I know the length of the lines in the file.
so assuming my file has the following /a-z/ 3 my program would be not able to do what is expected from it.
Note
I tried using Regexp.new reg and this resulted with the following /\/e\/3/ which isn't very helpful in this case.
simple example to the `Regexp
str="/e/3"
result="elt"
result=result.gsub(Regexp.new str)
p result #outputs: #<Enumerator: "elt":gsub(/\/e\/3/)>
i already tried stripping off the slashes but even though this wont deliver the desired result thus the gsub() takes two parameters, such as this gsub(/e/, "3").
for the usage of the Regexp, I have already read Convert a string to regular expression ruby
While you can write something to parse that file, it rapidly gets complicated because you have to parse regular expressions. Consider /\/foo\\/.
There are a number of incomplete solutions. You can split on whitespace, but this will fail on /foo bar/.
re, replace = line.split(/\s+/, 2)
You can use a regex. Here's a first stab.
match = "/3/ 4".match(%r{^/(.*)/\s+(.+)})
This fails on escaped /, we need something more complex.
match = '/3\// 4'.match(%r{\A / ((?:[^/]|\\/)*) / \s+ (.+)}x)
I'm going to guess it was not your teacher's intent to have you parsing regexes. For the purposes of the assignment, splitting on whitespace is probably fine. You should clarify with your teacher.
This is a poor data format. It is non-standard, difficult to parse, and has limitations on the replacement. Even a tab-delimited file would be better.
There's little reason to use a non-standard format these days. The simplest thing is to use a standard data format for the file. YAML or JSON are the most obvious choices. For such simple data, I'd suggest JSON.
[
{ "re": "e", "replace": "3" },
{ "re": "l", "replace": "1" }
]
Parsing the file is trivial, use the built-in JSON library.
require 'json'
specs = JSON.load("test.json")
And then you can use them as a list of hashes.
specs.each do |spec|
# No eval necessary.
re = Regexp.new(spec["re"])
# `gsub!` replaces in place
result.gsub!(re, spec["replace"])
end
The data file is extensible. For example, if later you want to add regex options.
[
{ "re": "e", "replace": "3" },
{ "re": "l", "replace": "1", "options": ['IGNORECASE'] }
]
While the teacher may have specified a poor format, pushing back on bad requirements is good practice for being a developer.
Here's a really simple example that uses vi notation like s/.../.../ and s/.../.../g:
def rsub(text, spec)
_, mode, repl, with, flags = spec.match(%r[\A(.)\/((?:[^/]|\\/)*)/((?:[^/]|\\/)*)/(\w*)\z]).to_a
case (mode)
when 's'
if (flags.include?('g'))
text.gsub(Regexp.new(repl), with)
else
text.sub(Regexp.new(repl), with)
end
end
end
Note the matcher looks for non-slash characters ([^/]) or a literal-slash combination (\\/) and splits out the two parts accordingly.
Where you can get results like this:
rsub('sandwich', 's/and/or/')
# => "sorwich"
rsub('and/or', 's/\//,/')
# => "and,or"
rsub('stack overflow', 's/o/O/')
# => "stack Overflow"
rsub('stack overflow', 's/o/O/g')
# => "stack OverflOw"
The principle here is you can use a very simple regular expression to parse out your input regular expression and feed that cleaned up data into Regexp.new. There is absolutely no need for eval here, and if anything that severely limits what you can do.
With a little work you could alter that regular expression to parse what's in your existing file and make it do what you want.

Regex match text followed by curly brackets

I have a text like this:
"entity"
{
"id" "5040044"
"classname" "weapon_defibrillator_spawn"
"angles" "0 0 0"
"body" "0"
"disableshadows" "0"
"skin" "0"
"solid" "6"
"spawnflags" "3"
"origin" "449.47 5797.25 2856"
editor
{
"color" "0 0 200"
"visgroupshown" "1"
"visgroupautoshown" "1"
"logicalpos" "[-13268 14500]"
}
}
What would regex expression be to select only that part in Notepad++:
editor
{
"color" "0 0 200"
"visgroupshown" "1"
"visgroupautoshown" "1"
"logicalpos" "[-13268 14500]"
}
First word is always "editor", but the number of lines and content in curly brackets may vary.
editor\s*{\s*(?:\"[a-z]*\"\s*\".*\"\s*)*\}
Demo
Also tested it in Notepad++ it works fine
The simplest way to find everything between curly brackets would be \{[^{}]*\} (example 1).
You can prepend editor\s* on it so it limits the search to only that specific entry: editor\s*\{[^{}]*\} (example 2).
However... if any of the keys or value strings within editor {...} contain a { or }, you're going to have edge cases.
You'll need to find double-quoted values and essentially ignore them. This example shows how you would stop before the first double quote within the group, and this example shows how to match up through the first key-value pair.
You essentially want to repeatedly match those key-value pairs until no more remain.
If your keys or values can contain \" within them, such as "help" "this is \"quoted\" text", you need to look for that \ character as well.
If there are nested groups within this group, you'll need to recursively handle those. Most regex (Notepad++ included) don't handle recursion, though, so to get around this, you copy-paste what you have so far inside of the code if it happens to come across more nested { and }. This does not handle more than one level of nesting, though.
TL;DR
For Notepad++, this is a single line regex you could use.

Delete extra spaces in list items - Sublime Text 2

Sometimes I need to put tags at the end of a list item using the shortcut: [Ctr] + [Shift] + [L] + [End] to edit multiple list items using multiple cursors. This doesn't always work so well when there are extra spaces at the end of a list item, making tha actual end of the item a few spaces longer than the text of the item.
Is there a shortcut I can use along with [Ctr] + [Shift] + [L] to delete those extra spaces and essentially make the "end" where the text ends?
Please ask questions if you need clarification on my question and I will try to best make it most clear.
I haven't found shortcuts for this. So I wrote one. It will trim the trailing white spaces of the current line. For example, after you enter ctrl+shift+l, you find that there are extra white spaces, enter ctrl+f and then ctrl+t to delete them. White spaces in other line won't be affected.
Key mapping:
{ "keys": ["ctrl+f", "ctrl+t"], "command": "delete_trailing_white_space" }
delete_trailing_white_space.py (put it Sublime Text 2\Packages\User)
import sublime
import sublime_plugin
class DeleteTrailingWhiteSpaceCommand(sublime_plugin.TextCommand):
def run(self, edit):
for region in self.view.sel():
line = self.view.line(region)
line_content = self.view.substr(line)
trimed_line = line_content.rstrip()
self.view.replace(edit, line, trimed_line)
If you don't want to delete the spaces, you can rebind the end key to a plugin that modifies the behavior. I wrote this when I was playing around with some virtual white space stuff, so it's only minimally tested (link). Save it to Packages/User. The name of the file doesn't matter, just make sure it has the .py extension. Then, add the following as a custom key binding.
{
"keys": ["end"], "command": "custom_move_to_end_line"
}

Railo, remove some double quotes from SerializeJSON result

Let's say I have:
<cfscript>
arrButtons = [
{
"name" = "Add",
"bclass" = "add",
"onpress" = "addItem"
},
{
"name" = "Edit",
"bclass" = "edit",
"onpress" = "editItem"
},
{
"name" = "Delete",
"bclass" = "delete",
"onpress" = "deleteItem"
}
];
jsButtons = SerializeJSON(arrButtons);
// result :
// [{"onpress":"addItem","name":"Add","bclass":"add"},{"onpress":"editItem","name":"Edit","bclass":"edit"},{"onpress":"deleteItem","name":"Delete","bclass":"delete"}]
</cfscript>
For every onpress item, I need to remove the double quotes from its value to match the JS library requirement (onpress value must a callback function).
How do I remove the double quotes using a regular expression?
The final result must be:
[{"onpress":addItem,"name":"Add","bclass":"add"},{"onpress":editItem,"name":"Edit","bclass":"edit"},{"onpress":deleteItem,"name":"Delete","bclass":"delete"}]
No double quotes surrounding addItem, editItem, and deleteItem.
Edit 2012-07-13
Why I need this? I created a CFML function that the result is a collection of JS that will be used in many files. jsButton object will be used as one part of the options available in the JS library. One of that function's arguments is an array of struct (the default is arrButtons), and the supplied arguments value can merge with the default value.
Since we can't (in CFML) write onpress value without double quotes, so I have to add double quotes to that value, and convert the (CFML) array of struct to JSON (which is just a string) and remove the double quotes before place it in the JS library option.
with Railo, we can declare the struct as a linked struct to make sure we have same ordered key for loop or conversion (from above example onpress always the latest key in the struct). with this linked struct and same key order, we can remove the double quotes with simple Replace function, but of course we can't guarantee every programmer who use the CFML function doesn't forget to use linked struct and key order same as example above
I'm not sure this is actually necessary - depending on how/where you're dealing with the JS callbacks, it might be possible to use the string function names to reference the function without needing to remove the quotes (i.e. object[button.onpress]).
However, since you asked, here is a regex solution:
jsButtons = jsButtons.replaceAll('(?<="onpress":)"([^"]+)"','$1');
The regex there is made up of two parts:
(?<="onpress":) -- lookbehind to ensure we are dealing with the text "onpress":
"([^"]+)" -- match the quotes and capture their contents.
The $1 on the replacement side is to replace the matched text (i.e. the entire quoted value) with the first capture group (i.e. the contents of the quotes).
If case-sensitivity of "onpress" might be an issue, you can prefix the regex with (?i) to ignore case.
If there will be multiple different events (not just "onpress") you can update the relevant part of the expression above to be (?<="on(?:press|hover|squeek)":) etc.
Note: All the above relies on the format output from serializeJson not changing - if it's possible that there might be comments, whitespace, single quotes, or anything else in future then a longer expression would be needed to cater for those - which is part of why you should investigate if you even need regex to solve this problem in the first place.
What you're wanting to output is not JSON, so using SerializeJSON is a kludge.
Is there any reason you are putting it into a ColdFusion Array first, instead of writing the Javascript directly?
JSON is purely meant to be a data description language. Per
http://www.json.org, it is a "lightweight data-interchange format." -
not a programming language.
Per http://en.wikipedia.org/wiki/JSON, the "basic types" supported
are:
Number (integer, real, or floating point)
String (double-quoted Unicode with backslash escaping)
Boolean (true and false)
Array (an ordered sequence of values, comma-separated and enclosed in square brackets)
Object (collection of key:value pairs, comma-separated and enclosed in curly braces)
null
--Source
I guess in this case you can simply use serialize(). That should do the trick...
Gert

Notepad++ RegeEx group capture syntax

I have a list of label names in a text file I'd like to manipulate using Find and Replace in Notepad++, they are listed as follows:
MyLabel_01
MyLabel_02
MyLabel_03
MyLabel_04
MyLabel_05
MyLabel_06
I want to rename them in Notepad++ to the following:
Label_A_One
Label_A_Two
Label_A_Three
Label_B_One
Label_B_Two
Label_B_Three
The Regex I'm using in the Notepad++'s replace dialog to capture the label name is the following:
((MyLabel_0)((1)|(2)|(3)|(4)|(5)|(6)))
I want to replace each capture group as follows:
\1 = Label_
\2 = A_One
\3 = A_Two
\4 = A_Three
\5 = B_One
\6 = B_Two
\7 = B_Three
My problem is that Notepad++ doesn't register the syntax of the regex above. When I hit Count in the Replace Dialog, it returns with 0 occurrences. Not sure what's misesing in the syntax. And yes I made sure the Regular Expression radio button is selected. Help is appreciated.
UPDATE:
Tried escaping the parenthesis, still didn't work:
\(\(MyLabel_0\)\((1\)|\(2\)|\(3\)|\(4\)|\(5\)|\(6\)\)\)
Ed's response has shown a working pattern since alternation isn't supported in Notepad++, however the rest of your problem can't be handled by regex alone. What you're trying to do isn't possible with a regex find/replace approach. Your desired result involves logical conditions which can't be expressed in regex. All you can do with the replace method is re-arrange items and refer to the captured items, but you can't tell it to use "A" for values 1-3, and "B" for 4-6. Furthermore, you can't assign placeholders like that. They are really capture groups that you are backreferencing.
To reach the results you've shown you would need to write a small program that would allow you to check the captured values and perform the appropriate replacements.
EDIT: here's an example of how to achieve this in C#
var numToWordMap = new Dictionary<int, string>();
numToWordMap[1] = "A_One";
numToWordMap[2] = "A_Two";
numToWordMap[3] = "A_Three";
numToWordMap[4] = "B_One";
numToWordMap[5] = "B_Two";
numToWordMap[6] = "B_Three";
string pattern = #"\bMyLabel_(\d+)\b";
string filePath = #"C:\temp.txt";
string[] contents = File.ReadAllLines(filePath);
for (int i = 0; i < contents.Length; i++)
{
contents[i] = Regex.Replace(contents[i], pattern,
m =>
{
int num = int.Parse(m.Groups[1].Value);
if (numToWordMap.ContainsKey(num))
{
return "Label_" + numToWordMap[num];
}
// key not found, use original value
return m.Value;
});
}
File.WriteAllLines(filePath, contents);
You should be able to use this easily. Perhaps you can download LINQPad or Visual C# Express to do so.
If your files are too large this might be an inefficient approach, in which case you could use a StreamReader and StreamWriter to read from the original file and write it to another, respectively.
Also be aware that my sample code writes back to the original file. For testing purposes you can change that path to another file so it isn't overwritten.
Bar bar bar - Notepad++ thinks you're a barbarian.
(obsolete - see update below.) No vertical bars in Notepad++ regex - sorry. I forget every few months, too!
Use [123456] instead.
Update: Sorry, I didn't read carefully enough; on top of the barhopping problem, #Ahmad's spot-on - you can't do a mapping replacement like that.
Update: Version 6 of Notepad++ changed the regular expression engine to a Perl-compatible one, which supports "|". AFAICT, if you have a version 5., auto-update won't update to 6. - you have to explicitly download it.
A regular expression search and replace for
MyLabel_((01)|(02)|(03)|(04)|(05)|(06))
with
Label_(?2A_One)(?3A_Two)(?4A_Three)(?5B_One)(?6B_Two)(?7B_Three)
works on Notepad 6.3.2
The outermost pair of brackets is for grouping, they limit the scope of the first alternation; not sure whether they could be omitted but including them makes the scope clear. The pattern searches for a fixed string followed by one of the two-digit pairs. (The leading zero could be factored out and placed in the fixed string.) Each digit pair is wrapped in round brackets so it is captured.
In the replacement expression, the clause (?4A_Three) says that if capture group 4 matched something then insert the text A_Three, otherwise insert nothing. Similarly for the other clauses. As the 6 alternatives are mutually exclusive only one will match. Thus only one of the (?...) clauses will have matched and so only one will insert text.
The easiest way to do this that I would recommend is to use AWK. If you're on Windows, look for the mingw32 precompiled binaries out there for free download (it'll be called gawk).
BEGIN {
FS = "_0";
a[1]="A_One";
a[2]="A_Two";
a[3]="A_Three";
a[4]="B_One";
a[5]="B_Two";
a[6]="B_Three";
}
{
printf("Label_%s\n", a[$2]);
}
Execute on Windows as follows:
C:\Users\Mydir>gawk -f test.awk awk.in
Label_A_One
Label_A_Two
Label_A_Three
Label_B_One
Label_B_Two
Label_B_Three