Add Join Lines command (#1151)

This commit is contained in:
1024jp 2023-12-10 20:12:33 +09:00
parent 030c20a755
commit 688f15bf5c
15 changed files with 172 additions and 3 deletions

View File

@ -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.

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -516,6 +516,7 @@
"Delete Duplicate Lines" = "重複行を削除";
"Duplicate Line" = "行を複製";
"Delete Line" = "行を削除";
"Join Lines" = "行を結合";
"Trim Trailing Whitespace" = "行末の空白を削除";
// Contextual menu items

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -516,6 +516,7 @@
"Delete Duplicate Lines" = "移除重复行";
"Duplicate Line" = "复制行";
"Delete Line" = "删除行";
"Join Lines" = "连接行";
"Trim Trailing Whitespace" = "移除行尾空白";
// Contextual menu items

View File

@ -516,6 +516,7 @@
"Delete Duplicate Lines" = "移除重複行";
"Duplicate Line" = "複製行";
"Delete Line" = "刪除行";
"Join Lines" = "加入文字行";
"Trim Trailing Whitespace" = "移除行尾空白";
// Contextual menu items

View File

@ -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)
}
}