mirror of
https://github.com/coteditor/CotEditor.git
synced 2024-10-26 10:58:05 +03:00
Add Join Lines command (#1151)
This commit is contained in:
parent
030c20a755
commit
688f15bf5c
@ -3,6 +3,11 @@
|
||||
4.7.1 (unreleased)
|
||||
--------------------------
|
||||
|
||||
### New Features
|
||||
|
||||
- Add “Join Lines” command to the Text menu.
|
||||
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix an issue that some actions, such as scripts, could not performed via the Quick Action bar.
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="22155" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22155"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Application-->
|
||||
@ -862,6 +862,11 @@ CA
|
||||
<action selector="deleteLine:" target="Ady-hI-5gd" id="vej-dU-Adx"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Join Lines" keyEquivalent="J" id="te2-4F-YwD">
|
||||
<connections>
|
||||
<action selector="joinLines:" target="Ady-hI-5gd" id="EVv-9z-tX9"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
|
@ -128,6 +128,21 @@ extension EditorTextView {
|
||||
}
|
||||
|
||||
|
||||
/// Join the lines in the selections when the selections contain more than one line break; otherwise join the line where the cursor exists to the subsequent line,
|
||||
@IBAction func joinLines(_ sender: Any?) {
|
||||
|
||||
guard let selectedRanges = self.rangesForUserTextChange?.map(\.rangeValue) else { return }
|
||||
|
||||
let editingInfo = if selectedRanges.contains(where: { !$0.isEmpty }) {
|
||||
self.string.joinLines(in: selectedRanges)
|
||||
} else {
|
||||
self.string.joinLines(after: selectedRanges)
|
||||
}
|
||||
|
||||
self.edit(with: editingInfo, actionName: String(localized: "Join Lines"))
|
||||
}
|
||||
|
||||
|
||||
/// trim all trailing whitespace
|
||||
@IBAction func trimTrailingWhitespace(_ sender: Any?) {
|
||||
|
||||
@ -419,6 +434,36 @@ extension String {
|
||||
}
|
||||
|
||||
|
||||
/// Join lines in the ranges by replacing continuous whitespaces with a space.
|
||||
func joinLines(in ranges: [NSRange]) -> EditingInfo {
|
||||
|
||||
let replacementStrings = ranges
|
||||
.map { (self as NSString).substring(with: $0) }
|
||||
.map { $0.replacing(/\s*\R\s*/, with: " ") }
|
||||
var selectedRanges: [NSRange] = []
|
||||
var offset = 0
|
||||
for (range, replacementString) in zip(ranges, replacementStrings) {
|
||||
selectedRanges.append(NSRange(location: range.location + offset, length: replacementString.length))
|
||||
offset += replacementString.length - range.length
|
||||
}
|
||||
|
||||
return EditingInfo(strings: replacementStrings, ranges: ranges, selectedRanges: selectedRanges)
|
||||
}
|
||||
|
||||
|
||||
/// Join each of lines containing the given ranges with the subsequent line by replacing continuous whitespaces with a space.
|
||||
func joinLines(after ranges: [NSRange]) -> EditingInfo {
|
||||
|
||||
let lineRanges = (self as NSString).lineRanges(for: ranges)
|
||||
let replacementRanges = lineRanges
|
||||
.map { (self as NSString).range(of: #"\s*\R\s*"#, options: .regularExpression, range: NSRange($0.lowerBound..<self.length)) }
|
||||
.filter { !$0.isNotFound } // when in the last line
|
||||
let replacementStrings = Array(repeating: " ", count: replacementRanges.count)
|
||||
|
||||
return EditingInfo(strings: replacementStrings, ranges: replacementRanges, selectedRanges: nil)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: Private Methods
|
||||
|
||||
|
@ -516,6 +516,7 @@
|
||||
"Delete Duplicate Lines" = "Doppelte Zeilen löschen";
|
||||
"Duplicate Line" = "Zeile duplizieren";
|
||||
"Delete Line" = "Zeile löschen";
|
||||
"Join Lines" = "Zeilen verbinden";
|
||||
"Trim Trailing Whitespace" = "Leerzeichen am Zeilenende entfernen";
|
||||
|
||||
// Contextual menu items
|
||||
|
@ -516,6 +516,7 @@
|
||||
"Delete Duplicate Lines" = "Delete Duplicate Lines";
|
||||
"Duplicate Line" = "Duplicate Line";
|
||||
"Delete Line" = "Delete Line";
|
||||
"Join Lines" = "Join Lines";
|
||||
"Trim Trailing Whitespace" = "Trim Trailing Whitespace";
|
||||
|
||||
// Contextual menu items
|
||||
|
@ -516,6 +516,7 @@
|
||||
"Delete Duplicate Lines" = "Eliminar líneas repetidas";
|
||||
"Duplicate Line" = "Duplicar línea";
|
||||
"Delete Line" = "Eliminar línea";
|
||||
"Join Lines" = "Unir líneas";
|
||||
"Trim Trailing Whitespace" = "Recortar espacios en blanco finales";
|
||||
|
||||
// Contextual menu items
|
||||
|
@ -516,6 +516,7 @@
|
||||
"Delete Duplicate Lines" = "Supprimer les lignes en double";
|
||||
"Duplicate Line" = "Dupliquer la ligne";
|
||||
"Delete Line" = "Supprimer la ligne";
|
||||
"Join Lines" = "Joindre des lignes";
|
||||
"Trim Trailing Whitespace" = "Rogner les espaces aux extrémités";
|
||||
|
||||
// Contextual menu items
|
||||
|
@ -512,10 +512,11 @@
|
||||
"Move Line" = "Muovi riga";
|
||||
"Sort Lines" = "Ordina righe";
|
||||
"Reverse Lines" = "Inverti righe";
|
||||
"Shuffle Lines" = "Mescola le righe";
|
||||
"Shuffle Lines" = "Mescola righe";
|
||||
"Delete Duplicate Lines" = "Elimina righe duplicate";
|
||||
"Duplicate Line" = "Duplica riga";
|
||||
"Delete Line" = "Elimina riga";
|
||||
"Join Lines" = "Unisci righe";
|
||||
"Trim Trailing Whitespace" = "Rimuovi spazi bianchi a fine riga";
|
||||
|
||||
// Contextual menu items
|
||||
|
@ -516,6 +516,7 @@
|
||||
"Delete Duplicate Lines" = "重複行を削除";
|
||||
"Duplicate Line" = "行を複製";
|
||||
"Delete Line" = "行を削除";
|
||||
"Join Lines" = "行を結合";
|
||||
"Trim Trailing Whitespace" = "行末の空白を削除";
|
||||
|
||||
// Contextual menu items
|
||||
|
@ -15049,6 +15049,78 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"te2-4F-YwD.title" : {
|
||||
"comment" : "Class = \"NSMenuItem\"; title = \"Join Lines\"; ObjectID = \"te2-4F-YwD\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Zeilen verbinden"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Join Lines"
|
||||
}
|
||||
},
|
||||
"en-GB" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Join Lines"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Unir líneas"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Joindre des lignes"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Unisci righe"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "行を結合"
|
||||
}
|
||||
},
|
||||
"pt" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Juntar Linhas"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Satırları Birleştir"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "连接行"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "加入文字行"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"TNF-pL-CAZ.title" : {
|
||||
"comment" : "Class = \"NSMenuItem\"; title = \"Share…\"; ObjectID = \"TNF-pL-CAZ\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
|
@ -516,6 +516,7 @@
|
||||
"Delete Duplicate Lines" = "Apagar Linhas Duplicadas";
|
||||
"Duplicate Line" = "Duplicar Linha";
|
||||
"Delete Line" = "Apagar Linha";
|
||||
"Join Lines" = "Juntar Linhas";
|
||||
"Trim Trailing Whitespace" = "Aparar Espaço em Branco à Direita";
|
||||
|
||||
// Contextual menu items
|
||||
|
@ -516,6 +516,7 @@
|
||||
"Delete Duplicate Lines" = "Çoğaltılan Satırları Sil";
|
||||
"Duplicate Line" = "Satır Yinele";
|
||||
"Delete Line" = "Satın Sil";
|
||||
"Join Lines" = "Satırları Birleştir";
|
||||
"Trim Trailing Whitespace" = "İzleyen Boşluğu Kırp";
|
||||
|
||||
// Contextual menu items
|
||||
|
@ -516,6 +516,7 @@
|
||||
"Delete Duplicate Lines" = "移除重复行";
|
||||
"Duplicate Line" = "复制行";
|
||||
"Delete Line" = "删除行";
|
||||
"Join Lines" = "连接行";
|
||||
"Trim Trailing Whitespace" = "移除行尾空白";
|
||||
|
||||
// Contextual menu items
|
||||
|
@ -516,6 +516,7 @@
|
||||
"Delete Duplicate Lines" = "移除重複行";
|
||||
"Duplicate Line" = "複製行";
|
||||
"Delete Line" = "刪除行";
|
||||
"Join Lines" = "加入文字行";
|
||||
"Trim Trailing Whitespace" = "移除行尾空白";
|
||||
|
||||
// Contextual menu items
|
||||
|
@ -214,6 +214,38 @@ final class StringLineProcessingTests: XCTestCase {
|
||||
XCTAssertEqual(info?.ranges, [NSRange(3, 5), NSRange(8, 3)])
|
||||
XCTAssertEqual(info?.selectedRanges, [NSRange(3, 0)])
|
||||
}
|
||||
|
||||
|
||||
func testJoinLinesIn() {
|
||||
|
||||
let string = """
|
||||
aa
|
||||
bbbb
|
||||
ccc
|
||||
d
|
||||
"""
|
||||
let info = string.joinLines(in: [NSRange(1, 6), NSRange(10, 1)])
|
||||
|
||||
XCTAssertEqual(info.strings, ["a bb", "c"])
|
||||
XCTAssertEqual(info.ranges, [NSRange(1, 6), NSRange(10, 1)])
|
||||
XCTAssertEqual(info.selectedRanges, [NSRange(1, 4), NSRange(8, 1)])
|
||||
}
|
||||
|
||||
|
||||
func testJoinLinesAfter() {
|
||||
|
||||
let string = """
|
||||
aa
|
||||
bbbb
|
||||
ccc
|
||||
d
|
||||
"""
|
||||
let info = string.joinLines(after: [NSRange(1, 0), NSRange(10, 0), NSRange(14, 0)])
|
||||
|
||||
XCTAssertEqual(info.strings, [" ", " "])
|
||||
XCTAssertEqual(info.ranges, [NSRange(2, 3), NSRange(13, 1)])
|
||||
XCTAssertNil(info.selectedRanges)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user