-- -- $Header: /Users/dap/lua/RCS/PageIO.lua,v 1.5 2007/02/11 08:48:12 dap Exp $ -- -- PageIO -- attempt to provide I/O abstraction to VoodooPad page -- -- Author (a) Damon Anton Permezel, 2007. All bugs revered. -- -- -- BUGS -- It appears that all the I/O goes to the last on opened. -- -- module(..., package.seeall) local OO=require"OO" local NSAttributedString = objc.class"NSAttributedString" local NSMutableDictionary = objc.class("NSMutableDictionary") local NSFont = objc.class("NSFont") local NSColor = objc.class("NSColor") local Colors = { black = NSColor:blackColor(), blue = NSColor:blueColor(), brown = NSColor:brownColor(), clear = NSColor:clearColor(), cyan = NSColor:cyanColor(), darkGray = NSColor:darkGrayColor(), gray = NSColor:grayColor(), green = NSColor:greenColor(), lightGray = NSColor:lightGrayColor(), magenta = NSColor:magentaColor(), orange = NSColor:orangeColor(), purple = NSColor:purpleColor(), red = NSColor:redColor(), white = NSColor:whiteColor(), yellow = NSColor:yellowColor(), rgb = function (r,g,b) return NSColor:colorWithCalibratedRed_green_blue_alpha(1.0 * r, 1.0 * g, 1.0 * b, 1.0) end, } local PageIO = {} local function _refresh(obj) obj.buf = objc.luaToNSString(obj.ts:mutableString()) obj.lines = objc.values(obj.buf:componentsSeparatedByString('\n')) obj.length = obj.buf:length() -- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -- If the current window in the window controller is not -- the one we were instantiated with, how can we force it -- back so that the I/O goes to the correct page? -- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX if (tostring(obj.doc:topWindowController()) ~= tostring(obj.wc)) then out('_refresh: %s ~= %s', tostring(obj.doc:topWindowController()), tostring(obj.wc)) out('_refresh: obj.wc:key %s obj.key %s topWinCtrl:key %s', obj.wc:key(), obj.key, obj.doc:topWindowController():key()) end -- XXXXXX -- XXXXXX comparison fails unless to tostring() them -- XXXXXX assert(tostring(obj.doc:topWindowController()) == tostring(obj.wc)) if (obj.wc:key() ~= obj.key) then out('_refresh: obj.wc:key %s obj.key %s topWinCtrl:key %s', obj.wc:key(), obj.key, obj.doc:topWindowController():key()) obj.doc:openPageWithTitle(obj.key) end assert(obj.wc:key() == obj.key) end function PageIO:init() self.doc = document if self.doc == nil then error("no document context") end self.wc = self.doc:topWindowController() self.orig_wc = self.wc self.orig_wi = self.wc:window() self.orig_key = self.wc:key() -- -- if `key' is supplied, use it to select/create the page, -- else use the current page. -- XXX: what if the current page is in the non-top window???? -- if self.key == nil then self.key = self.wc:key() else self.key = self.key:lower() self.doc:openPageWithTitle(self.key) self.wc = self.doc:topWindowController() end if self.wc == nil then error("cannot find window controller") end self.page = self.doc:vpDataForKey(self.key) if not self.page then error('cannot get page') end if self.page:type() ~= VPPageType then error('funny type: ' .. self.page:type()) end self.tv = self.wc:textView() self.ts = self.tv:textStorage() self.offset = 0 end function PageIO:len() _refresh(self) return self.length end function PageIO:seek(offset) _refresh(self) if 0 <= offset and offset <= self.length then self.offset = offset else self.offset = self.length end return self.offset end function PageIO:tell() _refresh(self) return self.offset end function PageIO:replace(str, attr) self:delete(str:len()) self:insert(str, attr) end -- -- insert - insert string at current position, updating position -- function PageIO:insert(str, attr) _refresh(self) local range = objc.luaToNSRange({location=self.offset, length=0}) local astr = NSAttributedString:alloc() if attr then -- I assume attr is a Lua table attrs = NSMutableDictionary:alloc():init():autorelease() for k, v in pairs(attr) do if k == 'font'then -- font is a table local name, size = 'Monaco', 12 for k, v in pairs(v) do if k == 'name' then name = v elseif k == 'size' then size = v else error(('unknown key in font: %s'):format(k)) end end local f = NSFont:fontWithName_size(name, size) attrs:setObject_forKey(f, "NSFont") elseif (k == 'foreground' or k == 'background') then local color, key if k == 'background' then key = "NSBackgroundColor" else key = "NSColor" end if type(v) == 'table' then -- r/g/b in table color = Colors.rgb(v.r or v.red or 1.0, v.g or v.green or 1.0, v.b or v.blue or 1.0) else color = Colors[v] if type(color) ~= type(Colors.yellow) then print(v) print(color) print(Colors.yellow) print(Colors[v]) error(("funny color: %s"):format(tostring(v))) end end attrs:setObject_forKey(color, key) else error(("unexpected attribute key: '%s'"):format(k)) end end astr:initWithString_attributes(str, attrs) else astr:initWithString(str) end self.ts:replaceCharactersInRange_withAttributedString(range, astr) self.offset = self.offset + str:len() end -- -- delete -- delete characters -- function PageIO:delete(n) _refresh(self) local off = self.offset local total = self.length if (n + off > total) then n = total - off end --[[ out("del: %d from offset %d/%d page '%s'", n, self.offset, total, tostring(self.buf)) --]] local range = objc.luaToNSRange({location=self.offset, length=n}) self.ts:deleteCharactersInRange(range) end function PageIO:deleteToEOL() _refresh(self) local off = self.offset local total = self.length --[[ out("del: offset %d/%d page '%s'", self.offset, total, tostring(self.buf)) --]] -- need to find which string we are within w.r.t. offset for line in self.lines do local len = line:len() total = total - len if total > 0 then len = len + 1 total = total - 1 end -- out("del: off %4d len %4d line '%s'", off, len, line) if len < off then off = off - len else -- we are within this string. -- we delete from self.offset to the end of this string. len = len - off -- out("deleting: off %4d len %4d", self.offset, len) local range =objc.luaToNSRange({location=self.offset, length=len}) self.ts:deleteCharactersInRange(range) return end end end -- -- deleteToEOP -- delete from current offset to end of page -- function PageIO:deleteToEOP() _refresh(self) local range = objc.luaToNSRange({location=self.offset, length=self.length-self.offset}) self.ts:deleteCharactersInRange(range) end -- -- display -- function PageIO:display() _refresh(self) self.tv:display() end -- -- restore - attempt to restore the view -- -- XXXXX does not restore view -- function PageIO:restore() self.doc:openPageWithTitle(self.key) self.orig_wi:makeMainWindow() end function PageIO:dumpLines() _refresh(self) local off = 0 local cnt = 0 for line in self.lines do len = line:len() out("line %4d: off %4d len %4d '%s'", cnt, off, len, line) off = off + len + 1 cnt = cnt + 1 end end -- -- contents - return the page contents -- function PageIO:contents() _refresh(self) return tostring(self.buf) end return OO.class(PageIO)