"
COPYRIGHT (c) 1990 by Claus Gittinger
All Rights Reserved
This software is furnished under a license and may be used
only in accordance with the terms of that license and with the
inclusion of the above copyright notice. This software may not
be provided or otherwise made available to, or used by, any
other person. No title to or ownership of the software is
hereby transferred.
"
'From Smalltalk/X, Version:2.10.4 on 6-nov-1994 at 21:36:13'!
StandardSystemView subclass:#ChangesBrowser
instanceVariableNames:'changeListView codeView changeFileName changeChunks
changePositions changeClassNames changeHeaderLines anyChanges
changeNrShown changeNrProcessed skipSignal'
classVariableNames:''
poolDictionaries:''
category:'Interface-Browsers'
!
ChangesBrowser comment:'
COPYRIGHT (c) 1990 by Claus Gittinger
All Rights Reserved
$Header: /cvs/stx/stx/libtool/ChangesBrowser.st,v 1.19 1994-11-17 14:44:05 claus Exp $
'!
!ChangesBrowser class methodsFor:'documentation'!
copyright
"
COPYRIGHT (c) 1990 by Claus Gittinger
All Rights Reserved
This software is furnished under a license and may be used
only in accordance with the terms of that license and with the
inclusion of the above copyright notice. This software may not
be provided or otherwise made available to, or used by, any
other person. No title to or ownership of the software is
hereby transferred.
"
!
version
"
$Header: /cvs/stx/stx/libtool/ChangesBrowser.st,v 1.19 1994-11-17 14:44:05 claus Exp $
"
!
documentation
"
this implements a browser for the changes-file.
See the extra document 'doc/misc/cbrowser.doc' for how to use this browser.
written jan 90 by claus
Notice:
this needs a total rewrite, to build up a changeSet from the file
(which did not exist when the ChangesBrowser was written) and
manipulate that changeSet.
This ways, we get a browser for any upcoming incore changeSets for
free.
"
! !
!ChangesBrowser class methodsFor:'defaults'!
defaultLabel
^ 'Changes Browser'
! !
!ChangesBrowser class methodsFor:'instance creation'!
new
"create a new changes browser"
^ super label:'Changes Browser'
icon:(Form fromFile:'CBrowser.xbm' resolution:100)
!
openOn:aFileName
"create c changes browser on a change file"
^ ((self new label:'Changes Browser: ', aFileName) changeFileName:aFileName) open
! !
!ChangesBrowser class methodsFor:'behavior'!
autoSelectNext
"returning true here, makes a Delete operation automatically
select the next change"
^ true
! !
!ChangesBrowser methodsFor:'menu stuff'!
disableMenuEntries
"enable all entries refering to a selected change"
#(doApply doApplyClassRest doApplyRest doDelete doDeleteRest doDeleteClassRest
doDeleteClassAll doCompare doMakePatch doSaveChangeInFile doMakePermanent
doSave doSaveRest doSaveClassAll doSaveClassRest doBrowse)
do:[:sel |
changeListView middleButtonMenu disable:sel
].
!
enableMenuEntries
"enable all entries refering to a selected change"
#(doApply doApplyClassRest doApplyRest
doDelete doDeleteRest doDeleteClassRest
doDeleteClassAll
doCompare doMakePatch doSaveChangeInFile doMakePermanent
doSave doSaveRest doSaveClassAll doSaveClassRest doBrowse)
do:[:sel |
changeListView middleButtonMenu enable:sel
].
! !
!ChangesBrowser methodsFor:'private'!
clearCodeView
self unselect "changeListView deselect".
codeView contents:nil.
changeNrShown := nil
!
unselect
"common unselect"
changeListView deselect.
self disableMenuEntries
!
streamForChange:changeNr
"answer a stream for change"
|aStream|
aStream := FileStream readonlyFileNamed:changeFileName.
aStream isNil ifTrue:[^ nil].
aStream position:(changePositions at:changeNr).
^ aStream
!
applyChange:changeNr
"fileIn a change"
|aStream upd nm|
aStream := self streamForChange:changeNr.
aStream isNil ifTrue:[^ self].
nm := self classNameOfChange:changeNr.
nm notNil ifTrue:[
|cls|
cls := Smalltalk at:(nm asSymbol) ifAbsent:[].
cls notNil ifTrue:[
cls isLoaded ifFalse:[
cls autoload
]
]
].
changeNrProcessed := changeNr.
upd := Class updateChanges:false.
[
|sig|
(skipSignal notNil) ifTrue:[
sig := skipSignal
] ifFalse:[
sig := Object abortSignal
].
sig catch:[
aStream fileInNextChunkNotifying:self
].
changeNrProcessed := nil.
] valueNowOrOnUnwindDo:[
Class updateChanges:upd.
aStream close
].
!
setChangeList
"extract type-information from changes and stuff into top selection
view"
changeListView setList:changeHeaderLines expandTabs:false.
"/ changeListView deselect.
self disableMenuEntries
!
silentDeleteChange:changeNr
"delete a change do not update changeListView"
anyChanges := true.
changeChunks removeIndex:changeNr.
changePositions removeIndex:changeNr.
changeClassNames removeIndex:changeNr.
changeHeaderLines removeIndex:changeNr
!
readChangesFileInBackground:inBackground
"read the changes file, create a list of header-lines (changeChunks)
and a list of chunk-positions (changePositions).
Starting with 2.10.3, the entries are multi-col entries;
the cols are:
1 class/selector
2 delta
'+' -> new method (w.r.t. current state)
'-' -> removed method (w.r.t. current state)
'?' -> class does not exist currently
3 type of change
doit
method
category change
"
|aStream maxLen|
maxLen := 60.
aStream := FileStream readonlyFileNamed:changeFileName.
aStream isNil ifTrue:[^ nil].
self withCursor:(Cursor read) do:[
|tabSpec myProcess myPriority|
tabSpec := TabulatorSpecification new.
tabSpec unit:#inch.
tabSpec positions:#(0 4.5 4.65 5.5 ).
" cls>>sel +/- type info"
tabSpec align: #(#left #left #left #left).
"
this is a time consuming operation (especially, if reading an
NFS-mounted directory; therefore lower my priority ...
"
inBackground ifTrue:[
myProcess := Processor activeProcess.
myPriority := myProcess priority.
myProcess priority:(Processor userBackgroundPriority).
].
[
|excla|
changeChunks := OrderedCollection new.
changeHeaderLines := OrderedCollection new.
changePositions := OrderedCollection new.
excla := aStream class chunkSeparator.
[aStream atEnd] whileFalse:[
|entry changeDelta changeString changeType
line s l changeClass sawExcla category
chunkText chunkPos|
"
get a chunk (separated by excla)
"
aStream skipSeparators.
chunkPos := aStream position.
sawExcla := aStream peekFor:excla.
chunkText := aStream nextChunk.
chunkText notNil ifTrue:[
|index headerLine|
"
only first line is saved in changeChunks ...
"
index := chunkText indexOf:(Character cr).
(index ~~ 0) ifTrue:[
chunkText := chunkText copyTo:(index - 1).
"take care for comment changes - must still be a
valid expression for classNameOfChange: to work"
(chunkText endsWith:'comment:''') ifTrue:[
chunkText := chunkText , '...'''
]
].
changeChunks add:chunkText.
changePositions add:chunkPos.
headerLine := nil.
changeDelta := ' '.
sawExcla ifFalse:[
(chunkText startsWith:'''---- snap') ifTrue:[
changeType := ''.
headerLine := chunkText.
changeString := (chunkText contractTo:maxLen).
] ifFalse:[
|p sel cls|
headerLine := chunkText , ' (doIt)'.
"
first, assume doIt - then lets have a more detailed look ...
"
changeType := '(doIt)'.
changeString := (chunkText contractTo:maxLen).
p := Parser parseExpression:chunkText.
(p notNil
and:[p ~~ #Error
and:[p isMessage]]) ifTrue:[
sel := p selector.
].
(sel == #removeSelector:) ifTrue:[
p receiver isUnaryMessage ifTrue:[
cls := p receiver receiver name.
changeClass := (Smalltalk classNamed:cls) class.
cls := cls , ' class'.
] ifFalse:[
cls := p receiver name.
changeClass := (Smalltalk classNamed:cls)
].
sel := (p args at:1) evaluate.
(changeClass isNil or:[changeClass isLoaded not]) ifTrue:[
changeDelta := '?'
] ifFalse:[
(changeClass implements:sel asSymbol) ifTrue:[
changeDelta := '-'.
]
].
changeType := '(remove)'.
changeString := self contractClass:cls selector:sel to:maxLen.
].
(sel == #category:) ifTrue:[
(p receiver isMessage
and:[p receiver selector == #compiledMethodAt:]) ifTrue:[
p receiver receiver isUnaryMessage ifTrue:[
cls := p receiver receiver receiver name.
changeClass := (Smalltalk classNamed:cls) class.
cls := cls , ' class'.
] ifFalse:[
cls := p receiver receiver name.
changeClass := (Smalltalk classNamed:cls)
].
sel := (p receiver args at:1) evaluate.
changeType := '(category change)'.
changeString := self contractClass:cls selector:sel to:maxLen.
]
]
].
] ifTrue:[
|done first p sel cls text|
"
method definitions actually consist of
two (or more) chunks; skip next chunk(s)
up to an empty one.
The system only writes one chunk,
and we cannot handle more in this ChangesBrowser ....
"
cls := nil.
p := Parser parseExpression:chunkText.
(p notNil and:[p ~~ #Error]) ifTrue:[
sel := p selector.
(sel == #methodsFor:) ifTrue:[
p receiver isUnaryMessage ifTrue:[
cls := p receiver receiver name.
changeClass := (Smalltalk classNamed:cls) class.
cls := cls , ' class'.
] ifFalse:[
cls := p receiver name.
changeClass := Smalltalk classNamed:cls
].
category := (p args at:1) evaluate.
].
].
done := false.
first := true.
[done] whileFalse:[
text := aStream nextChunk.
text isNil ifTrue:[
done := true
] ifFalse:[
done := text isEmpty
].
done ifFalse:[
first ifFalse:[
Transcript showCr:'only one method per ''methodsFor:'' handled'.
] ifTrue:[
first := false.
"
try to find the selector
"
sel := nil.
cls notNil ifTrue:[
p := Parser
parseMethodSpecification:text
in:nil
ignoreErrors:true
ignoreWarnings:true.
(p notNil and:[p ~~ #Error]) ifTrue:[
sel := p selector.
]
].
sel isNil ifTrue:[
changeString := (chunkText contractTo:maxLen).
changeType := '(change)'.
] ifFalse:[
changeString := self contractClass:cls selector:sel to:maxLen.
changeType := '(method in: ''' , category , ''')'.
].
sel isNil ifTrue:[
headerLine := chunkText , ' (change)'.
] ifFalse:[
headerLine := cls , ' ' , sel , ' ' , '(change category: ''' , category , ''')'.
].
(changeClass isNil or:[changeClass isLoaded not]) ifTrue:[
changeDelta := '?'
] ifFalse:[
(changeClass implements:sel asSymbol) ifFalse:[
changeDelta := '+'.
]
]
]
]
]
].
changeString notNil ifTrue:[
entry := MultiColListEntry new.
entry tabulatorSpecification:tabSpec.
entry colAt:1 put:changeString.
entry colAt:2 put:changeDelta.
entry colAt:3 put:changeType.
changeHeaderLines add:entry
] ifFalse:[
headerLine notNil ifTrue:[
changeHeaderLines add:headerLine
]
]
]
].
changeClassNames := OrderedCollection new grow:(changeChunks size).
aStream close.
anyChanges := false
] valueNowOrOnUnwindDo:[
inBackground ifTrue:[myProcess priority:myPriority].
].
]
!
contractClass:className selector:selector to:maxLen
|s l|
s := className , ' ', selector.
s size > maxLen ifTrue:[
l := maxLen - 1 - selector size max:20.
s := (className contractTo:l) , ' ' , selector.
s size > maxLen ifTrue:[
l := maxLen - 1 - className size max:20.
s := className , ' ', (selector contractTo:l).
s size > maxLen ifTrue:[
s := (className contractTo:(maxLen // 2 - 1)) , ' ' , (selector contractTo:maxLen // 2)
]
]
].
^ s
!
autoSelect:changeNr
self class autoSelectNext ifTrue:[
(changeNr <= changePositions size) ifTrue:[
self clearCodeView.
changeListView selection:changeNr.
self changeSelection:changeNr.
^ self
]
].
self clearCodeView
!
classNameOfChange:changeNr
"return the classname of a change (for xxx class - changes xxx is returned)
- since parsing ascii methods is slow, keep result cached in
changeClassNames for the next query"
|chunk aParseTree recTree sel name arg1Tree|
changeNr isNil ifTrue:[^ nil].
"
first look, if not already known
"
name := changeClassNames at:changeNr.
name notNil ifTrue:[^ name].
"
get the chunk
"
chunk := changeChunks at:changeNr.
chunk isNil ifTrue:[^ nil]. "mhmh - empty"
"
use parser to construct a parseTree
"
aParseTree := Parser parseExpression:chunk.
(aParseTree isNil or:[aParseTree == #Error]) ifTrue:[
^ nil "seems strange ... (could be a comment)"
].
aParseTree isMessage ifFalse:[
^ nil "very strange ... (whats that ?)"
].
"
ask parser for selector
"
sel := aParseTree selector.
recTree := aParseTree receiver.
"
is it a method-change, methodRemove or comment-change ?
"
(#(#'methodsFor:'
#'privateMethodsFor:'
#'protectedMethodsFor:'
#'publicMethodsFor:'
#'removeSelector:'
#'comment:'
#'renameCategory:to:'
) includes:sel) ifTrue:[
"
yes, the className is the receiver
"
(recTree notNil and:[recTree ~~ #Error]) ifTrue:[
recTree isUnaryMessage ifTrue:[
(recTree selector ~~ #class) ifTrue:[^ nil].
"id class methodsFor:..."
recTree := recTree receiver
].
recTree isPrimary ifTrue:[
name := recTree name.
changeClassNames at:changeNr put:name.
^ name
]
].
"more strange things"
^ nil
].
"
is it a change in a class-description ?
"
('subclass:*' match:sel) ifTrue:[
arg1Tree := aParseTree arg1.
(arg1Tree notNil and:[arg1Tree isConstant]) ifTrue:[
name := arg1Tree value asString.
changeClassNames at:changeNr put:name.
^ name
].
"very strange"
^ nil
].
"
is it a class remove ?
"
(sel == #removeClass:) ifTrue:[
(recTree notNil
and:[recTree ~~ #Error
and:[recTree isPrimary
and:[recTree name = 'Smalltalk']]]) ifTrue:[
arg1Tree := aParseTree arg1.
(arg1Tree notNil and:[arg1Tree isPrimary]) ifTrue:[
name := arg1Tree name.
changeClassNames at:changeNr put:name.
^ name
].
]
].
"
is it a method category change ?
"
(sel == #category:) ifTrue:[
(recTree notNil
and:[recTree ~~ #Error
and:[recTree isMessage
and:[recTree selector == #compiledMethodAt:]]]) ifTrue:[
recTree := recTree receiver.
recTree isUnaryMessage ifTrue:[
(recTree selector ~~ #class) ifTrue:[^ nil].
"id class "
recTree := recTree receiver
].
recTree isPrimary ifTrue:[
name := recTree name.
changeClassNames at:changeNr put:name.
^ name
]
]
].
^ nil
!
autoSelectOrEnd:changeNr
|last|
last := changePositions size.
changeNr < last ifTrue:[
self autoSelect:changeNr
] ifFalse:[
self clearCodeView.
changeListView selection:last .
self changeSelection:last .
]
!
writeBackChanges
"write back the changes file"
|inStream outStream|
outStream := FileStream newFileNamed:'n_changes'.
outStream isNil ifTrue:[
self warn:'cannot create temporary file'.
^ self
].
inStream := FileStream readonlyFileNamed:changeFileName.
inStream isNil ifTrue:[^ nil].
self withCursor:(Cursor write) do:[
|excla sawExcla done dir chunk
nChanges "{Class:SmallInteger}" |
excla := inStream class chunkSeparator.
nChanges := changePositions size.
1 to:nChanges do:[:index |
inStream position:(changePositions at:index).
sawExcla := inStream peekFor:excla.
chunk := inStream nextChunk.
sawExcla ifTrue:[
outStream nextPut:excla.
outStream nextChunkPut:chunk.
outStream cr.
"
a method-definition chunk - skip followups
"
done := false.
[done] whileFalse:[
chunk := inStream nextChunk.
chunk isNil ifTrue:[
done := true
] ifFalse:[
outStream nextChunkPut:chunk.
outStream cr.
done := chunk isEmpty
]
].
] ifFalse:[
outStream nextChunkPut:chunk.
outStream cr
]
].
outStream close.
inStream close.
dir := FileDirectory currentDirectory.
dir renameFile:changeFileName newName:'changes.bak'.
dir renameFile:'n_changes' newName:changeFileName.
anyChanges := false
]
!
deleteChange:changeNr
"delete a change"
changeListView deselect.
self silentDeleteChange:changeNr.
self setChangeList
!
queryCloseText
"made this a method for easy redefinition in subclasses"
^ 'Quit without updating changeFile ?'
!
compareChange:changeNr
"compare a change with current version"
|aStream chunk sawExcla parseTree thisClass cat oldSource newSource
parser sel oldMethod outcome showDiff d|
aStream := self streamForChange:changeNr.
aStream isNil ifTrue:[^ self].
sawExcla := aStream peekFor:(aStream class chunkSeparator).
chunk := aStream nextChunk.
sawExcla ifFalse:[
outcome := 'not comparable ...'
] ifTrue:[
parseTree := Parser parseExpression:chunk.
(parseTree notNil and:[parseTree isMessage]) ifTrue:[
(parseTree selector == #methodsFor:) ifTrue:[
thisClass := (parseTree receiver evaluate).
thisClass isBehavior ifTrue:[
showDiff := false.
cat := parseTree arg1 evaluate.
newSource := aStream nextChunk.
parser := Parser parseMethod:newSource in:thisClass.
(parser notNil and:[parser ~~ #Error]) ifTrue:[
sel := parser selector.
oldMethod := thisClass compiledMethodAt:sel.
oldMethod notNil ifTrue:[
(oldMethod category = cat) ifFalse:[
Transcript showCr:'category changed.'.
].
oldSource := oldMethod source.
(oldSource = newSource) ifTrue:[
outcome := 'same source'
] ifFalse:[
outcome := 'source changed.'.
showDiff := true
]
] ifFalse:[
outcome := 'method does not exist.'
]
] ifFalse:[
outcome := 'change unparsable.'
].
(showDiff and:[oldSource notNil and:[newSource notNil]]) ifTrue:[
d := DiffTextView openOn:oldSource and:newSource.
d label:'differences (current left; change right)'.
]
] ifFalse:[
outcome := 'class does not exist.'
]
] ifFalse:[
outcome := 'not comparable.'
]
] ifFalse:[
outcome := 'not comparable.'
]
].
Transcript showCr:outcome.
aStream close.
!
deleteChangesFrom:start to:stop
"delete a range of changes"
changeListView deselect.
stop to:start by:-1 do:[:changeNr |
self silentDeleteChange:changeNr
].
self setChangeList
!
changeFileName:aFileName
changeFileName := aFileName
!
readChangesFile
"read the changes file, create a list of header-lines (changeChunks)
and a list of chunk-positions (changePositions)"
^ self readChangesFileInBackground:false
!
appendChange:changeNr toFile:fileName
"append change to a file. return true if ok."
|aStream outStream chunk sawExcla separator|
outStream := FileStream oldFileNamed:fileName.
outStream isNil ifTrue:[
outStream isNil ifTrue:[
outStream := FileStream newFileNamed:fileName.
outStream isNil ifTrue:[
self warn:'cannot update file ''%1''' with:fileName.
^ false
]
]
].
outStream setToEnd.
aStream := self streamForChange:changeNr.
aStream isNil ifTrue:[
self warn:'oops cannot read change'.
^ false
].
separator := aStream class chunkSeparator.
sawExcla := aStream peekFor:separator.
sawExcla ifTrue:[
outStream nextPut:separator
].
chunk := aStream nextChunk.
outStream nextChunkPut:chunk.
outStream cr.
sawExcla ifTrue:[
chunk := aStream nextChunk.
outStream nextChunkPut:chunk.
outStream space
].
sawExcla ifTrue:[
outStream nextPut:separator
].
outStream cr.
aStream close.
outStream close.
^ true
!
makeChangeAPatch:changeNr
"append change to patchfile"
self appendChange:changeNr toFile:'patches'
!
makeChangePermanent:changeNr
"rewrite the source file where change changeNr lies"
self notify:'this is not yet implemented'
!
silentDeleteChangesFor:aClassName from:start to:stop
"delete changes for a given class in a range"
|thisClassName index|
index := stop.
[index >= start] whileTrue:[
thisClassName := self classNameOfChange:index.
thisClassName = aClassName ifTrue:[
self silentDeleteChange:index
].
index := index - 1
]
! !
!ChangesBrowser methodsFor:'initialize / release'!
initialize
|frame v|
super initialize.
changeFileName := 'changes'.
frame := VariableVerticalPanel origin:(0.0 @ 0.0)
corner:(1.0 @ 1.0)
borderWidth:0
in:self.
v := ScrollableView for:SelectionInListView in:frame.
v origin:(0.0 @ 0.0) corner:(1.0 @ 0.3).
changeListView := v scrolledView.
v := HVScrollableView for:CodeView miniScrollerH:true miniScrollerV:false in:frame.
v origin:(0.0 @ 0.3) corner:(1.0 @ 1.0).
codeView := v scrolledView.
codeView readOnly.
anyChanges := false.
ObjectMemory addDependent:self. "to get shutdown-update"
!
initializeMiddleButtonMenu
|labels|
labels := resources array:#(
'apply change'
'apply changes to end'
'apply changes for this class to end'
'apply all changes'
'-'
'delete'
'delete to end'
'delete changes for this class to end'
'delete all changes for this class'
'-'
'update'
'compress'
'compare with current version'
'browse class'
'-'
'make change a patch'
"/ 'update sourcefile from change'
"/ '-'
'save change in file ...'
'save changes to end in file ...'
'save changes for this class to end in file ...'
'save all changes for this class in file ...'
'-'
'writeback changeFile').
changeListView
middleButtonMenu:(PopUpMenu
labels:labels
selectors:#(
doApply
doApplyRest
doApplyClassRest
doApplyAll
nil
doDelete
doDeleteRest
doDeleteClassRest
doDeleteClassAll
nil
doUpdate
doCompress
doCompare
doBrowse
nil
doMakePatch
"/ doMakePermanent
"/ nil
doSave
doSaveRest
doSaveClassRest
doSaveClassAll
nil
doWriteBack)
receiver:self
for:changeListView)
!
realize
super realize.
self readChangesFileInBackground:true.
self setChangeList.
changeListView action:[:lineNr | self changeSelection:lineNr].
self disableMenuEntries.
!
update:what
|box|
(what == #aboutToExit) ifTrue:[
"
smalltalk is about to shut down -
- if change list was modified, ask user and save if requested.
"
anyChanges ifTrue:[
shown ifFalse:[
self unrealize.
self realize
].
self raise.
box := YesNoBox new.
box title:(resources at:'ChangesBrowser:\changefile has not been updated from the modified changelist.\\Update before exiting ?') withCRs.
box noText:(resources at:'don''t update').
box okText:(resources at:'update').
box yesAction:[self writeBackChanges]
noAction:[].
box showAtPointer
].
^ self
].
super update:what
! !
!ChangesBrowser methodsFor:'user interaction'!
changeSelection:lineNr
"show a change in the codeView"
|aStream sawExcla chunk|
aStream := self streamForChange:lineNr.
aStream isNil ifTrue:[^ self].
sawExcla := aStream peekFor:(aStream class chunkSeparator).
chunk := aStream nextChunk.
sawExcla ifTrue:[
chunk := aStream nextChunk
].
aStream close.
codeView contents:chunk.
codeView acceptAction:[:theCode | self doApply "noChangesAllowed"].
changeNrShown := lineNr.
self enableMenuEntries
!
doApply
"user wants a change to be applied"
|changeNr|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
self withCursor:(Cursor execute) do:[
skipSignal := nil.
self applyChange:changeNr.
self autoSelect:(changeNr + 1)
]
]
!
noChangesAllowed
"show a warning that changes cannot be changed"
self warn:'changes are not allowed to be changed'
!
doSaveClass
"user wants changes for some class from current to end to be appended to a file"
self doSaveClassFrom:1
!
doSaveClassAll
"user wants changes for some class from current to end to be appended to a file"
self doSaveClassFrom:1
!
doSaveClassRest
"user wants changes for some class from current to end to be appended to a file"
|changeNr|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
self doSaveClassFrom:changeNr
]
!
doBrowse
"user wants a browser on the class of a change"
|changeNr className cls|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
className := self classNameOfChange:changeNr.
className notNil ifTrue:[
(cls := Smalltalk classNamed:className) notNil ifTrue:[
SystemBrowser browseClass:cls
]
]
]
!
saveClass:aClassName from:startNr
"user wants changes from current to end to be appended to a file"
|changeNr fileBox|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
fileBox := FileSelectionBox new.
fileBox title:'append change to:'.
fileBox okText:'append'.
fileBox abortText:'cancel'.
fileBox action:[:fileName |
|thisClassName|
self withCursor:(Cursor write) do:[
startNr to:(changePositions size) do:[:changeNr |
thisClassName := self classNameOfChange:changeNr.
thisClassName = aClassName ifTrue:[
changeListView selection:changeNr.
(self appendChange:changeNr toFile:fileName) ifFalse:[
^ self
]
]
]
]
].
fileBox pattern:'*.chg'.
fileBox showAtPointer
]
!
doSaveClassFrom:startNr
"user wants changes from current to end to be appended to a file"
|changeNr classNameToSave|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
classNameToSave := self classNameOfChange:changeNr.
classNameToSave notNil ifTrue:[
self saveClass:classNameToSave from:startNr
]
]
!
doSaveRest
"user wants changes from current to end to be appended to a file"
|changeNr fileBox|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
fileBox := FileSelectionBox new.
fileBox title:'append change to:'.
fileBox okText:'append'.
fileBox abortText:'cancel'.
fileBox action:[:fileName |
self withCursor:(Cursor write) do:[
changeNr to:(changePositions size) do:[:changeNr |
changeListView selection:changeNr.
(self appendChange:changeNr toFile:fileName) ifFalse:[
^ self
]
]
]
].
fileBox pattern:'*.chg'.
fileBox showAtPointer
]
!
doDelete
"delete currently selected change"
|changeNr|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
self deleteChange:changeNr.
self autoSelectOrEnd:changeNr
]
!
doSave
"user wants a change to be appended to a file"
|changeNr fileBox|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
fileBox := FileSelectionBox new.
fileBox title:'append change to:'.
fileBox okText:'append'.
fileBox abortText:'cancel'.
fileBox action:[:fileName |
self withCursor:(Cursor write) do:[
self appendChange:changeNr toFile:fileName.
].
self autoSelect:(changeNr + 1)
].
fileBox pattern:'*.chg'.
fileBox showAtPointer
]
!
doUpdate
"reread the changes-file"
self readChangesFileInBackground:true.
realized ifTrue:[
self setChangeList.
changeListView hasSelection ifTrue:[self enableMenuEntries]
]
!
doMakePermanent
"user wants a change to be made permanent
- rewrite the source file where this change has to go"
|yesNoBox|
yesNoBox := YesNoBox new.
yesNoBox title:(resources at:'Warning: this operation cannot be undone').
yesNoBox okText:(resources at:'continue').
yesNoBox noText:(resources at:'abort').
yesNoBox okAction:[ |changeNr|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
self makeChangePermanent:changeNr.
self autoSelect:(changeNr + 1)
]
].
yesNoBox showAtPointer
!
doMakePatch
"user wants a change to be made a patch
- copy it over to the patches file"
|changeNr|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
self makeChangeAPatch:changeNr.
self autoSelect:(changeNr + 1)
]
!
doApplyRest
"user wants all changes from changeNr to be applied"
|changeNr|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
self withCursor:(Cursor execute) do:[
self clearCodeView.
skipSignal isNil ifTrue:[skipSignal := Signal new].
changeNr to:(changePositions size) do:[:changeNr |
changeListView selection:changeNr.
self applyChange:changeNr
].
]
]
!
doDeleteRest
"delete all changes from current to the end"
|changeNr|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
self deleteChangesFrom:changeNr to:(changePositions size).
self clearCodeView.
self autoSelectOrEnd:changeNr-1
]
!
doCompress
"compress the change-set; this replaces multiple method-changes by the last
(i.e. the most recent) change"
|aStream searchIndex anyMore deleteSet index
str snapshotProto snapshotPrefix snapshotNameIndex fileName|
aStream := FileStream readonlyFileNamed:changeFileName.
aStream isNil ifTrue:[^ self].
"
get a prototype snapshot record (to be independent of
the actual format ..
"
str := WriteStream on:String new.
Class addChangeRecordForSnapshot:'foo' to:str.
snapshotProto := str contents.
snapshotPrefix := snapshotProto copyTo:10.
snapshotNameIndex := snapshotProto findString:'foo'.
self withCursor:(Cursor execute) do:[
|numChanges classes selectors types excla sawExcla
changeNr chunk aParseTree parseTreeChunk
thisClass thisSelector codeChunk codeParser|
numChanges := changePositions size.
classes := Array new:numChanges.
selectors := Array new:numChanges.
types := Array new:numChanges.
"starting at the end, get the change class and change selector;
collect all in classes / selectors"
changeNr := numChanges.
excla := aStream class chunkSeparator.
[changeNr >= 1] whileTrue:[
aStream position:(changePositions at:changeNr).
sawExcla := aStream peekFor:excla.
chunk := aStream nextChunk.
sawExcla ifTrue:[
"optimize a bit if multiple methods for same category arrive"
(chunk = parseTreeChunk) ifFalse:[
aParseTree := Parser parseExpression:chunk.
parseTreeChunk := chunk
].
(aParseTree notNil
and:[(aParseTree ~~ #Error)
and:[aParseTree isMessage]]) ifTrue:[
(aParseTree selector == #methodsFor:) ifTrue:[
thisClass := (aParseTree receiver evaluate).
codeChunk := aStream nextChunk.
codeParser := Parser
parseMethodSpecification:codeChunk
in:thisClass
ignoreErrors:true
ignoreWarnings:true.
codeParser notNil ifTrue:[
selectors at:changeNr put:(codeParser selector).
classes at:changeNr put:thisClass.
types at:changeNr put:#methodsFor
]
]
]
] ifFalse:[
aParseTree := Parser parseExpression:chunk.
parseTreeChunk := chunk.
(aParseTree notNil
and:[(aParseTree ~~ #Error)
and:[aParseTree isMessage]]) ifTrue:[
(aParseTree selector == #removeSelector:) ifTrue:[
selectors at:changeNr put:(aParseTree arg1 value ).
classes at:changeNr put:(aParseTree receiver evaluate).
types at:changeNr put:#removeSelector
]
] ifFalse:[
(chunk startsWith:snapshotPrefix) ifTrue:[
str := chunk readStream position:snapshotNameIndex.
fileName := str upTo:(Character space).
"
kludge to allow use of match-check below
"
selectors at:changeNr put:snapshotPrefix.
classes at:changeNr put:fileName.
]
]
].
changeNr := changeNr - 1
].
aStream close.
"for all changes, look for another class/selector occurence later
in the list and, if there is one, add change number to the delete set"
deleteSet := OrderedCollection new.
changeNr := 1.
[changeNr < changePositions size] whileTrue:[
thisClass := classes at:changeNr.
thisSelector := selectors at:changeNr.
searchIndex := changeNr.
anyMore := true.
[anyMore] whileTrue:[
searchIndex := classes indexOf:thisClass
startingAt:(searchIndex + 1).
(searchIndex ~~ 0) ifTrue:[
((selectors at:searchIndex) == thisSelector) ifTrue:[
thisClass notNil ifTrue:[
deleteSet add:changeNr.
anyMore := false
]
]
] ifFalse:[
anyMore := false
]
].
changeNr := changeNr + 1
].
"finally delete what has been found"
(deleteSet size > 0) ifTrue:[
changeListView deselect.
index := deleteSet size.
[index > 0] whileTrue:[
self silentDeleteChange:(deleteSet at:index).
index := index - 1
].
self setChangeList.
"
scroll back a bit, if we are left way behind the list
"
changeListView firstLineShown > changePositions size ifTrue:[
changeListView makeLineVisible:changePositions size
].
self clearCodeView
]
]
!
doApplyAll
"user wants all changes to be applied"
self withCursor:(Cursor execute) do:[
self clearCodeView.
skipSignal isNil ifTrue:[skipSignal := Signal new].
1 to:(changePositions size) do:[:changeNr |
changeListView selection:changeNr.
self applyChange:changeNr
].
]
!
doCompare
"compare change with current system version
- give a note in transcript"
|changeNr|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
self withCursor:(Cursor execute) do:[
self compareChange:changeNr
]
]
!
doDeleteClassRest
"delete rest of changes with same class as currently selected change"
|changeNr|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
self withCursor:(Cursor execute) do:[
| classNameToDelete |
classNameToDelete := self classNameOfChange:changeNr.
classNameToDelete notNil ifTrue:[
changeListView selection:nil.
self silentDeleteChangesFor:classNameToDelete
from:changeNr
to:(changePositions size).
self setChangeList.
self autoSelectOrEnd:changeNr
]
]
]
!
doWriteBack
"write back the list onto the changes file"
anyChanges ifTrue:[
self writeBackChanges.
realized ifTrue:[
self readChangesFileInBackground:false.
realized ifTrue:[
self setChangeList
]
]
]
!
doDeleteClassAll
"delete all changes with same class as currently selected change"
|changeNr|
changeNr := changeListView selection.
changeNr notNil ifTrue:[
self withCursor:(Cursor execute) do:[
| classNameToDelete |
classNameToDelete := self classNameOfChange:changeNr.
classNameToDelete notNil ifTrue:[
changeListView selection:nil.
self silentDeleteChangesFor:classNameToDelete
from:1
to:(changePositions size).
self setChangeList.
self autoSelectOrEnd:changeNr
]
]
]
!
doApplyClassRest
"user wants all changes for this class from changeNr to be applied"
|changeNr |
changeNr := changeListView selection.
changeNr notNil ifTrue:[
self withCursor:(Cursor execute) do:[
|thisClassName classNameToApply|
classNameToApply := self classNameOfChange:changeNr.
classNameToApply notNil ifTrue:[
self clearCodeView.
skipSignal isNil ifTrue:[skipSignal := Signal new].
changeNr to:(changePositions size) do:[:changeNr |
thisClassName := self classNameOfChange:changeNr.
thisClassName = classNameToApply ifTrue:[
changeListView selection:changeNr.
self applyChange:changeNr
].
].
]
]
]
! !
!ChangesBrowser methodsFor:'termination'!
terminate
"window manager wants us to go away"
|box|
anyChanges ifTrue:[
box := OptionBox title:'' numberOfOptions:3.
box title:(resources at:'close ChangesBrowser.\\changefile has not been updated from the modified changelist.\\Update before closing ?') withCRs.
box buttonTitles:(resources array:#('abort' 'don''t update' 'update')).
box actions:(Array with:[^ self]
with:[self destroy]
with:[self writeBackChanges. self destroy]
).
box showAtPointer.
] ifFalse:[
self destroy
]
!
destroy
"destroy the receiver; make certain, that boxes are destroyed too"
ObjectMemory removeDependent:self.
super destroy
!
saveAndTerminate
"update the changes file and quit.
Dont depend on this being sent, not all window managers
send it; instead, they simply destroy the view."
anyChanges ifTrue:[
self writeBackChanges.
].
self destroy
! !
!ChangesBrowser methodsFor:'error handling'!
correctableError:aString position:relPos to:relEndPos
"compiler notifys us of an error - this should really not happen since
changes ought to be correct (did someone edit the changes file ??).
Show the bad change in the codeView and let codeView hilight the error;
no corrections allowed here therefore return false"
|action|
(changeNrProcessed ~~ changeNrShown) ifTrue:[
self changeSelection:changeNrProcessed
].
(skipSignal notNil) ifTrue:[
codeView highlightingErrorPosition:relPos to:relEndPos do:[
|box|
"
start dialog - make certain cleanup is done
"
box := OptionBox title:aString numberOfOptions:3.
box buttonTitles:#('abort' 'skip' 'continue').
box actions:(Array with:[action := #abort]
with:[action := #skip]
with:[action := #continue]).
box showAtPointer
].
action == #abort ifTrue:[
Object abortSignal raise.
^ false
].
action == #skip ifTrue:[
skipSignal raise.
^ false
].
^ false
].
codeView error:aString position:relPos to:relEndPos.
^ false
!
warning:aString position:relPos to:relEndPos
"compiler notifys us of a warning - ignore it"
^ self
!
error:aString position:relPos to:relEndPos
"compiler notifys us of an error - this should really not happen since
changes ought to be correct (did someone edit the changes file ??).
Show the bad change in the codeView and let codeView hilight the error"
|action|
(changeNrProcessed ~~ changeNrShown) ifTrue:[
self changeSelection:changeNrProcessed
].
(skipSignal notNil) ifTrue:[
codeView highlightingErrorPosition:relPos to:relEndPos do:[
|box|
"
start dialog - make certain cleanup is done
"
box := OptionBox title:aString numberOfOptions:3.
box buttonTitles:#('abort' 'skip' 'continue').
box actions:(Array with:[action := #abort]
with:[action := #skip]
with:[action := #continue]).
box showAtPointer
].
action == #abort ifTrue:[
Object abortSignal raise.
^ false
].
action == #skip ifTrue:[
skipSignal raise.
^ false
].
^ false
].
^ codeView error:aString position:relPos to:relEndPos
! !