Browse Source

Handle packages state: dirty/clean

Nicolas Petton 10 years ago
parent
commit
5f7d139e55

+ 7 - 5
css/helios.css

@@ -182,6 +182,7 @@ body[id="helios"] .navbar .nav > li > a {
   color: #444;
   text-shadow: 0 1px 0 #ddd;
   border-left: 1px solid #aaa;
+  border-right: 1px solid transparent;
 }
 body[id="helios"] .navbar .nav > li > a span {
   display: block;
@@ -191,17 +192,15 @@ body[id="helios"] .nav > li > a:hover {
   background: transparent;
 }
 body[id="helios"] .navbar .nav > .active + li a {
-  border-left: 0 none;
+  border-left: 1px solid transparent;
 }
 body[id="helios"] .navbar .nav > .active > a,
 body[id="helios"] .navbar .nav > .active > a:hover,
 body[id="helios"] .navbar .nav > .active > a:focus {
   border-left: 0 none;
   background-color: #bababa;
-  background-image: linear-gradient(left, #777777 0%, #bababa 2px, transparent 2px), linear-gradient(right, #777777 0%, #bababa 2px, transparent 2px);
-  background-image: -webkit-linear-gradient(left, #777777 0%, #bababa 2px, transparent 2px), -webkit-linear-gradient(right, #777777 0%, #bababa 2px, transparent 2px);
-  background-image: -moz-linear-gradient(left, #777777 0%, #bababa 2px, transparent 2px), -moz-linear-gradient(right, #777777 0%, #bababa 2px, transparent 2px);
-  background-image: -o-linear-gradient(left, #777777 0%, #bababa 2px, transparent 2px), -o-linear-gradient(right, #777777 0%, #bababa 2px, transparent 2px);
+  border-left: 1px solid #777;
+  border-right: 1px solid #777;
   text-shadow: #ddd 0px 1px 0px;
   color: #222;
 }
@@ -378,6 +377,9 @@ body[id="helios"] .tool_container .pane .nav-pills i.magnitude {
 body[id="helios"] .tool_container .pane .nav-pills i.package {
   background-image: url('../images/package.png');
 }
+body[id="helios"] .tool_container .pane .nav-pills i.package_dirty {
+  background-image: url('../images/package-dirty.png');
+}
 body[id="helios"] .tool_container .pane .nav-pills i.private {
   background-image: url('../images/private.png');
 }

+ 7 - 5
css/helios.less

@@ -216,6 +216,7 @@ body[id="helios"] {
 		color: #444;
 		text-shadow: 0 1px 0 #ddd;
 		border-left: 1px solid #aaa;
+		border-right: 1px solid transparent;
 
 		span {
 			display: block;
@@ -229,7 +230,7 @@ body[id="helios"] {
 
 	.navbar .nav > .active + li {
 		a {
-			border-left: 0 none;
+			border-left: 1px solid transparent;
 		}
 	}
 
@@ -238,10 +239,8 @@ body[id="helios"] {
 	.navbar .nav > .active > a:focus {
 		border-left: 0 none;
 		background-color: #bababa;
-		background-image: linear-gradient(left, #777 0%, #bababa 2px, transparent 2px), linear-gradient(right, #777 0%, #bababa 2px, transparent 2px);
-		background-image: -webkit-linear-gradient(left, #777 0%, #bababa 2px, transparent 2px), -webkit-linear-gradient(right, #777 0%, #bababa 2px, transparent 2px);
-		background-image: -moz-linear-gradient(left, #777 0%, #bababa 2px, transparent 2px), -moz-linear-gradient(right, #777 0%, #bababa 2px, transparent 2px);
-		background-image: -o-linear-gradient(left, #777 0%, #bababa 2px, transparent 2px), -o-linear-gradient(right, #777 0%, #bababa 2px, transparent 2px);
+		border-left: 1px solid #777;
+		border-right: 1px solid #777;
 		text-shadow: #ddd 0px 1px 0px;
 		color: #222;
 	}
@@ -443,6 +442,9 @@ body[id="helios"] {
 	.tool_container .pane .nav-pills i.package {
 		background-image: url('../images/package.png');
 	}
+	.tool_container .pane .nav-pills i.package_dirty {
+		background-image: url('../images/package-dirty.png');
+	}
 	.tool_container .pane .nav-pills i.private {
 		background-image: url('../images/private.png');
 	}

BIN
images/package-dirty.png


+ 49 - 11
src/Helios-Browser.js

@@ -3437,13 +3437,21 @@ smalltalk.addMethod(
 smalltalk.method({
 selector: "cssClassForItem:",
 protocol: 'accessing',
-fn: function (anItem) {
+fn: function (anItem){
 var self=this;
-return "package";
-},
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=_st(anItem)._isDirty();
+if(smalltalk.assert($2)){
+$1="package_dirty";
+} else {
+$1="package";
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"cssClassForItem:",{anItem:anItem},globals.HLPackagesListWidget)})},
 args: ["anItem"],
-source: "cssClassForItem: anItem\x09\x0a\x09^ 'package'",
-messageSends: [],
+source: "cssClassForItem: anItem\x09\x0a\x09^ anItem isDirty \x0a\x09\x09ifTrue: [ 'package_dirty' ]\x0a\x09\x09ifFalse: [ 'package' ]",
+messageSends: ["ifTrue:ifFalse:", "isDirty"],
 referencedClasses: []
 }),
 globals.HLPackagesListWidget);
@@ -3553,24 +3561,38 @@ smalltalk.addMethod(
 smalltalk.method({
 selector: "observeSystem",
 protocol: 'actions',
-fn: function () {
+fn: function (){
 var self=this;
 function $ClassAdded(){return globals.ClassAdded||(typeof ClassAdded=="undefined"?nil:ClassAdded)}
 function $PackageAdded(){return globals.PackageAdded||(typeof PackageAdded=="undefined"?nil:PackageAdded)}
+function $PackageClean(){return globals.PackageClean||(typeof PackageClean=="undefined"?nil:PackageClean)}
+function $PackageDirty(){return globals.PackageDirty||(typeof PackageDirty=="undefined"?nil:PackageDirty)}
 return smalltalk.withContext(function($ctx1) { 
-var $2,$1;
+var $2,$1,$4,$3,$6,$5;
 $2=self._model();
 $ctx1.sendIdx["model"]=1;
 $1=_st($2)._systemAnnouncer();
 $ctx1.sendIdx["systemAnnouncer"]=1;
 _st($1)._on_send_to_($ClassAdded(),"onClassAdded:",self);
 $ctx1.sendIdx["on:send:to:"]=1;
-_st(_st(self._model())._systemAnnouncer())._on_send_to_($PackageAdded(),"onPackageAdded:",self);
-return self}, function($ctx1) {$ctx1.fill(self,"observeSystem",{},globals.HLPackagesListWidget)});},
+$4=self._model();
+$ctx1.sendIdx["model"]=2;
+$3=_st($4)._systemAnnouncer();
+$ctx1.sendIdx["systemAnnouncer"]=2;
+_st($3)._on_send_to_($PackageAdded(),"onPackageAdded:",self);
+$ctx1.sendIdx["on:send:to:"]=2;
+$6=self._model();
+$ctx1.sendIdx["model"]=3;
+$5=_st($6)._systemAnnouncer();
+$ctx1.sendIdx["systemAnnouncer"]=3;
+_st($5)._on_send_to_($PackageClean(),"onPackageStateChanged",self);
+$ctx1.sendIdx["on:send:to:"]=3;
+_st(_st(self._model())._systemAnnouncer())._on_send_to_($PackageDirty(),"onPackageStateChanged",self);
+return self}, function($ctx1) {$ctx1.fill(self,"observeSystem",{},globals.HLPackagesListWidget)})},
 args: [],
-source: "observeSystem\x0a    self model systemAnnouncer \x0a\x09\x09on: ClassAdded \x0a\x09\x09send: #onClassAdded:\x0a\x09\x09to: self.\x0a\x09\x09\x0a\x09self model systemAnnouncer\x0a\x09\x09on: PackageAdded\x0a\x09\x09send: #onPackageAdded:\x0a\x09\x09to: self",
+source: "observeSystem\x0a    self model systemAnnouncer \x0a\x09\x09on: ClassAdded \x0a\x09\x09send: #onClassAdded:\x0a\x09\x09to: self.\x0a\x09\x09\x0a\x09self model systemAnnouncer\x0a\x09\x09on: PackageAdded\x0a\x09\x09send: #onPackageAdded:\x0a\x09\x09to: self.\x0a\x09\x09\x0a\x09self model systemAnnouncer\x0a\x09\x09on: PackageClean\x0a\x09\x09send: #onPackageStateChanged\x0a\x09\x09to: self.\x0a\x09\x09\x0a\x09self model systemAnnouncer\x0a\x09\x09on: PackageDirty\x0a\x09\x09send: #onPackageStateChanged\x0a\x09\x09to: self.\x0a\x09",
 messageSends: ["on:send:to:", "systemAnnouncer", "model"],
-referencedClasses: ["ClassAdded", "PackageAdded"]
+referencedClasses: ["ClassAdded", "PackageAdded", "PackageClean", "PackageDirty"]
 }),
 globals.HLPackagesListWidget);
 
@@ -3639,6 +3661,22 @@ referencedClasses: []
 }),
 globals.HLPackagesListWidget);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "onPackageStateChanged",
+protocol: 'reactions',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self._refresh();
+return self}, function($ctx1) {$ctx1.fill(self,"onPackageStateChanged",{},globals.HLPackagesListWidget)})},
+args: [],
+source: "onPackageStateChanged\x0a\x09self refresh",
+messageSends: ["refresh"],
+referencedClasses: []
+}),
+globals.HLPackagesListWidget);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "onPackagesFocusRequested",

+ 18 - 2
src/Helios-Browser.st

@@ -1202,7 +1202,9 @@ I render a list of the system packages.!
 !HLPackagesListWidget methodsFor: 'accessing'!
 
 cssClassForItem: anItem	
-	^ 'package'
+	^ anItem isDirty 
+		ifTrue: [ 'package_dirty' ]
+		ifFalse: [ 'package' ]
 !
 
 items
@@ -1239,7 +1241,17 @@ observeSystem
 	self model systemAnnouncer
 		on: PackageAdded
 		send: #onPackageAdded:
-		to: self
+		to: self.
+		
+	self model systemAnnouncer
+		on: PackageClean
+		send: #onPackageStateChanged
+		to: self.
+		
+	self model systemAnnouncer
+		on: PackageDirty
+		send: #onPackageStateChanged
+		to: self.
 !
 
 reselectItem: anItem
@@ -1287,6 +1299,10 @@ onPackageSelected: anAnnouncement
 			focus ]
 !
 
+onPackageStateChanged
+	self refresh
+!
+
 onPackagesFocusRequested
 	self focus
 ! !

+ 8 - 0
src/Kernel-Announcements.js

@@ -798,6 +798,14 @@ smalltalk.addClass('PackageAdded', globals.PackageAnnouncement, [], 'Kernel-Anno
 globals.PackageAdded.comment="I am emitted when a `Package` is added to the system.";
 
 
+smalltalk.addClass('PackageClean', globals.PackageAnnouncement, [], 'Kernel-Announcements');
+globals.PackageClean.comment="I am emitted when a package is committed and becomes clean.";
+
+
+smalltalk.addClass('PackageDirty', globals.PackageAnnouncement, [], 'Kernel-Announcements');
+globals.PackageDirty.comment="I am emitted when a package becomes dirty.";
+
+
 smalltalk.addClass('PackageRemoved', globals.PackageAnnouncement, [], 'Kernel-Announcements');
 globals.PackageRemoved.comment="I am emitted when a `Package` is removed from the system.";
 

+ 12 - 0
src/Kernel-Announcements.st

@@ -373,6 +373,18 @@ PackageAnnouncement subclass: #PackageAdded
 !PackageAdded commentStamp!
 I am emitted when a `Package` is added to the system.!
 
+PackageAnnouncement subclass: #PackageClean
+	instanceVariableNames: ''
+	package: 'Kernel-Announcements'!
+!PackageClean commentStamp!
+I am emitted when a package is committed and becomes clean.!
+
+PackageAnnouncement subclass: #PackageDirty
+	instanceVariableNames: ''
+	package: 'Kernel-Announcements'!
+!PackageDirty commentStamp!
+I am emitted when a package becomes dirty.!
+
 PackageAnnouncement subclass: #PackageRemoved
 	instanceVariableNames: ''
 	package: 'Kernel-Announcements'!

+ 7 - 3
src/Kernel-ImportExport.js

@@ -1533,12 +1533,16 @@ var self=this;
 return smalltalk.withContext(function($ctx1) { 
 self._commitJsFileFor_onSuccess_onError_(aPackage,(function(){
 return smalltalk.withContext(function($ctx2) {
-return self._commitStFileFor_onSuccess_onError_(aPackage,aBlock,anotherBlock);
+return self._commitStFileFor_onSuccess_onError_(aPackage,(function(){
+return smalltalk.withContext(function($ctx3) {
+_st(aPackage)._beClean();
+return _st(aBlock)._value();
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2,2)})}),anotherBlock);
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)})}),anotherBlock);
 return self}, function($ctx1) {$ctx1.fill(self,"commit:onSuccess:onError:",{aPackage:aPackage,aBlock:aBlock,anotherBlock:anotherBlock},globals.PackageHandler)})},
 args: ["aPackage", "aBlock", "anotherBlock"],
