├── .gitattributes ├── .github ├── move.yml └── no-response.yml ├── .gitignore ├── .travis.yml ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── appveyor.yml ├── lib ├── helpers.js ├── main.js └── status-bar-item.js ├── package.json └── spec ├── fixtures ├── mixed-endings.md ├── unix-endings.md └── windows-endings.md └── line-ending-selector-spec.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Do not autoconvert line endings for these test files, they are not supposed 2 | # to be native to your platform. 3 | spec/fixtures/*.md -text 4 | -------------------------------------------------------------------------------- /.github/move.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atom/line-ending-selector/cbadbc9d020f2a3637a350e1759f05a6f18d3da0/.github/move.yml -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an issue is closed for lack of response 4 | daysUntilClose: 28 5 | 6 | # Label requiring a response 7 | responseRequiredLabel: more-information-needed 8 | 9 | # Comment to post when closing an issue for lack of response. Set to `false` to disable. 10 | closeComment: > 11 | This issue has been automatically closed because there has been no response 12 | to our request for more information from the original author. With only the 13 | information that is currently in the issue, we don't have enough information 14 | to take action. Please reach out if you have or find the answers we need so 15 | that we can investigate further. 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | notifications: 4 | email: 5 | on_success: never 6 | on_failure: change 7 | 8 | script: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' 9 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ### Prerequisites 10 | 11 | * [ ] Put an X between the brackets on this line if you have done all of the following: 12 | * Reproduced the problem in Safe Mode: http://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode 13 | * Followed all applicable steps in the debugging guide: http://flight-manual.atom.io/hacking-atom/sections/debugging/ 14 | * Checked the FAQs on the message board for common solutions: https://discuss.atom.io/c/faq 15 | * Checked that your issue isn't already filed: https://github.com/issues?utf8=✓&q=is%3Aissue+user%3Aatom 16 | * Checked that there is not already an Atom package that provides the described functionality: https://atom.io/packages 17 | 18 | ### Description 19 | 20 | [Description of the issue] 21 | 22 | ### Steps to Reproduce 23 | 24 | 1. [First Step] 25 | 2. [Second Step] 26 | 3. [and so on...] 27 | 28 | **Expected behavior:** [What you expect to happen] 29 | 30 | **Actual behavior:** [What actually happens] 31 | 32 | **Reproduces how often:** [What percentage of the time does it reproduce?] 33 | 34 | ### Versions 35 | 36 | You can get this information from copy and pasting the output of `atom --version` and `apm --version` from the command line. Also, please include the OS and what version of the OS you're running. 37 | 38 | ### Additional Information 39 | 40 | Any additional information, configuration or data that might be necessary to reproduce the issue. 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Requirements 2 | 3 | * Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. 4 | * All new code requires tests to ensure against regressions 5 | 6 | ### Description of the Change 7 | 8 | 13 | 14 | ### Alternate Designs 15 | 16 | 17 | 18 | ### Benefits 19 | 20 | 21 | 22 | ### Possible Drawbacks 23 | 24 | 25 | 26 | ### Applicable Issues 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### This package is now a part of the [core Atom repository](https://github.com/atom/atom/tree/master/packages/line-ending-selector). Please direct all issues and pull requests there in the future! 2 | 3 | --- 4 | 5 | # Line Ending Selector package 6 | [![OS X Build Status](https://travis-ci.org/atom/line-ending-selector.svg?branch=master)](https://travis-ci.org/atom/line-ending-selector) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/b3743n9ojomlpn1g/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/line-ending-selector/branch/master) [![Dependency Status](https://david-dm.org/atom/line-ending-selector.svg)](https://david-dm.org/atom/line-ending-selector) 7 | 8 | ![status bar tile](https://cloud.githubusercontent.com/assets/1305617/9274149/6b317568-4293-11e5-83ba-614a6c0d9890.png) 9 | 10 | This is an [Atom](https://atom.io) package that displays the current line ending type of a file: `CRLF` (Windows), `LF` (Unix), or `Mixed` (both). It also lets you change the line ending of a file. 11 | 12 | ## To Use 13 | 14 | When the package is activated it will show the current line ending of the file in the right side of the status-bar. If a new file is created the line ending will start with the system default: `CRLF` for Windows, `LF` for Mac and Linux, and `CR` for old-style Mac files. If a file contains multiple line-ending types it will display `Mixed`. 15 | 16 | ### Changing a File's Line Ending 17 | 18 | You can click the line ending in the status-bar to open a modal with the line ending options. Selecting a different line ending will change each line of the file in the active editor. 19 | 20 | ![modal](https://cloud.githubusercontent.com/assets/1305617/9273907/2be5c136-4291-11e5-94af-65ece408eb12.png) 21 | 22 | **Line Endings** 23 | 24 | - `LF` is "\n" 25 | - `CRLF` is "\r\n" 26 | 27 | **Note:** Because the `CR` line ending style is not used in any modern operating system, this package only supports converting *from* `CR` line endings not to it. 28 | 29 | ### Atom Commands 30 | 31 | You can also change a file's line endings by using or cmd-shift-P searching for these commands: 32 | 33 | ```text 34 | line-ending-selector:convert-to-LF 35 | line-ending-selector:convert-to-CRLF 36 | ``` 37 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | platform: x64 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | clone_depth: 10 10 | 11 | skip_tags: true 12 | 13 | environment: 14 | APM_TEST_PACKAGES: 15 | 16 | matrix: 17 | - ATOM_CHANNEL: stable 18 | - ATOM_CHANNEL: beta 19 | 20 | install: 21 | - ps: Install-Product node 4 22 | 23 | build_script: 24 | - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/atom/ci/master/build-package.ps1')) 25 | 26 | test: off 27 | deploy: off 28 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | 3 | export default { 4 | getProcessPlatform () { 5 | return process.platform 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | 3 | import _ from 'underscore-plus' 4 | import {CompositeDisposable, Disposable} from 'atom' 5 | import SelectListView from 'atom-select-list' 6 | import StatusBarItem from './status-bar-item' 7 | import helpers from './helpers' 8 | 9 | const LineEndingRegExp = /\r\n|\n/g 10 | const LFRegExp = /(\A|[^\r])\n/g 11 | const CRLFRegExp = /\r\n/g 12 | 13 | let disposables = null 14 | let modalPanel = null 15 | let lineEndingListView = null 16 | 17 | export function activate () { 18 | disposables = new CompositeDisposable() 19 | 20 | disposables.add(atom.commands.add('atom-text-editor', { 21 | 'line-ending-selector:show': (event) => { 22 | if (!modalPanel) { 23 | lineEndingListView = new SelectListView({ 24 | items: [{name: 'LF', value: '\n'}, {name: 'CRLF', value: '\r\n'}], 25 | filterKeyForItem: (lineEnding) => lineEnding.name, 26 | didConfirmSelection: (lineEnding) => { 27 | setLineEnding(atom.workspace.getActiveTextEditor(), lineEnding.value) 28 | modalPanel.hide() 29 | }, 30 | didCancelSelection: () => { 31 | modalPanel.hide() 32 | }, 33 | elementForItem: (lineEnding) => { 34 | const element = document.createElement('li') 35 | element.textContent = lineEnding.name 36 | return element 37 | } 38 | }) 39 | modalPanel = atom.workspace.addModalPanel({item: lineEndingListView}) 40 | disposables.add(new Disposable(() => { 41 | lineEndingListView.destroy() 42 | modalPanel.destroy() 43 | modalPanel = null 44 | })) 45 | } 46 | 47 | lineEndingListView.reset() 48 | modalPanel.show() 49 | lineEndingListView.focus() 50 | }, 51 | 52 | 'line-ending-selector:convert-to-LF': (event) => { 53 | const editorElement = event.target.closest('atom-text-editor') 54 | setLineEnding(editorElement.getModel(), '\n') 55 | }, 56 | 57 | 'line-ending-selector:convert-to-CRLF': (event) => { 58 | const editorElement = event.target.closest('atom-text-editor') 59 | setLineEnding(editorElement.getModel(), '\r\n') 60 | } 61 | })) 62 | } 63 | 64 | export function deactivate () { 65 | disposables.dispose() 66 | } 67 | 68 | export function consumeStatusBar (statusBar) { 69 | let statusBarItem = new StatusBarItem() 70 | let currentBufferDisposable = null 71 | let tooltipDisposable = null 72 | 73 | const updateTile = _.debounce((buffer) => { 74 | getLineEndings(buffer).then((lineEndings) => { 75 | if (lineEndings.size === 0) { 76 | let defaultLineEnding = getDefaultLineEnding() 77 | buffer.setPreferredLineEnding(defaultLineEnding) 78 | lineEndings = new Set().add(defaultLineEnding) 79 | } 80 | statusBarItem.setLineEndings(lineEndings) 81 | }) 82 | }, 0) 83 | 84 | disposables.add(atom.workspace.observeActiveTextEditor((editor) => { 85 | if (currentBufferDisposable) currentBufferDisposable.dispose() 86 | 87 | if (editor && editor.getBuffer) { 88 | let buffer = editor.getBuffer() 89 | updateTile(buffer) 90 | currentBufferDisposable = buffer.onDidChange(({oldText, newText}) => { 91 | if (!statusBarItem.hasLineEnding('\n')) { 92 | if (newText.indexOf('\n') >= 0) { 93 | updateTile(buffer) 94 | } 95 | } else if (!statusBarItem.hasLineEnding('\r\n')) { 96 | if (newText.indexOf('\r\n') >= 0) { 97 | updateTile(buffer) 98 | } 99 | } else if (oldText.indexOf('\n')) { 100 | updateTile(buffer) 101 | } 102 | }) 103 | } else { 104 | statusBarItem.setLineEndings(new Set()) 105 | currentBufferDisposable = null 106 | } 107 | 108 | if (tooltipDisposable) { 109 | disposables.remove(tooltipDisposable) 110 | tooltipDisposable.dispose() 111 | } 112 | tooltipDisposable = atom.tooltips.add(statusBarItem.element, { 113 | title () { 114 | return `File uses ${statusBarItem.description()} line endings` 115 | } 116 | }) 117 | disposables.add(tooltipDisposable) 118 | })) 119 | 120 | disposables.add(new Disposable(() => { 121 | if (currentBufferDisposable) currentBufferDisposable.dispose() 122 | })) 123 | 124 | statusBarItem.onClick(() => { 125 | const editor = atom.workspace.getActiveTextEditor() 126 | atom.commands.dispatch( 127 | atom.views.getView(editor), 128 | 'line-ending-selector:show' 129 | ) 130 | }) 131 | 132 | let tile = statusBar.addRightTile({item: statusBarItem, priority: 200}) 133 | disposables.add(new Disposable(() => tile.destroy())) 134 | } 135 | 136 | function getDefaultLineEnding () { 137 | switch (atom.config.get('line-ending-selector.defaultLineEnding')) { 138 | case 'LF': 139 | return '\n' 140 | case 'CRLF': 141 | return '\r\n' 142 | case 'OS Default': 143 | default: 144 | return (helpers.getProcessPlatform() === 'win32') ? '\r\n' : '\n' 145 | } 146 | } 147 | 148 | function getLineEndings (buffer) { 149 | if (typeof buffer.find === 'function') { 150 | return Promise.all([ 151 | buffer.find(LFRegExp), 152 | buffer.find(CRLFRegExp) 153 | ]).then(([hasLF, hasCRLF]) => { 154 | const result = new Set() 155 | if (hasLF) result.add('\n') 156 | if (hasCRLF) result.add('\r\n') 157 | return result 158 | }) 159 | } else { 160 | return new Promise((resolve) => { 161 | const result = new Set() 162 | for (let i = 0; i < buffer.getLineCount() - 1; i++) { 163 | result.add(buffer.lineEndingForRow(i)) 164 | } 165 | resolve(result) 166 | }) 167 | } 168 | } 169 | 170 | function setLineEnding (item, lineEnding) { 171 | if (item && item.getBuffer) { 172 | let buffer = item.getBuffer() 173 | buffer.setPreferredLineEnding(lineEnding) 174 | buffer.setText(buffer.getText().replace(LineEndingRegExp, lineEnding)) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /lib/status-bar-item.js: -------------------------------------------------------------------------------- 1 | const {Emitter} = require('atom') 2 | 3 | module.exports = 4 | class StatusBarItem { 5 | constructor () { 6 | this.element = document.createElement('a') 7 | this.element.className = 'line-ending-tile inline-block' 8 | this.emitter = new Emitter() 9 | this.setLineEndings(new Set()) 10 | } 11 | 12 | setLineEndings (lineEndings) { 13 | this.lineEndings = lineEndings 14 | this.element.textContent = lineEndingName(lineEndings) 15 | this.emitter.emit('did-change') 16 | } 17 | 18 | onDidChange (callback) { 19 | return this.emitter.on('did-change', callback) 20 | } 21 | 22 | hasLineEnding (lineEnding) { 23 | return this.lineEndings.has(lineEnding) 24 | } 25 | 26 | description () { 27 | return lineEndingDescription(this.lineEndings) 28 | } 29 | 30 | onClick (callback) { 31 | this.element.addEventListener('click', callback) 32 | } 33 | } 34 | 35 | function lineEndingName (lineEndings) { 36 | if (lineEndings.size > 1) { 37 | return 'Mixed' 38 | } else if (lineEndings.has('\n')) { 39 | return 'LF' 40 | } else if (lineEndings.has('\r\n')) { 41 | return 'CRLF' 42 | } else { 43 | return '' 44 | } 45 | } 46 | 47 | function lineEndingDescription (lineEndings) { 48 | switch (lineEndingName(lineEndings)) { 49 | case 'Mixed': return 'mixed' 50 | case 'LF': return 'LF (Unix)' 51 | case 'CRLF': return 'CRLF (Windows)' 52 | default: return 'unknown' 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-ending-selector", 3 | "version": "0.7.7", 4 | "main": "./lib/main", 5 | "description": "Select the line ending to use for the current editor.", 6 | "license": "MIT", 7 | "repository": "https://github.com/atom/line-ending-selector", 8 | "engines": { 9 | "atom": "^1.0.0" 10 | }, 11 | "dependencies": { 12 | "atom-select-list": "^0.7.0", 13 | "underscore-plus": "^1.6.6" 14 | }, 15 | "consumedServices": { 16 | "status-bar": { 17 | "versions": { 18 | "^1.0.0": "consumeStatusBar" 19 | } 20 | } 21 | }, 22 | "devDependencies": { 23 | "standard": "^5.1.0" 24 | }, 25 | "configSchema": { 26 | "defaultLineEnding": { 27 | "title": "Default line ending", 28 | "description": "Line ending to use for new files", 29 | "type": "string", 30 | "default": "OS Default", 31 | "enum": [ 32 | "OS Default", 33 | "LF", 34 | "CRLF" 35 | ] 36 | } 37 | }, 38 | "standard": { 39 | "globals": [ 40 | "advanceClock", 41 | "atom", 42 | "beforeEach", 43 | "expect", 44 | "describe", 45 | "it", 46 | "jasmine", 47 | "MouseEvent", 48 | "runs", 49 | "spyOn", 50 | "waits", 51 | "waitsFor", 52 | "waitsForPromise" 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /spec/fixtures/mixed-endings.md: -------------------------------------------------------------------------------- 1 | Hello 2 | Goodbye 3 | Mixed 4 | -------------------------------------------------------------------------------- /spec/fixtures/unix-endings.md: -------------------------------------------------------------------------------- 1 | Hello 2 | Goodbye 3 | Unix 4 | -------------------------------------------------------------------------------- /spec/fixtures/windows-endings.md: -------------------------------------------------------------------------------- 1 | Hello 2 | Goodbye 3 | Windows 4 | -------------------------------------------------------------------------------- /spec/line-ending-selector-spec.js: -------------------------------------------------------------------------------- 1 | const helpers = require('../lib/helpers') 2 | const {TextEditor} = require('atom') 3 | 4 | describe('line ending selector', () => { 5 | let lineEndingTile 6 | 7 | beforeEach(() => { 8 | jasmine.useRealClock() 9 | 10 | waitsForPromise(() => { 11 | return atom.packages.activatePackage('status-bar') 12 | }) 13 | 14 | waitsForPromise(() => { 15 | return atom.packages.activatePackage('line-ending-selector') 16 | }) 17 | 18 | waits(1) 19 | 20 | runs(() => { 21 | const statusBar = atom.workspace.getFooterPanels()[0].getItem() 22 | lineEndingTile = statusBar.getRightTiles()[0].getItem() 23 | expect(lineEndingTile.element.className).toMatch(/line-ending-tile/) 24 | expect(lineEndingTile.element.textContent).toBe('') 25 | }) 26 | }) 27 | 28 | describe('Commands', () => { 29 | let editor, editorElement 30 | 31 | beforeEach(() => { 32 | waitsForPromise(() => { 33 | return atom.workspace.open('mixed-endings.md').then((e) => { 34 | editor = e 35 | editorElement = atom.views.getView(editor) 36 | jasmine.attachToDOM(editorElement) 37 | }) 38 | }) 39 | }) 40 | 41 | describe('When "line-ending-selector:convert-to-LF" is run', () => { 42 | it('converts the file to LF line endings', () => { 43 | editorElement.focus() 44 | atom.commands.dispatch(document.activeElement, 'line-ending-selector:convert-to-LF') 45 | expect(editor.getText()).toBe('Hello\nGoodbye\nMixed\n') 46 | }) 47 | }) 48 | 49 | describe('When "line-ending-selector:convert-to-LF" is run', () => { 50 | it('converts the file to CRLF line endings', () => { 51 | editorElement.focus() 52 | atom.commands.dispatch(document.activeElement, 'line-ending-selector:convert-to-CRLF') 53 | expect(editor.getText()).toBe('Hello\r\nGoodbye\r\nMixed\r\n') 54 | }) 55 | }) 56 | }) 57 | 58 | describe('Status bar tile', () => { 59 | describe('when an empty file is opened', () => { 60 | it('uses the default line endings for the platform', () => { 61 | waitsFor((done) => { 62 | spyOn(helpers, 'getProcessPlatform').andReturn('win32') 63 | 64 | atom.workspace.open('').then((editor) => { 65 | const subscription = lineEndingTile.onDidChange(() => { 66 | subscription.dispose() 67 | expect(lineEndingTile.element.textContent).toBe('CRLF') 68 | expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n') 69 | expect(getTooltipText(lineEndingTile.element)).toBe('File uses CRLF (Windows) line endings') 70 | 71 | done() 72 | }) 73 | }) 74 | }) 75 | 76 | waitsFor((done) => { 77 | helpers.getProcessPlatform.andReturn('darwin') 78 | 79 | atom.workspace.open('').then((editor) => { 80 | const subscription = lineEndingTile.onDidChange(() => { 81 | subscription.dispose() 82 | expect(lineEndingTile.element.textContent).toBe('LF') 83 | expect(editor.getBuffer().getPreferredLineEnding()).toBe('\n') 84 | expect(getTooltipText(lineEndingTile.element)).toBe('File uses LF (Unix) line endings') 85 | 86 | done() 87 | }) 88 | }) 89 | }) 90 | }) 91 | 92 | describe('when the "defaultLineEnding" setting is set to "LF"', () => { 93 | beforeEach(() => { 94 | atom.config.set('line-ending-selector.defaultLineEnding', 'LF') 95 | }) 96 | 97 | it('uses LF line endings, regardless of the platform', () => { 98 | waitsFor((done) => { 99 | spyOn(helpers, 'getProcessPlatform').andReturn('win32') 100 | 101 | atom.workspace.open('').then((editor) => { 102 | lineEndingTile.onDidChange(() => { 103 | expect(lineEndingTile.element.textContent).toBe('LF') 104 | expect(editor.getBuffer().getPreferredLineEnding()).toBe('\n') 105 | done() 106 | }) 107 | }) 108 | }) 109 | }) 110 | }) 111 | 112 | describe('when the "defaultLineEnding" setting is set to "CRLF"', () => { 113 | beforeEach(() => { 114 | atom.config.set('line-ending-selector.defaultLineEnding', 'CRLF') 115 | }) 116 | 117 | it('uses CRLF line endings, regardless of the platform', () => { 118 | waitsFor((done) => { 119 | atom.workspace.open('').then((editor) => { 120 | lineEndingTile.onDidChange(() => { 121 | expect(lineEndingTile.element.textContent).toBe('CRLF') 122 | expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n') 123 | done() 124 | }) 125 | }) 126 | }) 127 | }) 128 | }) 129 | }) 130 | 131 | describe('when a file is opened that contains only CRLF line endings', () => { 132 | it('displays "CRLF" as the line ending', () => { 133 | waitsFor((done) => { 134 | atom.workspace.open('windows-endings.md').then(() => { 135 | lineEndingTile.onDidChange(() => { 136 | expect(lineEndingTile.element.textContent).toBe('CRLF') 137 | done() 138 | }) 139 | }) 140 | }) 141 | }) 142 | }) 143 | 144 | describe('when a file is opened that contains only LF line endings', () => { 145 | it('displays "LF" as the line ending', () => { 146 | waitsFor((done) => { 147 | atom.workspace.open('unix-endings.md').then((editor) => { 148 | lineEndingTile.onDidChange(() => { 149 | expect(lineEndingTile.element.textContent).toBe('LF') 150 | expect(editor.getBuffer().getPreferredLineEnding()).toBe(null) 151 | done() 152 | }) 153 | }) 154 | }) 155 | }) 156 | }) 157 | 158 | describe('when a file is opened that contains mixed line endings', () => { 159 | it('displays "Mixed" as the line ending', () => { 160 | waitsFor((done) => { 161 | atom.workspace.open('mixed-endings.md').then(() => { 162 | lineEndingTile.onDidChange(() => { 163 | expect(lineEndingTile.element.textContent).toBe('Mixed') 164 | done() 165 | }) 166 | }) 167 | }) 168 | }) 169 | }) 170 | 171 | describe('clicking the tile', () => { 172 | let lineEndingModal, lineEndingSelector 173 | 174 | beforeEach(() => { 175 | jasmine.attachToDOM(atom.views.getView(atom.workspace)) 176 | 177 | waitsFor((done) => 178 | atom.workspace.open('unix-endings.md').then(() => 179 | lineEndingTile.onDidChange(done) 180 | ) 181 | ) 182 | }) 183 | 184 | describe('when the text editor has focus', () => { 185 | it('opens the line ending selector modal for the text editor', () => { 186 | atom.workspace.getCenter().activate() 187 | const item = atom.workspace.getActivePaneItem() 188 | expect(item.getFileName && item.getFileName()).toBe('unix-endings.md') 189 | 190 | lineEndingTile.element.dispatchEvent(new MouseEvent('click', {})) 191 | lineEndingModal = atom.workspace.getModalPanels()[0] 192 | lineEndingSelector = lineEndingModal.getItem() 193 | 194 | expect(lineEndingModal.isVisible()).toBe(true) 195 | expect(lineEndingSelector.element.contains(document.activeElement)).toBe(true) 196 | let listItems = lineEndingSelector.element.querySelectorAll('li') 197 | expect(listItems[0].textContent).toBe('LF') 198 | expect(listItems[1].textContent).toBe('CRLF') 199 | }) 200 | }) 201 | 202 | describe('when the text editor does not have focus', () => { 203 | it('opens the line ending selector modal for the active text editor', () => { 204 | atom.workspace.getLeftDock().activate() 205 | const item = atom.workspace.getActivePaneItem() 206 | expect(item instanceof TextEditor).toBe(false) 207 | 208 | lineEndingTile.element.dispatchEvent(new MouseEvent('click', {})) 209 | lineEndingModal = atom.workspace.getModalPanels()[0] 210 | lineEndingSelector = lineEndingModal.getItem() 211 | 212 | expect(lineEndingModal.isVisible()).toBe(true) 213 | expect(lineEndingSelector.element.contains(document.activeElement)).toBe(true) 214 | let listItems = lineEndingSelector.element.querySelectorAll('li') 215 | expect(listItems[0].textContent).toBe('LF') 216 | expect(listItems[1].textContent).toBe('CRLF') 217 | }) 218 | }) 219 | 220 | describe('when selecting a different line ending for the file', () => { 221 | it('changes the line endings in the buffer', () => { 222 | lineEndingTile.element.dispatchEvent(new MouseEvent('click', {})) 223 | lineEndingModal = atom.workspace.getModalPanels()[0] 224 | lineEndingSelector = lineEndingModal.getItem() 225 | 226 | const lineEndingChangedPromise = new Promise(resolve => { 227 | lineEndingTile.onDidChange(() => { 228 | expect(lineEndingTile.element.textContent).toBe('CRLF') 229 | const editor = atom.workspace.getActiveTextEditor() 230 | expect(editor.getText()).toBe('Hello\r\nGoodbye\r\nUnix\r\n') 231 | expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n') 232 | resolve() 233 | }) 234 | }) 235 | 236 | lineEndingSelector.refs.queryEditor.setText('CR') 237 | lineEndingSelector.confirmSelection() 238 | expect(lineEndingModal.isVisible()).toBe(false) 239 | 240 | waitsForPromise(() => lineEndingChangedPromise) 241 | }) 242 | }) 243 | 244 | describe('when modal is exited', () => { 245 | it('leaves the tile selection as-is', () => { 246 | lineEndingTile.element.dispatchEvent(new MouseEvent('click', {})) 247 | lineEndingModal = atom.workspace.getModalPanels()[0] 248 | lineEndingSelector = lineEndingModal.getItem() 249 | 250 | lineEndingSelector.cancelSelection() 251 | expect(lineEndingTile.element.textContent).toBe('LF') 252 | }) 253 | }) 254 | }) 255 | 256 | describe('closing the last text editor', () => { 257 | it('displays no line ending in the status bar', () => { 258 | waitsForPromise(() => { 259 | return atom.workspace.open('unix-endings.md').then(() => { 260 | atom.workspace.getActivePane().destroy() 261 | expect(lineEndingTile.element.textContent).toBe('') 262 | }) 263 | }) 264 | }) 265 | }) 266 | 267 | describe('when the buffer\'s line endings change', () => { 268 | let editor 269 | 270 | beforeEach(() => { 271 | waitsFor((done) => { 272 | atom.workspace.open('unix-endings.md').then((e) => { 273 | editor = e 274 | lineEndingTile.onDidChange(done) 275 | }) 276 | }) 277 | }) 278 | 279 | it('updates the line ending text in the tile', () => { 280 | let tileText = lineEndingTile.element.textContent 281 | let tileUpdateCount = 0 282 | Object.defineProperty(lineEndingTile.element, 'textContent', { 283 | get () { 284 | return tileText 285 | }, 286 | 287 | set (text) { 288 | tileUpdateCount++ 289 | tileText = text 290 | } 291 | }) 292 | 293 | expect(lineEndingTile.element.textContent).toBe('LF') 294 | expect(getTooltipText(lineEndingTile.element)).toBe('File uses LF (Unix) line endings') 295 | 296 | waitsFor((done) => { 297 | editor.setTextInBufferRange([[0, 0], [0, 0]], '... ') 298 | editor.setTextInBufferRange([[0, Infinity], [1, 0]], '\r\n', {normalizeLineEndings: false}) 299 | lineEndingTile.onDidChange(done) 300 | }) 301 | 302 | runs(() => { 303 | expect(tileUpdateCount).toBe(1) 304 | expect(lineEndingTile.element.textContent).toBe('Mixed') 305 | expect(getTooltipText(lineEndingTile.element)).toBe('File uses mixed line endings') 306 | }) 307 | 308 | waitsFor((done) => { 309 | atom.commands.dispatch(editor.getElement(), 'line-ending-selector:convert-to-CRLF') 310 | lineEndingTile.onDidChange(done) 311 | }) 312 | 313 | runs(() => { 314 | expect(tileUpdateCount).toBe(2) 315 | expect(lineEndingTile.element.textContent).toBe('CRLF') 316 | expect(getTooltipText(lineEndingTile.element)).toBe('File uses CRLF (Windows) line endings') 317 | }) 318 | 319 | waitsFor((done) => { 320 | atom.commands.dispatch(editor.getElement(), 'line-ending-selector:convert-to-LF') 321 | lineEndingTile.onDidChange(done) 322 | }) 323 | 324 | runs(() => { 325 | expect(tileUpdateCount).toBe(3) 326 | expect(lineEndingTile.element.textContent).toBe('LF') 327 | }) 328 | 329 | runs(() => { 330 | editor.setTextInBufferRange([[0, 0], [0, 0]], '\n') 331 | }) 332 | 333 | waits(100) 334 | 335 | runs(() => { 336 | expect(tileUpdateCount).toBe(3) 337 | }) 338 | }) 339 | }) 340 | }) 341 | }) 342 | 343 | function getTooltipText (element) { 344 | const [tooltip] = atom.tooltips.findTooltips(element) 345 | return tooltip.getTitle() 346 | } 347 | --------------------------------------------------------------------------------