wrap-guide-element.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. const {CompositeDisposable} = require('atom');
  2. module.exports = class WrapGuideElement {
  3. #_when = null;
  4. #_shouldShow = null;
  5. constructor(editor, editorElement, when) {
  6. this.editor = editor;
  7. this.editorElement = editorElement;
  8. this.subscriptions = new CompositeDisposable();
  9. this.configSubscriptions = new CompositeDisposable();
  10. this.softWrapAPLLsubscriptions = null;
  11. this.element = document.createElement('div');
  12. this.element.setAttribute('is', 'wrap-guide');
  13. this.element.classList.add('wrap-guide-container');
  14. this.attachToLines();
  15. this.handleEvents();
  16. this.setWhen(when);
  17. this.element.updateGuide = (async () => await this.updateGuide()).bind(this);
  18. this.element.getDefaultColumn = this.getDefaultColumn.bind(this);
  19. }
  20. get shouldShow() { return this.#_shouldShow; }
  21. get when() { return this.#_when; }
  22. setWhen(when) {
  23. if (when == this.when) return;
  24. this.#_when = when;
  25. this.updateWhen();
  26. }
  27. async updateWhen() {
  28. switch (this.when) {
  29. case "atPreferredLineLength":
  30. this.#_shouldShow = this.editor.isSoftWrapped() && atom.config.get('editor.softWrapAtPreferredLineLength', {scope: this.editor.getRootScopeDescriptor()});
  31. break;
  32. case "wrapping":
  33. this.#_shouldShow = this.editor.isSoftWrapped();
  34. break;
  35. default: // "always"
  36. this.#_shouldShow = true;
  37. break;
  38. }
  39. await this.updateGuide();
  40. }
  41. attachToLines() {
  42. const scrollView = this.editorElement.querySelector('.scroll-view');
  43. return (scrollView != null ? scrollView.appendChild(this.element) : undefined);
  44. }
  45. handleEvents() {
  46. const updateGuideCallback = async () => await this.updateGuide();
  47. this.handleConfigEvents();
  48. this.subscriptions.add(this.editor.onDidChangeSoftWrapped(async (wrapped) => {
  49. if (this.when === null) return;
  50. await this.updateWhen();
  51. }));
  52. this.subscriptions.add(atom.config.onDidChange('editor.fontSize', async () => {
  53. // Wait for editor to finish updating before updating wrap guide
  54. await this.editorElement.getComponent().getNextUpdatePromise();
  55. updateGuideCallback();
  56. }));
  57. this.subscriptions.add(this.editorElement.onDidChangeScrollLeft(updateGuideCallback));
  58. this.subscriptions.add(this.editor.onDidChangePath(updateGuideCallback));
  59. this.subscriptions.add(this.editor.onDidChangeGrammar(async () => {
  60. this.configSubscriptions.dispose();
  61. this.handleConfigEvents();
  62. await this.updateWhen();
  63. }));
  64. this.subscriptions.add(this.editor.onDidDestroy(() => {
  65. this.subscriptions.dispose();
  66. if (this.softWrapAPLLsubscriptions) this.softWrapAPLLsubscriptions.dispose();
  67. this.configSubscriptions.dispose();
  68. }));
  69. this.subscriptions.add(this.editorElement.onDidAttach(async () => {
  70. this.attachToLines();
  71. await updateGuideCallback();
  72. }));
  73. }
  74. handleConfigEvents() {
  75. const {uniqueAscending} = require('./main');
  76. if (this.softWrapAPLLsubscriptions) this.softWrapAPLLsubscriptions.dispose();
  77. this.softWrapAPLLsubscriptions = new CompositeDisposable();
  78. this.softWrapAPLLsubscriptions.add(atom.config.onDidChange('editor.softWrapAtPreferredLineLength',
  79. {scope: this.editor.getRootScopeDescriptor()}, async ({newValue}) => {
  80. if (this.when === null) return;
  81. await this.updateWhen();
  82. }));
  83. const updatePreferredLineLengthCallback = async (args) => {
  84. // ensure that the right-most wrap guide is the preferredLineLength
  85. let columns = atom.config.get('wrap-guide.columns', {scope: this.editor.getRootScopeDescriptor()});
  86. if (columns.length > 0) {
  87. columns[columns.length - 1] = args.newValue;
  88. columns = uniqueAscending(Array.from(columns).filter((i) => i <= args.newValue));
  89. atom.config.set('wrap-guide.columns', columns,
  90. {scopeSelector: `.${this.editor.getGrammar().scopeName}`});
  91. }
  92. return await this.updateGuide();
  93. };
  94. this.configSubscriptions.add(atom.config.onDidChange(
  95. 'editor.preferredLineLength',
  96. {scope: this.editor.getRootScopeDescriptor()},
  97. updatePreferredLineLengthCallback
  98. ));
  99. const updateGuideCallback = async () => await this.updateGuide();
  100. this.configSubscriptions.add(atom.config.onDidChange(
  101. 'wrap-guide.enabled',
  102. {scope: this.editor.getRootScopeDescriptor()},
  103. updateGuideCallback
  104. ));
  105. const updateGuidesCallback = async (args) => {
  106. // ensure that multiple guides stay sorted in ascending order
  107. const columns = uniqueAscending(args.newValue);
  108. if (columns != null ? columns.length : undefined) {
  109. atom.config.set('wrap-guide.columns', columns);
  110. if (atom.config.get('wrap-guide.modifyPreferredLineLength')) {
  111. atom.config.set('editor.preferredLineLength', columns[columns.length - 1],
  112. {scopeSelector: `.${this.editor.getGrammar().scopeName}`});
  113. }
  114. return await this.updateGuide();
  115. }
  116. };
  117. return this.configSubscriptions.add(atom.config.onDidChange(
  118. 'wrap-guide.columns',
  119. {scope: this.editor.getRootScopeDescriptor()},
  120. updateGuidesCallback
  121. ));
  122. }
  123. getDefaultColumn() {
  124. return atom.config.get('editor.preferredLineLength', {scope: this.editor.getRootScopeDescriptor()});
  125. }
  126. getGuidesColumns(path, scopeName) {
  127. let left;
  128. const columns = (left = atom.config.get('wrap-guide.columns', {scope: this.editor.getRootScopeDescriptor()})) != null ? left : [];
  129. if (columns.length > 0) { return columns; }
  130. return [this.getDefaultColumn()];
  131. }
  132. isEnabled() {
  133. let left;
  134. return (left = atom.config.get('wrap-guide.enabled', {scope: this.editor.getRootScopeDescriptor()})) != null ? left : true;
  135. }
  136. hide() {
  137. return this.element.style.display = 'none';
  138. }
  139. show() {
  140. return this.element.style.display = 'block';
  141. }
  142. updateGuide() {
  143. if (this.isEnabled())
  144. return this.updateGuides();
  145. else return this.hide();
  146. }
  147. updateGuides() {
  148. this.removeGuides();
  149. if (!this.shouldShow) return this.hide();
  150. this.appendGuides();
  151. if (this.element.children.length) {
  152. return this.show();
  153. } else {
  154. return this.hide();
  155. }
  156. }
  157. destroy() {
  158. this.element.remove();
  159. this.subscriptions.dispose();
  160. if (this.softWrapAPLLsubscriptions) this.softWrapAPLLsubscriptions.dispose();
  161. return this.configSubscriptions.dispose();
  162. }
  163. removeGuides() {
  164. return (() => {
  165. const result = [];
  166. while (this.element.firstChild) {
  167. result.push(this.element.removeChild(this.element.firstChild));
  168. }
  169. return result;
  170. })();
  171. }
  172. appendGuides() {
  173. const columns = this.getGuidesColumns(this.editor.getPath(), this.editor.getGrammar().scopeName);
  174. return (() => {
  175. const result = [];
  176. for (let column of columns) {
  177. if (!(column < 0)) {
  178. result.push(this.appendGuide(column));
  179. } else {
  180. result.push(undefined);
  181. }
  182. }
  183. return result;
  184. })();
  185. }
  186. appendGuide(column) {
  187. let columnWidth = this.editorElement.getDefaultCharacterWidth() * column;
  188. columnWidth -= this.editorElement.getScrollLeft();
  189. const guide = document.createElement('div');
  190. guide.classList.add('wrap-guide');
  191. guide.style.left = `${Math.round(columnWidth)}px`;
  192. return this.element.appendChild(guide);
  193. }
  194. };