-source: "commit: aPackage onSuccess: aBlock onError: anotherBlock\x0a\x09self \x0a\x09\x09commitJsFileFor: aPackage \x0a\x09\x09onSuccess: [\x0a\x09\x09\x09self \x0a\x09\x09\x09commitStFileFor: aPackage \x0a\x09\x09\x09onSuccess: aBlock\x0a\x09\x09\x09onError: anotherBlock ] \x0a\x09\x09onError: anotherBlock",
-messageSends: ["commitJsFileFor:onSuccess:onError:", "commitStFileFor:onSuccess:onError:"],
+source: "commit: aPackage onSuccess: aBlock onError: anotherBlock\x0a\x09self \x0a\x09\x09commitJsFileFor: aPackage \x0a\x09\x09onSuccess: [\x0a\x09\x09\x09self \x0a\x09\x09\x09\x09commitStFileFor: aPackage \x0a\x09\x09\x09\x09onSuccess: [ aPackage beClean. aBlock value ]\x0a\x09\x09\x09\x09onError: anotherBlock ] \x0a\x09\x09onError: anotherBlock",
+messageSends: ["commitJsFileFor:onSuccess:onError:", "commitStFileFor:onSuccess:onError:", "beClean", "value"],
 referencedClasses: []
 }),
 globals.PackageHandler);

