Merge branch 'master' into mb-tree-sitter-parsers

This commit is contained in:
Max Brunsfeld
2017-12-06 14:56:09 -08:00
10 changed files with 121 additions and 36 deletions

View File

@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "1.18.10"
"atom-package-manager": "1.18.11"
}
}

View File

@@ -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",

View File

@@ -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.<anonymous> (path:1:2)
prefixMatch = line.match(/at jasmine\.Spec\.<anonymous> \(([^)]+)\)/)
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.<anonymous> (path:1:2) -> at path:1:2
.replace(/^at jasmine\.Spec\.<anonymous> \(([^)]+)\)/, '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')

View File

@@ -70,6 +70,19 @@ describe('TextEditorElement', () => {
expect(element.getModel().isLineNumberGutterVisible()).toBe(false)
})
it("honors the 'readonly' attribute", async function() {
jasmineContent.innerHTML = "<atom-text-editor readonly>"
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 = '<atom-text-editor>testing</atom-text-editor>'
const element = jasmineContent.firstChild

View File

@@ -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()

View File

@@ -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, '\\$&')
}

View File

@@ -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 () {

View File

@@ -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()
}

View File

@@ -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)) {

View File

@@ -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})
}