Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Enhance the mecanism that removes whitespaces on newline insertion #190

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 190 additions & 25 deletions lib/whitespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@ module.exports = class Whitespace {
constructor () {
this.watchedEditors = new WeakSet()
this.subscriptions = new CompositeDisposable()
/* This map is used to control cursors in multiple tab
The key is the TextEditor.id field, as it seems to be unique and stable
over buffer renaming
The value is an array of row positions.
When inserting text (whatever the text inserted), each row is checked
and cleaned. The set is cleared after each insertion
When moving the cursor, the old row position is stored
*/
this.mapOfOldCursorPositions = new Map()
/* This variable holds the status of the update process
When we are removing whitespaces lines, we don't want to process
onDidChange events generated by these removals
*/
this.currentlyRemovingWhitespacesLines = false
/* This variable is used to tell if during the last change we updated the
indentation or not. This is used to manage the history
*/
this.hasUpdatedIndentation = false
/* This variable is true when the text inserted is blank only */
this.insertedTextIsBlank = false

this.groupLastEventWasIndentUpdate = false

this.subscriptions.add(atom.workspace.observeTextEditors(editor => {
return this.handleEvents(editor)
Expand Down Expand Up @@ -69,12 +91,93 @@ module.exports = class Whitespace {
return this.subscriptions.dispose()
}

// Retrieve the right array for the current TextEditor
getCurrentEditorArray () {
let id = atom.workspace.getActiveTextEditor().id
let mapOld = this.mapOfOldCursorPositions
if (!mapOld.has(id)) {
mapOld.set(id, [])
}
return mapOld.get(id)
}

// Check that the row in the given buffer is eligible for being stored
// A row is considered eligible if it's not empty and if only blank filled
isBufferRowEligible (buffer, row) {
return buffer.lineLengthForRow(row) && buffer.isRowBlank(row)
}

// Store a row in the current array, if it doesn't already exist (set-like storage)
appendRowCurrentBuffer (row) {
let curArray = this.getCurrentEditorArray()
if (curArray.indexOf(row) === -1) {
curArray.push(row)
}
}

// This function actually cleans the rows that are only filled with blanks
// As it cleans the rows, it also clears the array storing the rows
updateAndClearCurrentBufferEmptyRows (buffer) {
// Flag set to true to tell other functions where the change event comes from
this.currentlyRemovingWhitespacesLines = true
// Group the changes into one history entity, so that several newlines can be
// undo/redo as one history event
this.hasUpdatedIndentation = 0
buffer.transact(() => {
let curArray = this.getCurrentEditorArray()
while (curArray.length) {
let v = curArray.pop()
if (this.isBufferRowEligible(buffer, v)) {
atom.workspace.getActiveTextEditor().setIndentationForBufferRow(v, 0)
this.hasUpdatedIndentation = 1
}
}
if (!this.hasUpdatedIndentation) {
buffer.abortTransaction()
}
})

// Unset the flag
this.currentlyRemovingWhitespacesLines = false
}

// This function updates the rows saved when lines are added
// from : row where the insertion happened
// altered : number or lines added/removed
// If a saved-row does not exist anymore after a deletion occurs, this function
// will remove it
updateSavedRows (from, altered) {
let curArray = this.getCurrentEditorArray()
let i = 0
while (i < curArray.length) {
if (curArray[i] > from) {
curArray[i] += altered
}
if (curArray[i] < 0) {
curArray.splice(i, 1)
} else {
++i
}
}
}

// Get the state of the ensureNoBlankLinesLeft configuration state
isNoBlankLinesLeftActivated (editor) {
return atom.config.get('whitespace.ensureNoBlankLinesLeft', {
scope: editor.getRootScopeDescriptor()
})
}

handleEvents (editor) {
if (this.watchedEditors.has(editor)) return
if (this.watchedEditors.has(editor)) {
return
}

let subArray = []

let buffer = editor.getBuffer()

let bufferSavedSubscription = buffer.onWillSave(() => {
subArray.push(buffer.onWillSave(() => {
return buffer.transact(() => {
let scopeDescriptor = editor.getRootScopeDescriptor()

Expand All @@ -88,43 +191,105 @@ module.exports = class Whitespace {
return this.ensureSingleTrailingNewline(editor)
}
})
})
}))

subArray.push(editor.onDidDestroy(event => {
// Get rid of informations corresponding to the recently closed editor
this.mapOfOldCursorPositions.delete(editor.id)
}))

let editorTextInsertedSubscription = editor.onDidInsertText(function (event) {
if (event.text !== '\n') {
subArray.push(buffer.onDidChange(event => {
if (!this.isNoBlankLinesLeftActivated(editor)) {
return
}

if (!buffer.isRowBlank(event.range.start.row)) {
// If this event happened during our update, discard it
if (this.currentlyRemovingWhitespacesLines) {
return
}

let scopeDescriptor = editor.getRootScopeDescriptor()
// Update the rows depending on the changes reported (insertion/deletion)
let altered = []

event.changes.forEach(val => {
const row = val.newRange.start.row
// row : the row where the change occured
// alt : final number of lines added/removed
altered.push({
row: row,
alt: (val.newRange.end.row - row) - (val.oldRange.end.row - val.oldRange.start.row)
})
})

if (atom.config.get('whitespace.removeTrailingWhitespace', {
scope: scopeDescriptor
})) {
if (!atom.config.get('whitespace.ignoreWhitespaceOnlyLines', {
scope: scopeDescriptor
})) {
return editor.setIndentationForBufferRow(event.range.start.row, 0)
// Keep the array sorted, so that we loop from the lowest row index
altered.sort((a, b) => {
if (a.row < b.row) {
return -1
}
return a.row > b.row
})

altered.forEach((val) => this.updateSavedRows(val.row, val.alt))

this.updateAndClearCurrentBufferEmptyRows(buffer)

if (this.hasUpdatedIndentation) {
if (this.groupLastEventWasIndentUpdate) {
buffer.groupLastChanges()
}
buffer.groupLastChanges()
}
this.groupLastEventWasIndentUpdate = this.hasUpdatedIndentation && this.insertedTextIsBlank
}))

subArray.push(editor.onWillInsertText(event => {
if (!this.isNoBlankLinesLeftActivated(editor)) {
return
}

// based on TextBuffer.rowIsBlank source : test if string is blank.
this.insertedTextIsBlank = /\s/.test(event.text)
/* if the text about to be inserted is blank only and don't contains newline, do not try to save the rows
Maybe we could implement a TextBuffer.stringIsBlank...
*/
if (this.insertedTextIsBlank && /[^\n]/.test(event.text)) {
return
}
editor.getCursorBufferPositions().forEach(pos => {
this.appendRowCurrentBuffer(pos.row)
})
}))

subArray.push(editor.onDidChangeCursorPosition(event => {
if (!this.isNoBlankLinesLeftActivated(editor)) {
return
}
})

let editorDestroyedSubscription = editor.onDidDestroy(() => {
bufferSavedSubscription.dispose()
editorTextInsertedSubscription.dispose()
editorDestroyedSubscription.dispose()
this.subscriptions.remove(bufferSavedSubscription)
this.subscriptions.remove(editorTextInsertedSubscription)
this.subscriptions.remove(editorDestroyedSubscription)
// If the text changed, discard (onDidChange will be called)
if (event.textChanged) {
return
}
// If we moved on the current line, discard
if (event.oldBufferPosition.row === event.newBufferPosition.row) {
return
}

this.hasUpdatedIndentation = 0
this.appendRowCurrentBuffer(event.oldBufferPosition.row)
}))

editor.onDidDestroy(() => {
subArray.forEach((sub) => {
sub.dispose()
this.subscriptions.remove(sub)
})
this.watchedEditors.delete(editor)
})

this.subscriptions.add(bufferSavedSubscription)
this.subscriptions.add(editorTextInsertedSubscription)
this.subscriptions.add(editorDestroyedSubscription)
subArray.forEach((sub) => {
this.subscriptions.add(sub)
})

this.watchedEditors.add(editor)
}

Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@
"type": "boolean",
"default": true,
"description": "If the buffer doesn't end with a newline character when it's saved, then append one. If it ends with more than one newline, remove all but one. To disable/enable for a certain language, use [syntax-scoped properties](https://github.com/atom/whitespace#readme) in your `config.cson`."
},
"ensureNoBlankLinesLeft": {
"type": "boolean",
"default": true,
"description": "When a cursor leaves a line with blanks only, remove the blanks. To disable/enable for a certain language, use [syntax-scoped properties](https://github.com/atom/whitespace#readme) in your `config.cson`."
}
}
}
Loading