+ 3 - 3
src/Kernel-ImportExport.st

@@ -596,9 +596,9 @@ commit: aPackage onSuccess: aBlock onError: anotherBlock
 		commitJsFileFor: aPackage 
 		onSuccess: [
 			self 
-			commitStFileFor: aPackage 
-			onSuccess: aBlock
-			onError: anotherBlock ] 
+				commitStFileFor: aPackage 
+				onSuccess: [ aPackage beClean. aBlock value ]
+				onError: anotherBlock ] 
 		onError: anotherBlock
 !
 

+ 264 - 6
src/Kernel-Infrastructure.js

@@ -1469,12 +1469,28 @@ smalltalk.addClass('PackageOrganizer', globals.Organizer, [], 'Kernel-Infrastruc
 globals.PackageOrganizer.comment="I am an organizer specific to packages. I hold classes categorization information.";
 
 
-smalltalk.addClass('Package', globals.Object, ['transport'], 'Kernel-Infrastructure');
+smalltalk.addClass('Package', globals.Object, ['transport', 'dirty'], 'Kernel-Infrastructure');
 globals.Package.comment="I am similar to a \x22class category\x22 typically found in other Smalltalks like Pharo or Squeak. Amber does not have class categories anymore, it had in the beginning but now each class in the system knows which package it belongs to.\x0a\x0aEach package has a name and can be queried for its classes, but it will then resort to a reverse scan of all classes to find them.\x0a\x0a## API\x0a\x0aPackages are manipulated through \x22Smalltalk current\x22, like for example finding one based on a name or with `Package class >> #name` directly:\x0a\x0a    Smalltalk current packageAt: 'Kernel'\x0a    Package named: 'Kernel'\x0a\x0aA package differs slightly from a Monticello package which can span multiple class categories using a naming convention based on hyphenation. But just as in Monticello a package supports \x22class extensions\x22 so a package can define behaviors in foreign classes using a naming convention for method categories where the category starts with an asterisk and then the name of the owning package follows.\x0a\x0aYou can fetch a package from the server:\x0a\x0a\x09Package load: 'Additional-Examples'";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "basicName:",
+protocol: 'private',
+fn: function (aString){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self.pkgName = aString;
+return self}, function($ctx1) {$ctx1.fill(self,"basicName:",{aString:aString},globals.Package)})},
+args: ["aString"],
+source: "basicName: aString\x0a\x09<self.pkgName = aString>",
+messageSends: [],
+referencedClasses: []
+}),
+globals.Package);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "basicTransport",
-protocol: 'accessing',
+protocol: 'private',
 fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
