                 lineString += "\n"
             }
             
+            replacementStrings.append(lineString)
             replacementRanges.append(replacementRange)
-            replacementStrings.append(lineString)
-        }
-        
-        self.replace(with: replacementStrings, ranges: replacementRanges, selectedRanges: nil,
-                     actionName: "Duplicate Line".localized)
+        }
+        
+        return (strings: replacementStrings, ranges: replacementRanges, selectedRanges: nil)
     }
     
     
     /// remove selected lines
-    @IBAction func deleteLine(_ sender: Any?) {
-        
-        let replacementRanges = self.selectedLineRanges
-        
-        // on empty last line
-        guard !replacementRanges.isEmpty else { return }
-        
+    func deleteLine(in ranges: [NSRange]) -> EditingInfo? {
+        
+        guard !ranges.isEmpty else { return nil }
+        
+        let replacementRanges = (self as NSString).lineRanges(for: ranges)
         let replacementStrings = [String](repeating: "", count: replacementRanges.count)
         
-        self.replace(with: replacementStrings, ranges: replacementRanges, selectedRanges: nil,
-                     actionName: "Delete Line".localized)
-    }
-    
-    
-    /// trim all trailing whitespace
-    @IBAction func trimTrailingWhitespace(_ sender: Any?) {
-        
-        let trimsWhitespaceOnlyLines = UserDefaults.standard[.trimsWhitespaceOnlyLines]
-        
-        self.trimTrailingWhitespace(ignoresEmptyLines: !trimsWhitespaceOnlyLines)
+        return (strings: replacementStrings, ranges: replacementRanges, selectedRanges: nil)
     }
     
 }
-
-
-extension NSTextView {
-    
-    func sortLines(pattern: SortPattern, options: SortOptions) {
-        
-        let string = self.string as NSString
-        
-        // process whole document if no text selected
-        if self.selectedRange.length == 0 {
-            self.selectedRange = string.range
-        }
-        
-        let lineRange = string.lineRange(for: self.selectedRange, excludingLastLineEnding: true)
-        
-        guard lineRange.length > 0 else { return }
-        
-        let newString = pattern.sort(string.substring(with: lineRange), options: options)
-        
-        self.replace(with: newString, range: lineRange, selectedRange: lineRange,
-                     actionName: "Sort Lines".localized)
-    }
-    
-}
-
-
-
-// MARK: Private NSTextView Extension
-
-private extension NSTextView {
-    
-    /// extract line by line line ranges which selected ranges include
-    var selectedLineRanges: [NSRange] {
-        
-        return (self.string as NSString).lineRanges(for: self.selectedRanges as! [NSRange])
-    }
-    
-}

            replacementRanges.append(lineRange)
        }
        
        self.replace(with: replacementStrings, ranges: replacementRanges, selectedRanges: nil,
                     actionName: "Delete Duplicate Lines".localized)
    }
    
    
    /// duplicate selected lines below
    @IBAction func duplicateLine(_ sender: Any?) {
        
        var replacementRanges = [NSRange]()
        var replacementStrings = [String]()
        
        let string = self.string as NSString
        let selectedRanges = self.selectedRanges as! [NSRange]
        
        // get lines to process
        for selectedRange in selectedRanges {
            let lineRange = string.lineRange(for: selectedRange)
            let replacementRange = NSRange(location: lineRange.location, length: 0)
            var lineString = string.substring(with: lineRange)
            

            self.didChangeText()
            
            // move selected ranges in the line to move
            for selectedRange in ranges {
                if let intersectionRange = selectedRange.intersection(editRange) {
                    selectedRanges.append(NSRange(location: intersectionRange.location + lowerLineRange.length,
                                                  length: intersectionRange.length))
                    
                } else if editRange.contains(selectedRange.location) {
                    selectedRanges.append(NSRange(location: selectedRange.location + lowerLineRange.length,
                                                  length: selectedRange.length))
                }
            }
        }
        textStorage.endEditing()
        
        self.setSelectedRangesWithUndo(selectedRanges)
        
        self.undoManager?.setActionName("Move Line".localized)
    }
