I took at shot at this using the ideas mentioned from @fico, @kingkeith, and I. I am not a python programmer so I may be doing some things incorrectly:
import sublime, sublime_plugin
class StorytellerListener (sublime_plugin.EventListener):
def __init__(self):
#do we need to call the base class constructor???
#a shadow copy of the text in the buffer (this will be changed to event
#objects with timestamp, dev group, etc. later)
#this object will have a 2D array for each buffer (keyed by the buffer id)
#there will be an arry for each line and events will be added to a line
#newlines will be added to the end of the line they are on
#file 1:
#123
#456
#789
#[["1", "2", "3", "\n"]["4", "5", "6", "\n"]["7", "8", "9"]]
self.allInsertEventsByFile = {}
#stores the size of each buffer (keyed by the buffer id)
self.allBufferSizes = {}
def on_new(self, view):
#all new files get an empty 2D array to hold insert events
self.allInsertEventsByFile[view.buffer_id()] = [[]]
#all new files start with a buffer size of 0
self.allBufferSizes[view.buffer_id()] = 0
#view is the changed file
def on_modified (self, view):
#get all of the cursors (this only works with one)
updatedRegions = view.sel()
#go through each of the regions (usually only one- multiple regions if
#there are multiple cursors)
for region in updatedRegions:
#get the current size of the text inside the active buffer
newBufferSize = view.size()
#get the id of the buffer for a multiple file project
bufferId = view.buffer_id();
#holds the number of characters that were either inserted or deleted
numCharsChanged = abs(newBufferSize - self.allBufferSizes[bufferId])
#if the current buffer size is exactly the same as the last edit
if(newBufferSize == self.allBufferSizes[bufferId]):
#I'm not sure when this happens!!!
print("Size the same-- When does this happen????")
#new buffer size is smaller than the old buffer size, this must be a delete
elif(newBufferSize < self.allBufferSizes[bufferId]):
#remove some data from the shadow container
self.removeEvent(numCharsChanged, region.a, self.allInsertEventsByFile[bufferId], view)
#buffer is larger, must be an insert
else:
self.insertEvent(numCharsChanged, region.a, self.allInsertEventsByFile[bufferId], view)
#store the new buffer size for next time
self.allBufferSizes[bufferId] = newBufferSize
def printEvents(self, view):
print("buffer id: %i" % view.buffer_id())
#get the events for this buffer
allEvents = self.allInsertEventsByFile[view.buffer_id()]
#go through all of the rows
for row in allEvents:
#print each row
print(row)
def insertEvent(self, numCharsChanged, cursorPoint, bufferOfInsertEvents, view):
#get the region with the new text
insertedRegion = sublime.Region(cursorPoint - numCharsChanged, cursorPoint)
#get the text from that region of the view
insertedText = view.substr(insertedRegion)
#go back to where the insert started and get the row and column
beginInsertRow, beginInsertColumn = view.rowcol(cursorPoint - numCharsChanged)
#used to specify where to insert
insertRow = beginInsertRow
insertColumn = beginInsertColumn
#for each of the new characters, insert into the shadow buffer
for newChar in insertedText:
#if the new character is a newline
if newChar == "\n":
#get the rest of the current line so we can add it to the next line
restOnLine = bufferOfInsertEvents[insertRow][insertColumn:]
#insert the newline event where it occured in the current line
bufferOfInsertEvents[insertRow].insert(insertColumn, newChar)
#insert a new line and add the rest of the previous line
bufferOfInsertEvents.insert(insertRow + 1, restOnLine)
#remove all of the characters we already added
for x in restOnLine:
#remove from the end of the line
bufferOfInsertEvents[insertRow].pop()
#start back at the beginning of the next line
insertRow = insertRow + 1
insertColumn = 0
else: #the new character is NOT a newline
#if the insert is on a new line (happens after deleting all the
#chars on a line and then adding some back)
if insertRow == len(bufferOfInsertEvents):
#add the new line
bufferOfInsertEvents.append([])
#add the character event to the current line
bufferOfInsertEvents[insertRow].insert(insertColumn, newChar)
#move the column index forward
insertColumn = insertColumn + 1
#print the new state of the events
self.printEvents(view)
def removeEvent(self, numCharsChanged, cursorPoint, bufferOfInsertEvents, view):
#get the row and column of the cursor after the delete (at the beginning of the delete)
deleteRow, deleteColumn = view.rowcol(cursorPoint)
count = 0
#start removing events, once for each of the chars deleted
while count < numCharsChanged:
#if we are removing a newline
if bufferOfInsertEvents[deleteRow][deleteColumn] == "\n":
#remove the newline from the end of the line
del bufferOfInsertEvents[deleteRow][deleteColumn]
#if there are more rows underneath
if deleteRow < len(bufferOfInsertEvents) - 1:
#grab the next line
nextLine = bufferOfInsertEvents[deleteRow + 1]
#add the characters from the next line to the current line
for char in nextLine:
bufferOfInsertEvents[deleteRow].append(char)
#remove the next line since we copied it over
bufferOfInsertEvents.pop(deleteRow + 1)
else: #removing a non-newline
#all removes happen at the cursor point since and events slide back
del bufferOfInsertEvents[deleteRow][deleteColumn]
#if the line is completely empty now AND it is not last line
if len(bufferOfInsertEvents[deleteRow]) == 0 and len(bufferOfInsertEvents) > 1:
#remove the empty line
bufferOfInsertEvents.pop(deleteRow)
#if we just removed the last character on a line
elif deleteColumn == len(bufferOfInsertEvents[deleteRow]):
#move on to the next row
deleteColumn = 0
deleteRow = deleteRow + 1
count = count + 1
#print the new state of the events
self.printEvents(view)
Currently, it only tracks new files. To run it, open up a new file using sublime and make sure the console is open.
It seems to work with single cursor inserts and deletes (typing and cutting and pasting). I am using the buffer size to determine if there was an insert or a delete. I am not sure when the file would change and the buffer size would be exactly the same size as before but it does appear to happen occasionally. Does anyone know when/why that happens?
I am also not sure about find/replace scenarios, multiple cursors, etc. If anyone with a lot of sublime experience can guide me that would be much appreciated.