@@ -1487,6 +1503,52 @@ referencedClasses: []
 }),
 globals.Package);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "beClean",
+protocol: 'accessing',
+fn: function (){
+var self=this;
+function $SystemAnnouncer(){return globals.SystemAnnouncer||(typeof SystemAnnouncer=="undefined"?nil:SystemAnnouncer)}
+function $PackageClean(){return globals.PackageClean||(typeof PackageClean=="undefined"?nil:PackageClean)}
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+self["@dirty"]=false;
+$1=_st($PackageClean())._new();
+_st($1)._package_(self);
+$2=_st($1)._yourself();
+_st(_st($SystemAnnouncer())._current())._announce_($2);
+return self}, function($ctx1) {$ctx1.fill(self,"beClean",{},globals.Package)})},
+args: [],
+source: "beClean\x0a\x09dirty := false.\x0a\x09\x0a\x09SystemAnnouncer current announce: (PackageClean new\x0a\x09\x09package: self;\x0a\x09\x09yourself)",
+messageSends: ["announce:", "current", "package:", "new", "yourself"],
+referencedClasses: ["SystemAnnouncer", "PackageClean"]
+}),
+globals.Package);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "beDirty",
+protocol: 'accessing',
+fn: function (){
+var self=this;
+function $SystemAnnouncer(){return globals.SystemAnnouncer||(typeof SystemAnnouncer=="undefined"?nil:SystemAnnouncer)}
+function $PackageClean(){return globals.PackageClean||(typeof PackageClean=="undefined"?nil:PackageClean)}
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+self["@dirty"]=true;
+$1=_st($PackageClean())._new();
+_st($1)._package_(self);
+$2=_st($1)._yourself();
+_st(_st($SystemAnnouncer())._current())._announce_($2);
+return self}, function($ctx1) {$ctx1.fill(self,"beDirty",{},globals.Package)})},
+args: [],
+source: "beDirty\x0a\x09dirty := true.\x0a\x09\x0a\x09SystemAnnouncer current announce: (PackageClean new\x0a\x09\x09package: self;\x0a\x09\x09yourself)",
+messageSends: ["announce:", "current", "package:", "new", "yourself"],
+referencedClasses: ["SystemAnnouncer", "PackageClean"]
+}),
+globals.Package);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "classTemplate",
@@ -1600,6 +1662,29 @@ referencedClasses: ["String"]
 }),
 globals.Package);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "isDirty",
