main.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. 'use babel';
  2. import _ from 'underscore-plus';
  3. import { CompositeDisposable, Disposable } from 'atom';
  4. import { Selector } from './selector';
  5. import StatusBarItem from './status-bar-item';
  6. import helpers from './helpers';
  7. const LineEndingRegExp = /\r\n|\n/g;
  8. // the following regular expression is executed natively via the `substring` package,
  9. // where `\A` corresponds to the beginning of the string.
  10. // More info: https://github.com/atom/line-ending-selector/pull/56
  11. // eslint-disable-next-line no-useless-escape
  12. const LFRegExp = /(\A|[^\r])\n/g;
  13. const CRLFRegExp = /\r\n/g;
  14. let disposables = null;
  15. export function activate() {
  16. disposables = new CompositeDisposable();
  17. let selectorDisposable;
  18. let selector;
  19. disposables.add(
  20. atom.commands.add('atom-text-editor', {
  21. 'line-ending-selector:show': () => {
  22. // Initiating Selector object - called only once when `line-ending-selector:show` is called
  23. if (!selectorDisposable) {
  24. // make a Selector object
  25. selector = new Selector([
  26. { name: 'LF', value: '\n' },
  27. { name: 'CRLF', value: '\r\n' }
  28. ]);
  29. // Add disposable for selector
  30. selectorDisposable = new Disposable(() => selector.dispose());
  31. disposables.add(selectorDisposable);
  32. }
  33. selector.show();
  34. },
  35. 'line-ending-selector:convert-to-LF': event => {
  36. const editorElement = event.target.closest('atom-text-editor');
  37. setLineEnding(editorElement.getModel(), '\n');
  38. },
  39. 'line-ending-selector:convert-to-CRLF': event => {
  40. const editorElement = event.target.closest('atom-text-editor');
  41. setLineEnding(editorElement.getModel(), '\r\n');
  42. }
  43. })
  44. );
  45. }
  46. export function deactivate() {
  47. disposables.dispose();
  48. }
  49. export function consumeStatusBar(statusBar) {
  50. let statusBarItem = new StatusBarItem();
  51. let currentBufferDisposable = null;
  52. let tooltipDisposable = null;
  53. const updateTile = _.debounce(buffer => {
  54. getLineEndings(buffer).then(lineEndings => {
  55. if (lineEndings.size === 0) {
  56. let defaultLineEnding = getDefaultLineEnding();
  57. buffer.setPreferredLineEnding(defaultLineEnding);
  58. lineEndings = new Set().add(defaultLineEnding);
  59. }
  60. statusBarItem.setLineEndings(lineEndings);
  61. });
  62. }, 0);
  63. disposables.add(
  64. atom.workspace.observeActiveTextEditor(editor => {
  65. if (currentBufferDisposable) currentBufferDisposable.dispose();
  66. if (editor && editor.getBuffer) {
  67. let buffer = editor.getBuffer();
  68. updateTile(buffer);
  69. currentBufferDisposable = buffer.onDidChange(({ oldText, newText }) => {
  70. if (!statusBarItem.hasLineEnding('\n')) {
  71. if (newText.indexOf('\n') >= 0) {
  72. updateTile(buffer);
  73. }
  74. } else if (!statusBarItem.hasLineEnding('\r\n')) {
  75. if (newText.indexOf('\r\n') >= 0) {
  76. updateTile(buffer);
  77. }
  78. } else if (oldText.indexOf('\n')) {
  79. updateTile(buffer);
  80. }
  81. });
  82. } else {
  83. statusBarItem.setLineEndings(new Set());
  84. currentBufferDisposable = null;
  85. }
  86. if (tooltipDisposable) {
  87. disposables.remove(tooltipDisposable);
  88. tooltipDisposable.dispose();
  89. }
  90. tooltipDisposable = atom.tooltips.add(statusBarItem.element, {
  91. title() {
  92. return `File uses ${statusBarItem.description()} line endings`;
  93. }
  94. });
  95. disposables.add(tooltipDisposable);
  96. })
  97. );
  98. disposables.add(
  99. new Disposable(() => {
  100. if (currentBufferDisposable) currentBufferDisposable.dispose();
  101. })
  102. );
  103. statusBarItem.onClick(() => {
  104. const editor = atom.workspace.getActiveTextEditor();
  105. atom.commands.dispatch(
  106. atom.views.getView(editor),
  107. 'line-ending-selector:show'
  108. );
  109. });
  110. let tile = statusBar.addRightTile({ item: statusBarItem, priority: 200 });
  111. disposables.add(new Disposable(() => tile.destroy()));
  112. }
  113. function getDefaultLineEnding() {
  114. switch (atom.config.get('line-ending-selector.defaultLineEnding')) {
  115. case 'LF':
  116. return '\n';
  117. case 'CRLF':
  118. return '\r\n';
  119. case 'OS Default':
  120. default:
  121. return helpers.getProcessPlatform() === 'win32' ? '\r\n' : '\n';
  122. }
  123. }
  124. function getLineEndings(buffer) {
  125. if (typeof buffer.find === 'function') {
  126. return Promise.all([buffer.find(LFRegExp), buffer.find(CRLFRegExp)]).then(
  127. ([hasLF, hasCRLF]) => {
  128. const result = new Set();
  129. if (hasLF) result.add('\n');
  130. if (hasCRLF) result.add('\r\n');
  131. return result;
  132. }
  133. );
  134. } else {
  135. return new Promise(resolve => {
  136. const result = new Set();
  137. for (let i = 0; i < buffer.getLineCount() - 1; i++) {
  138. result.add(buffer.lineEndingForRow(i));
  139. }
  140. resolve(result);
  141. });
  142. }
  143. }
  144. export function setLineEnding(item, lineEnding) {
  145. if (item && item.getBuffer) {
  146. let buffer = item.getBuffer();
  147. buffer.setPreferredLineEnding(lineEnding);
  148. buffer.setText(buffer.getText().replace(LineEndingRegExp, lineEnding));
  149. }
  150. }