diff --git a/apm/package.json b/apm/package.json index 336544d3e..b15e4a30f 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "1.18.10" + "atom-package-manager": "1.18.11" } } diff --git a/package.json b/package.json index fa50740de..5fbf1dbf7 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "background-tips": "0.27.1", "bookmarks": "0.45.0", "bracket-matcher": "0.88.0", - "command-palette": "0.42.1", + "command-palette": "0.43.0", "dalek": "0.2.1", "deprecation-cop": "0.56.9", "dev-live-reload": "0.48.1", @@ -121,7 +121,7 @@ "link": "0.31.4", "markdown-preview": "0.159.18", "metrics": "1.2.6", - "notifications": "0.69.2", + "notifications": "0.70.2", "open-on-github": "1.3.1", "package-generator": "1.3.0", "settings-view": "0.253.0", diff --git a/spec/atom-reporter.coffee b/spec/atom-reporter.coffee index 455afcb27..a522d9298 100644 --- a/spec/atom-reporter.coffee +++ b/spec/atom-reporter.coffee @@ -9,34 +9,41 @@ ipcHelpers = require '../src/ipc-helpers' formatStackTrace = (spec, message='', stackTrace) -> return stackTrace unless stackTrace + # at ... (.../jasmine.js:1:2) jasminePattern = /^\s*at\s+.*\(?.*[/\\]jasmine(-[^/\\]*)?\.js:\d+:\d+\)?\s*$/ - firstJasmineLinePattern = /^\s*at [/\\].*[/\\]jasmine(-[^/\\]*)?\.js:\d+:\d+\)?\s*$/ + # at jasmine.Something... (.../jasmine.js:1:2) + firstJasmineLinePattern = /^\s*at\s+jasmine\.[A-Z][^\s]*\s+\(?.*[/\\]jasmine(-[^/\\]*)?\.js:\d+:\d+\)?\s*$/ lines = [] for line in stackTrace.split('\n') - lines.push(line) unless jasminePattern.test(line) break if firstJasmineLinePattern.test(line) + lines.push(line) unless jasminePattern.test(line) # Remove first line of stack when it is the same as the error message errorMatch = lines[0]?.match(/^Error: (.*)/) lines.shift() if message.trim() is errorMatch?[1]?.trim() - for line, index in lines - # Remove prefix of lines matching: at jasmine.Spec. (path:1:2) - prefixMatch = line.match(/at jasmine\.Spec\. \(([^)]+)\)/) - line = "at #{prefixMatch[1]}" if prefixMatch + lines = lines.map (line) -> + # Only format actual stacktrace lines + if /^\s*at\s/.test(line) + # Needs to occur before path relativization + if process.platform is 'win32' and /file:\/\/\//.test(line) + # file:///C:/some/file -> C:\some\file + line = line.replace('file:///', '').replace(///#{path.posix.sep}///g, path.win32.sep) - # Relativize locations to spec directory - if process.platform is 'win32' - line = line.replace('file:///', '').replace(///#{path.posix.sep}///g, path.win32.sep) - line = line.replace("at #{spec.specDirectory}#{path.sep}", 'at ') - lines[index] = line.replace("(#{spec.specDirectory}#{path.sep}", '(') # at step (path:1:2) + line = line.trim() + # at jasmine.Spec. (path:1:2) -> at path:1:2 + .replace(/^at jasmine\.Spec\. \(([^)]+)\)/, 'at $1') + # at it (path:1:2) -> at path:1:2 + .replace(/^at f*it \(([^)]+)\)/, 'at $1') + # at spec/file-test.js -> at file-test.js + .replace(spec.specDirectory + path.sep, '') + + return line - lines = lines.map (line) -> line.trim() lines.join('\n').trim() module.exports = class AtomReporter - constructor: -> @element = document.createElement('div') @element.classList.add('spec-reporter-container') diff --git a/spec/text-editor-element-spec.js b/spec/text-editor-element-spec.js index 7c05aced4..b7181fa91 100644 --- a/spec/text-editor-element-spec.js +++ b/spec/text-editor-element-spec.js @@ -70,6 +70,19 @@ describe('TextEditorElement', () => { expect(element.getModel().isLineNumberGutterVisible()).toBe(false) }) + it("honors the 'readonly' attribute", async function() { + jasmineContent.innerHTML = "" + const element = jasmineContent.firstChild + + expect(element.getComponent().isInputEnabled()).toBe(false) + + element.removeAttribute('readonly') + expect(element.getComponent().isInputEnabled()).toBe(true) + + element.setAttribute('readonly', true) + expect(element.getComponent().isInputEnabled()).toBe(false) + }) + it('honors the text content', () => { jasmineContent.innerHTML = 'testing' const element = jasmineContent.firstChild diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index 89af72137..41a14f76a 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -86,6 +86,23 @@ describe('TextEditor', () => { }) }) + describe('when the editor is readonly', () => { + it('overrides TextBuffer.isModified to return false', async () => { + const editor = await atom.workspace.open(null, {readOnly: true}) + editor.setText('I am altering the buffer, pray I do not alter it any further') + expect(editor.isModified()).toBe(false) + editor.setReadOnly(false) + expect(editor.isModified()).toBe(true) + }) + it('clears the readonly status when saved', async () => { + const editor = await atom.workspace.open(null, {readOnly: true}) + editor.setText('I am altering the buffer, pray I do not alter it any further') + expect(editor.isReadOnly()).toBe(true) + await editor.saveAs(temp.openSync('was-readonly').path) + expect(editor.isReadOnly()).toBe(false) + }) + }) + describe('.copy()', () => { it('returns a different editor with the same initial state', () => { expect(editor.getAutoHeight()).toBeFalsy() diff --git a/spec/workspace-spec.js b/spec/workspace-spec.js index 83a4429b6..6bc3199ba 100644 --- a/spec/workspace-spec.js +++ b/spec/workspace-spec.js @@ -1,5 +1,7 @@ const path = require('path') const temp = require('temp').track() +const dedent = require('dedent') +const TextBuffer = require('text-buffer') const TextEditor = require('../src/text-editor') const Workspace = require('../src/workspace') const Project = require('../src/project') @@ -932,6 +934,18 @@ describe('Workspace', () => { }) }) }) + + describe('when opening an editor with a buffer that isn\'t part of the project', () => { + it('adds the buffer to the project', async () => { + const buffer = new TextBuffer() + const editor = new TextEditor({buffer}) + + await atom.workspace.open(editor) + + expect(atom.project.getBuffers().map(buffer => buffer.id)).toContain(buffer.id) + expect(buffer.getLanguageMode().getLanguageId()).toBe('text.plain.null-grammar') + }) + }) }) describe('finding items in the workspace', () => { @@ -1206,8 +1220,8 @@ describe('Workspace', () => { }) }) - describe('::onDidStopChangingActivePaneItem()', function () { - it('invokes observers when the active item of the active pane stops changing', function () { + describe('::onDidStopChangingActivePaneItem()', () => { + it('invokes observers when the active item of the active pane stops changing', () => { const pane1 = atom.workspace.getCenter().getActivePane() const pane2 = pane1.splitRight({items: [document.createElement('div'), document.createElement('div')]}); atom.workspace.getLeftDock().getActivePane().addItem(document.createElement('div')) @@ -1364,7 +1378,7 @@ describe('Workspace', () => { describe('::getActiveTextEditor()', () => { describe("when the workspace center's active pane item is a text editor", () => { - describe('when the workspace center has focus', function () { + describe('when the workspace center has focus', () => { it('returns the text editor', () => { const workspaceCenter = workspace.getCenter() const editor = new TextEditor() @@ -1375,7 +1389,7 @@ describe('Workspace', () => { }) }) - describe('when a dock has focus', function () { + describe('when a dock has focus', () => { it('returns the text editor', () => { const workspaceCenter = workspace.getCenter() const editor = new TextEditor() @@ -1536,11 +1550,10 @@ describe('Workspace', () => { waitsForPromise(() => atom.workspace.open('sample.coffee')) - runs(function () { - atom.workspace.getActiveTextEditor().setText(`\ -i = /test/; #FIXME\ -` - ) + runs(() => { + atom.workspace.getActiveTextEditor().setText(dedent ` + i = /test/; #FIXME\ + `) const atom2 = new AtomEnvironment({applicationDelegate: atom.applicationDelegate}) atom2.initialize({ @@ -2867,4 +2880,6 @@ i = /test/; #FIXME\ }) }) -const escapeStringRegex = str => str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') +function escapeStringRegex (string) { + return string.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') +} diff --git a/src/text-editor-component.js b/src/text-editor-component.js index da6ec452d..08af5ada1 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -56,7 +56,7 @@ class TextEditorComponent { this.props = props if (!props.model) { - props.model = new TextEditor({mini: props.mini}) + props.model = new TextEditor({mini: props.mini, readOnly: props.readOnly}) } this.props.model.component = this @@ -460,9 +460,13 @@ class TextEditorComponent { } } - let attributes = null + let attributes = {} if (model.isMini()) { - attributes = {mini: ''} + attributes.mini = '' + } + + if (!this.isInputEnabled()) { + attributes.readonly = '' } const dataset = {encoding: model.getEncoding()} @@ -819,7 +823,7 @@ class TextEditorComponent { const oldClassList = this.classList const newClassList = ['editor'] - if (this.focused) newClassList.push('is-focused') + if (this.focused && this.isInputEnabled()) newClassList.push('is-focused') if (model.isMini()) newClassList.push('mini') for (var i = 0; i < model.selections.length; i++) { if (!model.selections[i].isEmpty()) { @@ -2962,11 +2966,11 @@ class TextEditorComponent { } setInputEnabled (inputEnabled) { - this.props.inputEnabled = inputEnabled + this.props.model.update({readOnly: !inputEnabled}) } isInputEnabled (inputEnabled) { - return this.props.inputEnabled != null ? this.props.inputEnabled : true + return !this.props.model.isReadOnly() } getHiddenInput () { diff --git a/src/text-editor-element.js b/src/text-editor-element.js index d56c5596b..7218b7f05 100644 --- a/src/text-editor-element.js +++ b/src/text-editor-element.js @@ -59,6 +59,9 @@ class TextEditorElement extends HTMLElement { case 'gutter-hidden': this.getModel().update({lineNumberGutterVisible: newValue == null}) break + case 'readonly': + this.getModel().update({readOnly: newValue != null}) + break } } } @@ -275,7 +278,8 @@ class TextEditorElement extends HTMLElement { this.component = new TextEditorComponent({ element: this, mini: this.hasAttribute('mini'), - updatedSynchronously: this.updatedSynchronously + updatedSynchronously: this.updatedSynchronously, + readOnly: this.hasAttribute('readonly') }) this.updateModelFromAttributes() } diff --git a/src/text-editor.js b/src/text-editor.js index b3d0e592a..e24476b2d 100644 --- a/src/text-editor.js +++ b/src/text-editor.js @@ -124,6 +124,7 @@ class TextEditor { this.decorationManager = params.decorationManager this.selectionsMarkerLayer = params.selectionsMarkerLayer this.mini = (params.mini != null) ? params.mini : false + this.readOnly = (params.readOnly != null) ? params.readOnly : false this.placeholderText = params.placeholderText this.showLineNumbers = params.showLineNumbers this.assert = params.assert || (condition => condition) @@ -400,6 +401,16 @@ class TextEditor { } break + case 'readOnly': + if (value !== this.readOnly) { + this.readOnly = value + if (this.component != null) { + this.component.scheduleUpdate() + } + this.buffer.emitModifiedStatusChanged(this.isModified()) + } + break + case 'placeholderText': if (value !== this.placeholderText) { this.placeholderText = value @@ -530,6 +541,7 @@ class TextEditor { softWrapAtPreferredLineLength: this.softWrapAtPreferredLineLength, preferredLineLength: this.preferredLineLength, mini: this.mini, + readOnly: this.readOnly, editorWidthInChars: this.editorWidthInChars, width: this.width, maxScreenLineLength: this.maxScreenLineLength, @@ -556,6 +568,11 @@ class TextEditor { this.disposables.add(this.buffer.onDidChangeModified(() => { if (!this.hasTerminatedPendingState && this.buffer.isModified()) this.terminatePendingState() })) + this.disposables.add(this.buffer.onDidSave(() => { + if (this.isReadOnly()) { + this.setReadOnly(false) + } + })) } terminatePendingState () { @@ -965,6 +982,12 @@ class TextEditor { isMini () { return this.mini } + setReadOnly (readOnly) { + this.update({readOnly}) + } + + isReadOnly () { return this.readOnly } + onDidChangeMini (callback) { return this.emitter.on('did-change-mini', callback) } @@ -1106,7 +1129,7 @@ class TextEditor { setEncoding (encoding) { this.buffer.setEncoding(encoding) } // Essential: Returns {Boolean} `true` if this editor has been modified. - isModified () { return this.buffer.isModified() } + isModified () { return this.isReadOnly() ? false : this.buffer.isModified() } // Essential: Returns {Boolean} `true` if this editor has no content. isEmpty () { return this.buffer.isEmpty() } @@ -4552,8 +4575,7 @@ class TextEditor { ? minBlankIndentLevel : 0 - const tabLength = this.getTabLength() - const indentString = ' '.repeat(tabLength * minIndentLevel) + const indentString = this.buildIndentString(minIndentLevel) for (let row = start; row <= end; row++) { const line = this.buffer.lineForRow(row) if (NON_WHITESPACE_REGEXP.test(line)) { diff --git a/src/workspace.js b/src/workspace.js index 9f2ad397b..5e85401ef 100644 --- a/src/workspace.js +++ b/src/workspace.js @@ -497,6 +497,9 @@ module.exports = class Workspace extends Model { this.textEditorRegistry.maintainConfig(item), item.observeGrammar(this.handleGrammarUsed.bind(this)) ) + if (!this.project.findBufferForId(item.buffer.id)) { + this.project.addBuffer(item.buffer) + } item.onDidDestroy(() => { subscriptions.dispose() }) this.emitter.emit('did-add-text-editor', {textEditor: item, pane, index}) }