+protocol: 'testing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@dirty"];
+if(($receiver = $2) == nil || $receiver == null){
+$1=false;
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"isDirty",{},globals.Package)})},
+args: [],
+source: "isDirty\x0a\x09^ dirty ifNil: [ false ]",
+messageSends: ["ifNil:"],
+referencedClasses: []
+}),
+globals.Package);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "isPackage",
@@ -1704,11 +1789,12 @@ protocol: 'accessing',
 fn: function (aString){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-self.pkgName = aString;
+self._basicName_(aString);
+self._beDirty();
 return self}, function($ctx1) {$ctx1.fill(self,"name:",{aString:aString},globals.Package)})},
 args: ["aString"],
-source: "name: aString\x0a\x09<self.pkgName = aString>",
-messageSends: [],
+source: "name: aString\x0a\x09self basicName: aString.\x0a\x09self beDirty",
+messageSends: ["basicName:", "beDirty"],
 referencedClasses: []
 }),
 globals.Package);
@@ -1959,6 +2045,178 @@ referencedClasses: ["ClassSorterNode", "Array"]
 globals.Package.klass);
 
 
+smalltalk.addClass('PackageStateObserver', globals.Object, [], 'Kernel-Infrastructure');
+globals.PackageStateObserver.comment="My current instance listens for any changes in the system that might affect the state of a package (being dirty).";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "announcer",
+protocol: 'accessing',
+fn: function (){
+var self=this;
+function $SystemAnnouncer(){return globals.SystemAnnouncer||(typeof SystemAnnouncer=="undefined"?nil:SystemAnnouncer)}
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st($SystemAnnouncer())._current();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"announcer",{},globals.PackageStateObserver)})},
+args: [],
+source: "announcer\x0a\x09^ SystemAnnouncer current",
+messageSends: ["current"],
+referencedClasses: ["SystemAnnouncer"]
+}),
+globals.PackageStateObserver);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "observeSystem",
+protocol: 'actions',
+fn: function (){
+var self=this;
+function $PackageAdded(){return globals.PackageAdded||(typeof PackageAdded=="undefined"?nil:PackageAdded)}
+function $ClassAnnouncement(){return globals.ClassAnnouncement||(typeof ClassAnnouncement=="undefined"?nil:ClassAnnouncement)}
+function $MethodAnnouncement(){return globals.MethodAnnouncement||(typeof MethodAnnouncement=="undefined"?nil:MethodAnnouncement)}
+function $ProtocolAnnouncement(){return globals.ProtocolAnnouncement||(typeof ProtocolAnnouncement=="undefined"?nil:ProtocolAnnouncement)}
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=self._announcer();
+_st($1)._on_send_to_($PackageAdded(),"onPackageAdded:",self);
+$ctx1.sendIdx["on:send:to:"]=1;
+_st($1)._on_send_to_($ClassAnnouncement(),"onClassModification:",self);
+$ctx1.sendIdx["on:send:to:"]=2;
+_st($1)._on_send_to_($MethodAnnouncement(),"onMethodModification:",self);
+$ctx1.sendIdx["on:send:to:"]=3;
+$2=_st($1)._on_send_to_($ProtocolAnnouncement(),"onProtocolModification:",self);
+return self}, function($ctx1) {$ctx1.fill(self,"observeSystem",{},globals.PackageStateObserver)})},
+args: [],
+source: "observeSystem\x0a\x09self announcer\x0a\x09\x09on: PackageAdded\x0a\x09\x09send: #onPackageAdded:\x0a\x09\x09to: self;\x0a\x09\x09\x0a\x09\x09on: ClassAnnouncement\x0a\x09\x09send: #onClassModification:\x0a\x09\x09to: self;\x0a\x09\x09\x0a\x09\x09on: MethodAnnouncement\x0a\x09\x09send: #onMethodModification:\x0a\x09\x09to: self;\x0a\x09\x09\x0a\x09\x09on: ProtocolAnnouncement\x0a\x09\x09send: #onProtocolModification:\x0a\x09\x09to: self",
+messageSends: ["on:send:to:", "announcer"],
+referencedClasses: ["PackageAdded", "ClassAnnouncement", "MethodAnnouncement", "ProtocolAnnouncement"]
+}),
+globals.PackageStateObserver);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "onClassModification:",
+protocol: 'reactions',
+fn: function (anAnnouncement){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(anAnnouncement)._theClass();
+if(($receiver = $1) == nil || $receiver == null){
+$1;
+} else {
+var theClass;
+theClass=$receiver;
+_st(_st(theClass)._package())._beDirty();
+};
+return self}, function($ctx1) {$ctx1.fill(self,"onClassModification:",{anAnnouncement:anAnnouncement},globals.PackageStateObserver)})},
+args: ["anAnnouncement"],
+source: "onClassModification: anAnnouncement\x0a\x09anAnnouncement theClass ifNotNil: [ :theClass |\x0a\x09\x09theClass package beDirty ]",
+messageSends: ["ifNotNil:", "theClass", "beDirty", "package"],
+referencedClasses: []
+}),
+globals.PackageStateObserver);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "onMethodModification:",
+protocol: 'reactions',
+fn: function (anAnnouncement){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(_st(anAnnouncement)._method())._package();
+if(($receiver = $1) == nil || $receiver == null){
+$1;
+} else {
+var package_;
+package_=$receiver;
+_st(package_)._beDirty();
+};
+return self}, function($ctx1) {$ctx1.fill(self,"onMethodModification:",{anAnnouncement:anAnnouncement},globals.PackageStateObserver)})},
+args: ["anAnnouncement"],
+source: "onMethodModification: anAnnouncement\x0a\x09anAnnouncement method package ifNotNil: [ :package | package beDirty ]",
+messageSends: ["ifNotNil:", "package", "method", "beDirty"],
+referencedClasses: []
+}),
+globals.PackageStateObserver);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "onPackageAdded:",
+protocol: 'reactions',
+fn: function (anAnnouncement){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(_st(anAnnouncement)._package())._beDirty();
+return self}, function($ctx1) {$ctx1.fill(self,"onPackageAdded:",{anAnnouncement:anAnnouncement},globals.PackageStateObserver)})},
+args: ["anAnnouncement"],
+source: "onPackageAdded: anAnnouncement\x0a\x09anAnnouncement package beDirty",
+messageSends: ["beDirty", "package"],
+referencedClasses: []
+}),
+globals.PackageStateObserver);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "onProtocolModification:",
+protocol: 'reactions',
+fn: function (anAnnouncement){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(_st(_st(anAnnouncement)._theClass())._package())._beDirty();
+return self}, function($ctx1) {$ctx1.fill(self,"onProtocolModification:",{anAnnouncement:anAnnouncement},globals.PackageStateObserver)})},
+args: ["anAnnouncement"],
+source: "onProtocolModification: anAnnouncement\x0a\x09anAnnouncement theClass package beDirty",
+messageSends: ["beDirty", "package", "theClass"],
+referencedClasses: []
+}),
+globals.PackageStateObserver);
+
+
+globals.PackageStateObserver.klass.iVarNames = ['current'];
+smalltalk.addMethod(
+smalltalk.method({
+selector: "current",
+protocol: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@current"];
+if(($receiver = $2) == nil || $receiver == null){
+self["@current"]=self._new();
+$1=self["@current"];
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"current",{},globals.PackageStateObserver.klass)})},
+args: [],
+source: "current\x0a\x09^ current ifNil: [ current := self new ]",
+messageSends: ["ifNil:", "new"],
+referencedClasses: []
+}),
+globals.PackageStateObserver.klass);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "initialize",
+protocol: 'initialization',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(self._current())._observeSystem();
+return self}, function($ctx1) {$ctx1.fill(self,"initialize",{},globals.PackageStateObserver.klass)})},
+args: [],
+source: "initialize\x0a\x09self current observeSystem",
+messageSends: ["observeSystem", "current"],
+referencedClasses: []
+}),
+globals.PackageStateObserver.klass);
+
+
 smalltalk.addClass('PlatformInterface', globals.Object, [], 'Kernel-Infrastructure');
 globals.PlatformInterface.comment="I am single entry point to UI and environment interface.\x0aMy `initialize` tries several options (for now, browser environment only) to set myself up.\x0a\x0a## API\x0a\x0a    PlatformInterface alert: 'Hey, there is a problem'.\x0a    PlatformInterface confirm: 'Affirmative?'.\x0a    PlatformInterface prompt: 'Your name:'.\x0a\x0a    PlatformInterface ajax: #{\x0a        'url' -> '/patch.js'. 'type' -> 'GET'. dataType->'script'\x0a    }.";
 
