I just started using Hammerspoon. I'm trying to output multiple lines of text by pressing Cmd+Shift+l .
Here is what I have tried so far :
hs.hotkey.bind({"cmd", "shift"}, "l", function()
hs.eventtap.keyStrokes('from sklearn import metrics')
hs.eventtap.keyStroke("return")
hs.eventtap.keyStrokes('from sklearn.cross_validation import train_test_split')
end)
I also tried with inline "\n" and "%\n"
How can I bind a key combination to output multiple lines of text? Or, How can I send a newline character?
I ran into the same problem. I tried what you tried above and although it worked in many applications, it still didn't work in Chrome. I used the pasteboard (clipboard) as a workaround.
jira_text = [[a
long
multi-line
string]]
-- Hotkey JIRA text
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "J", function ()
hs.alert.show("Remove this message after debugging!")
--hs.eventtap.keyStrokes(jira_text)#don't do this!
hs.pasteboard.writeObjects(jira_text)
hs.eventtap.keyStroke("cmd", "v")
end)
--
You could improve it further by using a custom named pasteboard so it doesn't overwrite your clipboard contents (if you want that).
I also ran into this problem and improved Josh Fox's answer by saving the contents of the system pasteboard into a temporary pasteboard before loading and pasting the multi-line string.
MULTILINE_STRING = [[multi
line
string]]
-- Paste Multi-line String
hs.hotkey.bind({'ctrl', 'cmd'}, 'F1', function()
-- save clipboard data to temp
tempClipboard = hs.pasteboard.uniquePasteboard()
hs.pasteboard.writeAllData(tempClipboard, hs.pasteboard.readAllData(nil))
-- load string into clipboard and paste
hs.pasteboard.writeObjects(MULTILINE_STRING)
hs.eventtap.keyStroke({'cmd'}, 'v')
-- recall clipboard data
hs.pasteboard.writeAllData(nil, hs.pasteboard.readAllData(tempClipboard))
hs.pasteboard.deletePasteboard(tempClipboard)
end)
I wasn't loving all this clipboard manipulation (too many side effects, probably unnecessarily heavy performance-wise), so I just solved this with the use of a helper function and some string splitting. Keep in mind that lua doesn't have a native string splitting function, I'm using the one from stringy here, but any custom or library-supplied string splitting function will work.
--- prevents hs.eventtap.keyStrokes from chewing up `\n`
--- #param str string
--- #return nil
function pasteMultilineString(str)
local lines = stringy.split(str, "\n")
local is_first_line = true
for _, line in ipairs(lines) do
if is_first_line then
is_first_line = false
else
hs.eventtap.keyStroke({}, "return")
end
hs.eventtap.keyStrokes(line)
end
end
Related
I'm trying to modify an existing lua script that cleans up subtitle data in Aegisub.
I want to add the ability to delete lines that contain the symbol "♪"
Here is the code I want to modify:
-- delete commented or empty lines
function noemptycom(subs,sel)
progress("Deleting commented/empty lines")
noecom_sel={}
for s=#sel,1,-1 do
line=subs[sel[s]]
if line.comment or line.text=="" then
for z,i in ipairs(noecom_sel) do noecom_sel[z]=i-1 end
subs.delete(sel[s])
else
table.insert(noecom_sel,sel[s])
end
end
return noecom_sel
end
I really have no idea what I'm doing here, but I know a little SQL and LUA apparently uses the IN keyword as well, so I tried modifying the IF line to this
if line.text in (♪) then
Needless to say, it didn't work. Is there a simple way to do this in LUA? I've seen some threads about the string.match() & string.find() functions, but I wouldn't know where to start trying to put that code together. What's the easiest way for someone with zero knowledge of Lua?
in is only used in the generic for loop. Your if line.text in (♪) then is no valid Lua syntax.
Something like
if line.comment or line.text == "" or line.text:find("\u{266A}") then
Should work.
In Lua every string have the string functions as methods attached.
So use gsub() on your string variable in loop like...
('Text with ♪ sign in text'):gsub('(♪)','note')
...thats replace the sign and output is...
Text with note sign in text
...instead of replacing it with 'note' an empty '' deletes it.
gsub() is returning 2 values.
First: The string with or without changes
Second: A number that tells how often the pattern matches
So second return value can be used for conditions or success.
( 0 stands for "pattern not found" )
So lets check above with...
local str,rc=('Text with strange ♪ sign in text'):gsub('(♪)','notation')
if rc~=0 then
print('Replaced ',rc,'times, changed to: ',str)
end
-- output
-- Replaced 1 times, changed to: Text with strange notation sign in text
And finally only detect, no change made...
local str,rc=('Text with strange ♪ sign in text'):gsub('(♪)','%1')
if rc~=0 then
print('Found ',rc,'times, Text is: ',str)
end
-- output is...
-- Found 1 times, Text is: Text with strange ♪ sign in text
The %1 holds what '(♪)' found.
So ♪ is replaced with ♪.
And only rc is used as a condition for further handling.
I'm currently working on a logging system. My problems arise when using for loops and file writing. Here is a small example:
file = io.open("text.txt","a") --text.txt can be literally anything
for i=1,8 do
if x == true then
file:write("X is true.")
elseif y == true then
file:write("Y is true.")
end
end
Is there a way to stop the file from being written to multiple times without using file:close()? I have a huge number of different file:write sections, and adding file:close() after all of them would be a massive problem.
If file:close() every time is a massive problem, then this is the custom logic you need.
myFileMetatable = {} --implement all necessary file operations here
function myFileMetatable.write(self, str)
if not self.written then
self.written = true
self.f:write(str)
end
end
function myFileMetatable.close(self)
self.f:close()
end
myFile = {}
function myFile.open(filename, mode)
local t = {f = io.open(filename, mode)}
setmetatable(t, {__index = myFileMetatable})
return t
end
--now you can do
file = myFile.open("test", "w")
file:write("test")
file:write("hello")
file:write("world")
file:close() --and only "test" will be written
Note that this is probably much better than replacing file:write(str) with something file_write(file, str), since you need to store somewhere the fact that the file has already been written to, which you cannot store inside the FILE* object and using a global variable for that will break when using multiple files. That's why I wrap the FILE* object in a table and use myFileMetatable to implement my own methods that I will need.
However, if you need just one file at a time and don't mind the global variable then this is more efficient.
file_written = false
function file_write(file, str)
if not file_written then
file_written = true
file:write(str)
end
end
file = io.open("test", "w")
file_write(file, "test")
file_write(file, "hello")
file_write(file, "world")
file:close()
Mind that it's not as pretty as the first example and you might face a problem in the future, if you decide to expand beyond one file.
How Egor Skriptunoff already said, I'll recommend you to write your own writing function. I'm normally using sth. like this:
local function writeFile(filePath, str)
local outfile = io.open(filePath, 'w')
outfile:write(str)
outfile:close()
end
For appending to the file easily change the mode from w to a.
For my specific case, I've found a solution - since I just want a single option to print, and it's the last option (rather than the first, and I should've specified this), I can just set a variable to what I want my output to be and write that at the end.
log = ""
if x == 2 then
log = "X is 2."
elseif y == 2 then
log = "Y is 2."
end
file:write(log)
For the last option, I'd refer anyone to the accepted answer which should be perfect.
Good afternoon everyone,
My problem is that I have 2 XML lists
<List1> <Agency>String</Agency> </List1>
and
<List2><Agency2>String</Agency2><List2>.
In Lua I need to create a program which is parsing this list and when the user inputs a matching string from List 1 or List 2, the program needs to actually confirm to the user if the string belongs to either L1 or L2 or if the string is inexistent. I'm new to Lua and to programming generally speaking and I would be very grateful for you answers. I have LuaExpat as a plugin but I can't seem to be able to actually read from file, I can only do some beginner tricks if the xml list is written in the code. At a later time this small program will be fed by an RSS.
require("lxp")
local stuff = {}
xmldata="<Top><A/> <B a='1'/> <B a='2'/><B a='3'/><C a='3'/></Top>"
function doFunc(parser, name, attr)
if not (name == 'B') then return end
stuff[#stuff+1]= attr
end
local xml = lxp.new{StartElement = doFunc}
xml:parse(xmldata)
xml:close()
print(stuff[3].a)
This code is a tutorial over the web that works, everything is just fine it prints nr. 3. Now I want to know how to do that from an actual file, as if I input io.read:(file, "r" or "rb" ) under xmldata variable and run the same thing it returns either empty space or nil.
Actually I am writting a programming language in Lua. It was quite fun. I've wrote a bit of standard library (stack op and simple io). Then I've thought about labels. It would look like in assembly. While and for loop aren't funny in any bit so programming in that language can be quite challenging. Here are some requirements for this system:
Label stack (or array, dictionary) must be accessible from global context.
Jump instruction handler will be in separate file.
This is how my label-handling function look like:
function handleLabel(name,currentLine)
end
I have no idea how to implement this kind of magic. First I've thought about that:
LabelLineIDS = {}
Labels = {}
Labelamount = 1;
function handleLabel(name,currentLine)
LabelLineIDS[Labelamount]=currentline
Labels[Labelamount]=name
Labelamount=Labelamount+1
end
-- In file "jump.lua":
local function has_value (tab, val)
for index, value in ipairs(tab) do
if value == val then
return index
end
end
print ("Error: Label not defined.") -- Bail out.
os.exit(1)
end
local function _onlabel()
local labelName = globalparams --Globalparams variable contain parameters to each function, at the moment
--this will contain label name. It _can_ be nil.
return LabelLineIDS[has_value(Labels, labelName)]
end
CurrLine = _onlabel() --Currline - current line that get's parsed.
--1 command per one line.
But I'm unsure is this well written or even work. Can you give me idea how to parse labels in my programming language (and write jump functions)? Or if my code is pretty ok at the moment, can you help me to improve it?
Using line counter in my parser I've decided to implement gotos like we can see in BASIC. Thanks everyone for help.
I have a file containing a text representation of an object. I have written a combinator parser grammar that parses the text and returns the object. In the text, "#" is a comment delimiter: everything from that character to the end of the line is ignored. Blank lines are also ignored. I want to process text one line at a time, so that I can handle very large files.
I don't want to clutter up my parser grammar with generic comment and blank line logic. I'd like to remove these as a preprocessing step. Converting the file to an iterator over line I can do something like this:
Source.fromFile("file.txt").getLines.map(_.replaceAll("#.*", "").trim).filter(!_.isEmpty)
How can I pass the output of an expression like that into a combinator parser? I can't figure out how to create a Reader object out of a filtered expression like this. The Java FileReader interface doesn't work that way.
Is there a way to do this, or should I put my comment and blank line logic in the parser grammar? If the latter, is there some util.parsing package that already does this for me?
The simplest way to do this is to use the fromLines method on PagedSeq:
import scala.collection.immutable.PagedSeq
import scala.io.Source
import scala.util.parsing.input.PagedSeqReader
val lines = Source.fromFile("file.txt").getLines.map(
_.replaceAll("#.*", "").trim
).filterNot(_.isEmpty)
val reader = new PagedSeqReader(PagedSeq.fromLines(lines))
And now you've got a scala.util.parsing.input.Reader that you can plug into your parser. This is essentially what happens when you parse a java.io.Reader, anyway—it immediately gets wrapped in a PagedSeqReader.
Not the prettiest code you'll ever write, but you could go through a new Source as follows:
val SEP = System.getProperty("line.separator")
def lineMap(fileName : String, trans : String=>String) : Source = {
Source.fromIterable(
Source.fromFile(fileName).getLines.flatMap(
line => trans(line) + SEP
).toIterable
)
}
Explanation: flatMap will produce an iterator on characters, which you can turn into an Iterable, which you can use to build a new Source. You need the extra SEP because getLines removes it by default (using \n may not work as Source will not properly separate the lines).
If you want to apply filtering too, i.e. remove some of the lines, you could for instance try:
// whenever `trans` returns `None`, the line is dropped.
def lineMapFilter(fileName : String, trans : String=>Option[String]) : Source = {
Source.fromIterable(
Source.fromFile(fileName).getLines.flatMap(
line => trans(line).map(_ + SEP).getOrElse("")
).toIterable
)
}
As an example:
lineMapFilter("in.txt", line => if(line.isEmpty) None else Some(line.reverse))
...will remove empty lines and reverse non-empty ones.