Christoph Thiede uploaded a new version of Morphic to project The Inbox: http://source.squeak.org/inbox/Morphic-ct.2124.mcz
==================== Summary ====================
Name: Morphic-ct.2124 Author: ct Time: 19 August 2023, 9:12:41.163516 pm UUID: c1fe136b-c765-f04f-9d30-63c7391d2d53 Ancestors: Morphic-mt.2123
Proposal: Adds new update hook #insertTextReplacement to PluggableTextMorph. Models can use this to insert or append text to the morph without discarding unaccepted changes, the editing history, and, optionally, the selection and scroll position.
There are several use cases for this, including button-assisted construction of documents, remote text editing, and dynamically updated information as part of a text widget.
=============== Diff against Morphic-mt.2123 ===============
Item was added: + ----- Method: PluggableTextMorph>>insertText:from:to:invisibly: (in category 'transcript') ----- + insertText: newText from: start to: stop invisibly: invisibly + + | priorSelection priorHadUnacceptedEdits | + priorSelection := self selectionInterval. + self selectInvisiblyFrom: start to: stop. + + priorHadUnacceptedEdits := self hasUnacceptedEdits. + textMorph editor restoreEmphasisHereAfter: [ + textMorph editor setEmphasisHere. + self replaceSelectionWith: newText]. + + invisibly ifFalse: [^ self]. + + "cover all our tracks" + self hasUnacceptedEdits: priorHadUnacceptedEdits. + + "Restore previous selection. Also update it if behind insertion." + priorSelection start >= start ifTrue: [ + priorSelection := priorSelection start + newText size - (stop - start) - 1 to: priorSelection stop]. + priorSelection stop >= start ifTrue: [ + priorSelection := priorSelection start to: priorSelection stop + newText size - (stop - start) - 1]. + self selectInvisiblyFrom: priorSelection start to: priorSelection stop. + textMorph selectionChanged. + textMorph updateFromParagraph. + self rememberSelectionInterval. + + self flag: #todo. "Also preserve scroll position (which is NOT identical to the selection), so that insertions at the beginning do not interrupt the user further down in the text. This is rarely visible to the user, but should ultimately be done."!
Item was added: + ----- Method: PluggableTextMorph>>insertText:toReplace:invisibly: (in category 'transcript') ----- + insertText: newText toReplace: oldTextOrNil invisibly: invisibly + "if oldTextOrNil is nil, append the text to the end" + + | start | + ^ (oldTextOrNil notNil and: [ + (start := textMorph text findString: oldTextOrNil) > 0]) + ifFalse: [self insertText: newText from: textMorph text size + 1 to: textMorph text size invisibly: invisibly] + ifTrue: [self insertText: newText from: start to: start + oldTextOrNil size - 1 invisibly: invisibly]!
Item was changed: ----- Method: PluggableTextMorph>>update:with: (in category 'updating') ----- update: aSymbol with: arg1
aSymbol == #editString ifTrue: [ self editString: arg1. self hasUnacceptedEdits: true. ^ self].
(aSymbol == #inputRequested and: [self getTextSelector == arg1 or: [self setTextSelector == arg1]]) ifTrue: [ self activeHand newKeyboardFocus: self. ^ self].
aSymbol == #textStyle ifTrue: [ self setTextStyle: arg1. ^ self].
aSymbol == #font ifTrue: [ self setFont: arg1. ^ self].
+ (aSymbol == #insertTextReplacement and: [self getTextSelector == arg1 first]) + "Models can use this to insert or append text to the morph without discarding unaccepted changes, the editing history, and, optionally, the selection and scroll position (if invisibly is true). + Usage: + model changed: #insertTextReplacement with: {#contents. 'replacement'. 'original'. invisibly}." + ifTrue: [ + self insertText: arg1 second toReplace: (arg1 at: 3 ifAbsent: [nil]) invisibly: (arg1 at: 4 ifAbsent: [false])]. ^super update: aSymbol with: arg1!
Item was added: + ----- Method: TextEditor>>restoreEmphasisHereAfter: (in category 'accessing') ----- + restoreEmphasisHereAfter: aBlock + + | priorEmphasis | + priorEmphasis := emphasisHere. + ^ aBlock ensure: [ + emphasisHere := priorEmphasis]!
Attached is a simple example.
For my own (2) use cases so far, specifying a subtext to replace was fine. However, I can imagine that some other use cases would benefit from being able to specify an exact index (though this would require the model to track unaccepted changes carefully) or search the text for certain attributes autc. Now my question is whether we should add support for this as well or whether this would be speculative generality, given zero actual users of this.
Looking forward to your feedback!
Best, Christoph
DynamicTextModel + StringHolder subclass: #DynamicTextModel + instanceVariableNames: 'lastDateString' + classVariableNames: '' + poolDictionaries: '' + category: 'as yet unclassified' + + DynamicTextModel class + instanceVariableNames: '' + + ""
DynamicTextModel>>dateString {private} · ct 8/19/2023 21:06 + dateString + + ^ lastDateString := DateAndTime now asString
DynamicTextModel>>defaultContents {initialize-release} · ct 8/19/2023 21:05 + defaultContents + + ^ 'Date: {1}' format: {self dateString}
DynamicTextModel>>step {updating} · ct 8/19/2023 21:10 + step + + | old | + old := lastDateString. + self changed: #insertTextReplacement with: {#contents. self dateString. old. true}
DynamicTextModel>>stepTimeIn: {updating} · ct 8/19/2023 21:07 + stepTimeIn: aWindow + + ^ 1000
DynamicTextModel>>wantsSteps {updating} · ct 8/19/2023 21:07 + wantsSteps + + ^ true
--- Sent from Squeak Inbox Talk
On 2023-08-19T19:12:52+00:00, commits@source.squeak.org wrote:
Christoph Thiede uploaded a new version of Morphic to project The Inbox: http://source.squeak.org/inbox/Morphic-ct.2124.mcz
==================== Summary ====================
Name: Morphic-ct.2124 Author: ct Time: 19 August 2023, 9:12:41.163516 pm UUID: c1fe136b-c765-f04f-9d30-63c7391d2d53 Ancestors: Morphic-mt.2123
Proposal: Adds new update hook #insertTextReplacement to PluggableTextMorph. Models can use this to insert or append text to the morph without discarding unaccepted changes, the editing history, and, optionally, the selection and scroll position.
There are several use cases for this, including button-assisted construction of documents, remote text editing, and dynamically updated information as part of a text widget.
=============== Diff against Morphic-mt.2123 ===============
Item was added:
- ----- Method: PluggableTextMorph>>insertText:from:to:invisibly: (in category 'transcript') -----
- insertText: newText from: start to: stop invisibly: invisibly
- | priorSelection priorHadUnacceptedEdits |
- priorSelection := self selectionInterval.
- self selectInvisiblyFrom: start to: stop.
- priorHadUnacceptedEdits := self hasUnacceptedEdits.
- textMorph editor restoreEmphasisHereAfter: [
- textMorph editor setEmphasisHere.
- self replaceSelectionWith: newText].
- invisibly ifFalse: [^ self].
- "cover all our tracks"
- self hasUnacceptedEdits: priorHadUnacceptedEdits.
- "Restore previous selection. Also update it if behind insertion."
- priorSelection start >= start ifTrue: [
- priorSelection := priorSelection start + newText size - (stop - start) - 1 to: priorSelection stop].
- priorSelection stop >= start ifTrue: [
- priorSelection := priorSelection start to: priorSelection stop + newText size - (stop - start) - 1].
- self selectInvisiblyFrom: priorSelection start to: priorSelection stop.
- textMorph selectionChanged.
- textMorph updateFromParagraph.
- self rememberSelectionInterval.
- self flag: #todo. "Also preserve scroll position (which is NOT identical to the selection), so that insertions at the beginning do not interrupt the user further down in the text. This is rarely visible to the user, but should ultimately be done."!
Item was added:
- ----- Method: PluggableTextMorph>>insertText:toReplace:invisibly: (in category 'transcript') -----
- insertText: newText toReplace: oldTextOrNil invisibly: invisibly
- "if oldTextOrNil is nil, append the text to the end"
- | start |
- ^ (oldTextOrNil notNil and: [
- (start := textMorph text findString: oldTextOrNil) > 0])
- ifFalse: [self insertText: newText from: textMorph text size + 1 to: textMorph text size invisibly: invisibly]
- ifTrue: [self insertText: newText from: start to: start + oldTextOrNil size - 1 invisibly: invisibly]!
Item was changed: ----- Method: PluggableTextMorph>>update:with: (in category 'updating') ----- update: aSymbol with: arg1
aSymbol == #editString ifTrue: [ self editString: arg1. self hasUnacceptedEdits: true. ^ self].
(aSymbol == #inputRequested and: [self getTextSelector == arg1 or: [self setTextSelector == arg1]]) ifTrue: [ self activeHand newKeyboardFocus: self. ^ self].
aSymbol == #textStyle ifTrue: [ self setTextStyle: arg1. ^ self].
aSymbol == #font ifTrue: [ self setFont: arg1. ^ self].
- (aSymbol == #insertTextReplacement and: [self getTextSelector == arg1 first])
- "Models can use this to insert or append text to the morph without discarding unaccepted changes, the editing history, and, optionally, the selection and scroll position (if invisibly is true).
- Usage:
- model changed: #insertTextReplacement with: {#contents. 'replacement'. 'original'. invisibly}."
- ifTrue: [
- self insertText: arg1 second toReplace: (arg1 at: 3 ifAbsent: [nil]) invisibly: (arg1 at: 4 ifAbsent: [false])].
^super update: aSymbol with: arg1!
Item was added:
- ----- Method: TextEditor>>restoreEmphasisHereAfter: (in category 'accessing') -----
- restoreEmphasisHereAfter: aBlock
- | priorEmphasis |
- priorEmphasis := emphasisHere.
- ^ aBlock ensure: [
- emphasisHere := priorEmphasis]!
Hi Christoph --
I don't like the magic around #restoreEmphasisHereAfter: Could it be more explicit having most of it in TextEditor itself? The text seleciton is already stored in the current paragraph and thus could be rained from within the current editor. Even if the update-event arrives in the pluggable container.
TL;DR: You edit the text. Do it in TextEditor, not PluggableTextMorph.
Best, Marcel Am 19.08.2023 21:18:04 schrieb christoph.thiede@student.hpi.uni-potsdam.de christoph.thiede@student.hpi.uni-potsdam.de: Attached is a simple example.
For my own (2) use cases so far, specifying a subtext to replace was fine. However, I can imagine that some other use cases would benefit from being able to specify an exact index (though this would require the model to track unaccepted changes carefully) or search the text for certain attributes autc. Now my question is whether we should add support for this as well or whether this would be speculative generality, given zero actual users of this.
Looking forward to your feedback!
Best, Christoph
DynamicTextModel + StringHolder subclass: #DynamicTextModel + instanceVariableNames: 'lastDateString' + classVariableNames: '' + poolDictionaries: '' + category: 'as yet unclassified' + + DynamicTextModel class + instanceVariableNames: '' + + ""
DynamicTextModel>>dateString {private} · ct 8/19/2023 21:06 + dateString + + ^ lastDateString := DateAndTime now asString
DynamicTextModel>>defaultContents {initialize-release} · ct 8/19/2023 21:05 + defaultContents + + ^ 'Date: {1}' format: {self dateString}
DynamicTextModel>>step {updating} · ct 8/19/2023 21:10 + step + + | old | + old := lastDateString. + self changed: #insertTextReplacement with: {#contents. self dateString. old. true}
DynamicTextModel>>stepTimeIn: {updating} · ct 8/19/2023 21:07 + stepTimeIn: aWindow + + ^ 1000
DynamicTextModel>>wantsSteps {updating} · ct 8/19/2023 21:07 + wantsSteps + + ^ true
--- Sent from Squeak Inbox Talk [https://github.com/hpi-swa-lab/squeak-inbox-talk]
On 2023-08-19T19:12:52+00:00, commits@source.squeak.org wrote:
Christoph Thiede uploaded a new version of Morphic to project The Inbox: http://source.squeak.org/inbox/Morphic-ct.2124.mcz
==================== Summary ====================
Name: Morphic-ct.2124 Author: ct Time: 19 August 2023, 9:12:41.163516 pm UUID: c1fe136b-c765-f04f-9d30-63c7391d2d53 Ancestors: Morphic-mt.2123
Proposal: Adds new update hook #insertTextReplacement to PluggableTextMorph. Models can use this to insert or append text to the morph without discarding unaccepted changes, the editing history, and, optionally, the selection and scroll position.
There are several use cases for this, including button-assisted construction of documents, remote text editing, and dynamically updated information as part of a text widget.
=============== Diff against Morphic-mt.2123 ===============
Item was added:
- ----- Method: PluggableTextMorph>>insertText:from:to:invisibly: (in category 'transcript') -----
- insertText: newText from: start to: stop invisibly: invisibly
- | priorSelection priorHadUnacceptedEdits |
- priorSelection := self selectionInterval.
- self selectInvisiblyFrom: start to: stop.
- priorHadUnacceptedEdits := self hasUnacceptedEdits.
- textMorph editor restoreEmphasisHereAfter: [
- textMorph editor setEmphasisHere.
- self replaceSelectionWith: newText].
- invisibly ifFalse: [^ self].
- "cover all our tracks"
- self hasUnacceptedEdits: priorHadUnacceptedEdits.
- "Restore previous selection. Also update it if behind insertion."
- priorSelection start >= start ifTrue: [
- priorSelection := priorSelection start + newText size - (stop - start) - 1 to: priorSelection stop].
- priorSelection stop >= start ifTrue: [
- priorSelection := priorSelection start to: priorSelection stop + newText size - (stop - start) - 1].
- self selectInvisiblyFrom: priorSelection start to: priorSelection stop.
- textMorph selectionChanged.
- textMorph updateFromParagraph.
- self rememberSelectionInterval.
- self flag: #todo. "Also preserve scroll position (which is NOT identical to the selection), so that insertions at the beginning do not interrupt the user further down in the text. This is rarely visible to the user, but should ultimately be done."!
Item was added:
- ----- Method: PluggableTextMorph>>insertText:toReplace:invisibly: (in category 'transcript') -----
- insertText: newText toReplace: oldTextOrNil invisibly: invisibly
- "if oldTextOrNil is nil, append the text to the end"
- | start |
- ^ (oldTextOrNil notNil and: [
- (start := textMorph text findString: oldTextOrNil) > 0])
- ifFalse: [self insertText: newText from: textMorph text size + 1 to: textMorph text size invisibly: invisibly]
- ifTrue: [self insertText: newText from: start to: start + oldTextOrNil size - 1 invisibly: invisibly]!
Item was changed: ----- Method: PluggableTextMorph>>update:with: (in category 'updating') ----- update: aSymbol with: arg1
aSymbol == #editString ifTrue: [ self editString: arg1. self hasUnacceptedEdits: true. ^ self].
(aSymbol == #inputRequested and: [self getTextSelector == arg1 or: [self setTextSelector == arg1]]) ifTrue: [ self activeHand newKeyboardFocus: self. ^ self].
aSymbol == #textStyle ifTrue: [ self setTextStyle: arg1. ^ self].
aSymbol == #font ifTrue: [ self setFont: arg1. ^ self].
- (aSymbol == #insertTextReplacement and: [self getTextSelector == arg1 first])
- "Models can use this to insert or append text to the morph without discarding unaccepted changes, the editing history, and, optionally, the selection and scroll position (if invisibly is true).
- Usage:
- model changed: #insertTextReplacement with: {#contents. 'replacement'. 'original'. invisibly}."
- ifTrue: [
- self insertText: arg1 second toReplace: (arg1 at: 3 ifAbsent: [nil]) invisibly: (arg1 at: 4 ifAbsent: [false])].
^super update: aSymbol with: arg1!
Item was added:
- ----- Method: TextEditor>>restoreEmphasisHereAfter: (in category 'accessing') -----
- restoreEmphasisHereAfter: aBlock
- | priorEmphasis |
- priorEmphasis := emphasisHere.
- ^ aBlock ensure: [
- emphasisHere := priorEmphasis]!
Hi Marcel,
thanks for the feedback! Would you mind taking another look at Morphic-ct.2125? I also would appreciate your opinion on the design of the #update: selector (see my comment on speculative generality in my previous message). :-)
Best, Christoph
--- Sent from Squeak Inbox Talk
On 2023-08-21T12:50:50+02:00, marcel.taeumel@hpi.de wrote:
Hi Christoph --
I don't like the magic around #restoreEmphasisHereAfter: Could it be more explicit having most of it in TextEditor itself? The text seleciton is already stored in the current paragraph and thus could be rained from within the current editor. Even if the update-event arrives in the pluggable container.
TL;DR: You edit the text. Do it in TextEditor, not PluggableTextMorph.
Best, Marcel Am 19.08.2023 21:18:04 schrieb christoph.thiede(a)student.hpi.uni-potsdam.de <christoph.thiede(a)student.hpi.uni-potsdam.de>: Attached is a simple example.
For my own (2) use cases so far, specifying a subtext to replace was fine. However, I can imagine that some other use cases would benefit from being able to specify an exact index (though this would require the model to track unaccepted changes carefully) or search the text for certain attributes autc. Now my question is whether we should add support for this as well or whether this would be speculative generality, given zero actual users of this.
Looking forward to your feedback!
Best, Christoph
DynamicTextModel
- StringHolder subclass: #DynamicTextModel
- instanceVariableNames: 'lastDateString'
- classVariableNames: ''
- poolDictionaries: ''
- category: 'as yet unclassified'
- DynamicTextModel class
- instanceVariableNames: ''
- ""
DynamicTextModel>>dateString {private} · ct 8/19/2023 21:06
- dateString
- ^ lastDateString := DateAndTime now asString
DynamicTextModel>>defaultContents {initialize-release} · ct 8/19/2023 21:05
- defaultContents
- ^ 'Date: {1}' format: {self dateString}
DynamicTextModel>>step {updating} · ct 8/19/2023 21:10
- step
- | old |
- old := lastDateString.
- self changed: #insertTextReplacement with: {#contents. self dateString. old. true}
DynamicTextModel>>stepTimeIn: {updating} · ct 8/19/2023 21:07
- stepTimeIn: aWindow
- ^ 1000
DynamicTextModel>>wantsSteps {updating} · ct 8/19/2023 21:07
- wantsSteps
- ^ true
Sent from Squeak Inbox Talk [https://github.com/hpi-swa-lab/squeak-inbox-talk]
On 2023-08-19T19:12:52+00:00, commits(a)source.squeak.org wrote:
Christoph Thiede uploaded a new version of Morphic to project The Inbox: http://source.squeak.org/inbox/Morphic-ct.2124.mcz
==================== Summary ====================
Name: Morphic-ct.2124 Author: ct Time: 19 August 2023, 9:12:41.163516 pm UUID: c1fe136b-c765-f04f-9d30-63c7391d2d53 Ancestors: Morphic-mt.2123
Proposal: Adds new update hook #insertTextReplacement to PluggableTextMorph. Models can use this to insert or append text to the morph without discarding unaccepted changes, the editing history, and, optionally, the selection and scroll position.
There are several use cases for this, including button-assisted construction of documents, remote text editing, and dynamically updated information as part of a text widget.
=============== Diff against Morphic-mt.2123 ===============
Item was added:
- ----- Method: PluggableTextMorph>>insertText:from:to:invisibly: (in category 'transcript') -----
- insertText: newText from: start to: stop invisibly: invisibly
- | priorSelection priorHadUnacceptedEdits |
- priorSelection := self selectionInterval.
- self selectInvisiblyFrom: start to: stop.
- priorHadUnacceptedEdits := self hasUnacceptedEdits.
- textMorph editor restoreEmphasisHereAfter: [
- textMorph editor setEmphasisHere.
- self replaceSelectionWith: newText].
- invisibly ifFalse: [^ self].
- "cover all our tracks"
- self hasUnacceptedEdits: priorHadUnacceptedEdits.
- "Restore previous selection. Also update it if behind insertion."
- priorSelection start >= start ifTrue: [
- priorSelection := priorSelection start + newText size - (stop - start) - 1 to: priorSelection stop].
- priorSelection stop >= start ifTrue: [
- priorSelection := priorSelection start to: priorSelection stop + newText size - (stop - start) - 1].
- self selectInvisiblyFrom: priorSelection start to: priorSelection stop.
- textMorph selectionChanged.
- textMorph updateFromParagraph.
- self rememberSelectionInterval.
- self flag: #todo. "Also preserve scroll position (which is NOT identical to the selection), so that insertions at the beginning do not interrupt the user further down in the text. This is rarely visible to the user, but should ultimately be done."!
Item was added:
- ----- Method: PluggableTextMorph>>insertText:toReplace:invisibly: (in category 'transcript') -----
- insertText: newText toReplace: oldTextOrNil invisibly: invisibly
- "if oldTextOrNil is nil, append the text to the end"
- | start |
- ^ (oldTextOrNil notNil and: [
- (start := textMorph text findString: oldTextOrNil) > 0])
- ifFalse: [self insertText: newText from: textMorph text size + 1 to: textMorph text size invisibly: invisibly]
- ifTrue: [self insertText: newText from: start to: start + oldTextOrNil size - 1 invisibly: invisibly]!
Item was changed: ----- Method: PluggableTextMorph>>update:with: (in category 'updating') ----- update: aSymbol with: arg1
aSymbol == #editString ifTrue: [ self editString: arg1. self hasUnacceptedEdits: true. ^ self].
(aSymbol == #inputRequested and: [self getTextSelector == arg1 or: [self setTextSelector == arg1]]) ifTrue: [ self activeHand newKeyboardFocus: self. ^ self].
aSymbol == #textStyle ifTrue: [ self setTextStyle: arg1. ^ self].
aSymbol == #font ifTrue: [ self setFont: arg1. ^ self].
- (aSymbol == #insertTextReplacement and: [self getTextSelector == arg1 first])
- "Models can use this to insert or append text to the morph without discarding unaccepted changes, the editing history, and, optionally, the selection and scroll position (if invisibly is true).
- Usage:
- model changed: #insertTextReplacement with: {#contents. 'replacement'. 'original'. invisibly}."
- ifTrue: [
- self insertText: arg1 second toReplace: (arg1 at: 3 ifAbsent: [nil]) invisibly: (arg1 at: 4 ifAbsent: [false])].
^super update: aSymbol with: arg1!
Item was added:
- ----- Method: TextEditor>>restoreEmphasisHereAfter: (in category 'accessing') -----
- restoreEmphasisHereAfter: aBlock
- | priorEmphasis |
- priorEmphasis := emphasisHere.
- ^ aBlock ensure: [
- emphasisHere := priorEmphasis]!
squeak-dev@lists.squeakfoundation.org