@@ -2786,7 +3044,7 @@ $3=package_;
 return $3;
 }, function($ctx1) {$ctx1.fill(self,"createPackage:",{packageName:packageName,package_:package_,announcement:announcement},globals.SmalltalkImage)})},
 args: ["packageName"],
-source: "createPackage: packageName\x0a\x09| package announcement |\x0a\x09\x0a\x09package := self basicCreatePackage: packageName.\x0a\x09announcement := PackageAdded new\x0a\x09\x09package: package;\x0a\x09\x09yourself.\x0a\x09\x09\x0a\x09SystemAnnouncer current announce: announcement.\x0a\x09\x0a\x09^ package",
+source: "createPackage: packageName\x0a\x09| package announcement |\x0a\x09\x0a\x09package := self basicCreatePackage: packageName.\x0a\x09\x0a\x09announcement := PackageAdded new\x0a\x09\x09package: package;\x0a\x09\x09yourself.\x0a\x09\x09\x0a\x09SystemAnnouncer current announce: announcement.\x0a\x09\x0a\x09^ package",
 messageSends: ["basicCreatePackage:", "package:", "new", "yourself", "announce:", "current"],
 referencedClasses: ["PackageAdded", "SystemAnnouncer"]
 }),

+ 99 - 5
src/Kernel-Infrastructure.st

