Move Lines in Textarea with Alt + Arrow Up/Down

Move lines up and down in textarea with Alt + Arrow Up/Down hotkeys, same as in VSCode

  1. // ==UserScript==
  2. // @name Move Lines in Textarea with Alt + Arrow Up/Down
  3. // @author NWP
  4. // @description Move lines up and down in textarea with Alt + Arrow Up/Down hotkeys, same as in VSCode
  5. // @namespace https://greasyfork.org/users/877912
  6. // @version 0.2
  7. // @license MIT
  8. // @match *://*/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. function moveLine(element, direction) {
  16. const start = element.selectionStart;
  17. const value = element.value;
  18. const lines = value.split('\n');
  19. const lineIndex = value.substr(0, start).split('\n').length - 1;
  20. const lineStart = value.lastIndexOf('\n', start - 1) + 1;
  21. const cursorOffsetInLine = start - lineStart;
  22.  
  23. if ((direction === 'up' && lineIndex === 0) || (direction === 'down' && lineIndex === lines.length - 1)) {
  24. return;
  25. }
  26.  
  27. const targetLineIndex = direction === 'up' ? lineIndex - 1 : lineIndex + 1;
  28. [lines[lineIndex], lines[targetLineIndex]] = [lines[targetLineIndex], lines[lineIndex]];
  29.  
  30. const newValue = lines.join('\n');
  31. const newLineStart = newValue.split('\n').slice(0, targetLineIndex).join('\n').length + (targetLineIndex > 0 ? 1 : 0);
  32. const newCursorPosition = newLineStart + cursorOffsetInLine;
  33.  
  34. element.value = newValue;
  35. element.setSelectionRange(newCursorPosition, newCursorPosition);
  36. }
  37.  
  38. function handleKeyDown(e) {
  39. if (e.altKey && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {
  40. const activeElement = document.activeElement;
  41. if (activeElement && activeElement.tagName === 'TEXTAREA') {
  42. e.preventDefault();
  43. const direction = e.key === 'ArrowUp' ? 'up' : 'down';
  44. moveLine(activeElement, direction);
  45. }
  46. }
  47. }
  48.  
  49. function observeTextAreas() {
  50. document.addEventListener('keydown', handleKeyDown);
  51.  
  52. const observer = new MutationObserver((mutations) => {
  53. mutations.forEach((mutation) => {
  54. mutation.addedNodes.forEach((node) => {
  55. if (node.tagName === 'TEXTAREA' || (node.nodeType === Node.ELEMENT_NODE && node.querySelector('textarea'))) {
  56. document.addEventListener('keydown', handleKeyDown);
  57. }
  58. });
  59. });
  60. });
  61.  
  62. observer.observe(document.body, { childList: true, subtree: true });
  63. }
  64.  
  65. observeTextAreas();
  66. })();