@@ -541,7 +541,7 @@ Organizer subclass: #PackageOrganizer
 I am an organizer specific to packages. I hold classes categorization information.!
 
 Object subclass: #Package
-	instanceVariableNames: 'transport'
+	instanceVariableNames: 'transport dirty'
 	package: 'Kernel-Infrastructure'!
 !Package commentStamp!
 I am similar to a "class category" typically found in other Smalltalks like Pharo or Squeak. Amber does not have class categories anymore, it had in the beginning but now each class in the system knows which package it belongs to.
@@ -563,10 +563,20 @@ You can fetch a package from the server:
 
 !Package methodsFor: 'accessing'!
 
-basicTransport
-	"Answer the transport literal JavaScript object as setup in the JavaScript file, if any"
+beClean
+	dirty := false.
 	
-	<return self.transport>
+	SystemAnnouncer current announce: (PackageClean new
+		package: self;
+		yourself)
+!
+
+beDirty
+	dirty := true.
+	
+	SystemAnnouncer current announce: (PackageClean new
+		package: self;
+		yourself)
 !
 
 classTemplate
@@ -600,7 +610,8 @@ name
 !
 
 name: aString
-	<self.pkgName = aString>
+	self basicName: aString.
+	self beDirty
 !
 
 organization
@@ -673,8 +684,24 @@ printOn: aStream
 		nextPutAll: ')'
 ! !
 
+!Package methodsFor: 'private'!
+
+basicName: aString
+	<self.pkgName = aString>
+!
+
+basicTransport
+	"Answer the transport literal JavaScript object as setup in the JavaScript file, if any"
+	
+	<return self.transport>
+! !
+
 !Package methodsFor: 'testing'!
 
+isDirty
+	^ dirty ifNil: [ false ]
+!
+
 isPackage
 	^ true
 ! !
@@ -724,6 +751,72 @@ sortedClasses: classes
 	^ expandedClasses
 ! !
 
+Object subclass: #PackageStateObserver
+	instanceVariableNames: ''
+	package: 'Kernel-Infrastructure'!
+!PackageStateObserver commentStamp!
+My current instance listens for any changes in the system that might affect the state of a package (being dirty).!
+
+!PackageStateObserver methodsFor: 'accessing'!
+
+announcer
+	^ SystemAnnouncer current
+! !
+
+!PackageStateObserver methodsFor: 'actions'!
+
+observeSystem
+	self announcer
+		on: PackageAdded
+		send: #onPackageAdded:
+		to: self;
+		
+		on: ClassAnnouncement
+		send: #onClassModification:
+		to: self;
+		
+		on: MethodAnnouncement
+		send: #onMethodModification:
+		to: self;
+		
+		on: ProtocolAnnouncement
+		send: #onProtocolModification:
+		to: self
+! !
+
+!PackageStateObserver methodsFor: 'reactions'!
+
+onClassModification: anAnnouncement
+	anAnnouncement theClass ifNotNil: [ :theClass |
+		theClass package beDirty ]
+!
+
+onMethodModification: anAnnouncement
+	anAnnouncement method package ifNotNil: [ :package | package beDirty ]
+!
+
+onPackageAdded: anAnnouncement
+	anAnnouncement package beDirty
+!
+
+onProtocolModification: anAnnouncement
+	anAnnouncement theClass package beDirty
+! !
+
+PackageStateObserver class instanceVariableNames: 'current'!
+
+!PackageStateObserver class methodsFor: 'accessing'!
+
+current
+	^ current ifNil: [ current := self new ]
+! !
+
+!PackageStateObserver class methodsFor: 'initialization'!
+
+initialize
+	self current observeSystem
+! !
+
 Object subclass: #PlatformInterface
 	instanceVariableNames: ''
 	package: 'Kernel-Infrastructure'!
@@ -1172,6 +1265,7 @@ createPackage: packageName
 	| package announcement |
 	
 	package := self basicCreatePackage: packageName.
+	
 	announcement := PackageAdded new
 		package: package;
 		yourself.

+ 20 - 13
src/Kernel-Methods.js

@@ -705,26 +705,33 @@ fn: function (){
 var self=this;
 function $Package(){return globals.Package||(typeof Package=="undefined"?nil:Package)}
 return smalltalk.withContext(function($ctx1) { 
-var $2,$1,$4,$3,$5;
-$2=self._protocol();
-$ctx1.sendIdx["protocol"]=1;
-$1=_st($2)._beginsWith_("*");
-if(! smalltalk.assert($1)){
-$4=self._methodClass();
+var $1,$3,$2,$5,$4,$6;
+$1=self._methodClass();
 $ctx1.sendIdx["methodClass"]=1;
-$3=_st($4)._package();
+if(($receiver = $1) == nil || $receiver == null){
+return nil;
+} else {
+$1;
+};
+$3=self._protocol();
+$ctx1.sendIdx["protocol"]=1;
+$2=_st($3)._beginsWith_("*");
+if(! smalltalk.assert($2)){
+$5=self._methodClass();
+$ctx1.sendIdx["methodClass"]=2;
+$4=_st($5)._package();
 $ctx1.sendIdx["package"]=1;
-return $3;
+return $4;
 };
-$5=_st($Package())._named_ifAbsent_(_st(self._protocol())._allButFirst(),(function(){
+$6=_st($Package())._named_ifAbsent_(_st(self._protocol())._allButFirst(),(function(){
 return smalltalk.withContext(function($ctx2) {
 return _st(self._methodClass())._package();
-}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)})}));
-return $5;
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,3)})}));
+return $6;
 }, function($ctx1) {$ctx1.fill(self,"package",{},globals.CompiledMethod)})},
 args: [],
-source: "package\x0a\x09\x22Answer the package the receiver belongs to:\x0a\x09- if it is an extension method, answer the corresponding package\x0a\x09- else answer the `methodClass` package\x22\x0a\x09\x0a\x09(self protocol beginsWith: '*') ifFalse: [\x0a\x09\x09^ self methodClass package ].\x0a\x09\x09\x0a\x09^ Package \x0a\x09\x09named: self protocol allButFirst\x0a\x09\x09ifAbsent: [ self methodClass package ]",
-messageSends: ["ifFalse:", "beginsWith:", "protocol", "package", "methodClass", "named:ifAbsent:", "allButFirst"],
+source: "package\x0a\x09\x22Answer the package the receiver belongs to:\x0a\x09- if it is an extension method, answer the corresponding package\x0a\x09- else answer the `methodClass` package\x22\x0a\x09\x0a\x09self methodClass ifNil: [ ^ nil ].\x0a\x09\x0a\x09(self protocol beginsWith: '*') ifFalse: [\x0a\x09\x09^ self methodClass package ].\x0a\x09\x09\x0a\x09^ Package \x0a\x09\x09named: self protocol allButFirst\x0a\x09\x09ifAbsent: [ self methodClass package ]",
+messageSends: ["ifNil:", "methodClass", "ifFalse:", "beginsWith:", "protocol", "package", "named:ifAbsent:", "allButFirst"],
 referencedClasses: ["Package"]
 }),
 globals.CompiledMethod);

+ 2 - 0
src/Kernel-Methods.st

@@ -239,6 +239,8 @@ package
 	- if it is an extension method, answer the corresponding package
 	- else answer the `methodClass` package"
 	
+	self methodClass ifNil: [ ^ nil ].
+	
 	(self protocol beginsWith: '*') ifFalse: [
 		^ self methodClass package ].