Я знаю, что это на четыре года позже, но я недавно столкнулся с той же проблемой и смог решить ее с помощью Swift.
Я хотел иметь возможность преобразовать выделенный текст в NSTextView в маркированный список, но выбор может явно охватывать части нескольких абзацев.Первым шагом является выяснение правильного диапазона выбора путем доступа к NSTextView.rangeForUserParagraphAttributeChange, который гарантирует, что вы всегда будете выбирать диапазон, включающий полные абзацы, а не частичные.
Теперь более сложная часть:Вы должны найти каждый абзац внутри измененного диапазона выделения и добавить к нему маркер, а также добавить соответствующий объект текстового списка и заменить текст в измененном выбранном диапазоне, чтобы включить новый текст.Это легче сказать, чем сделать, и мне понадобилось несколько дней, чтобы понять (я все еще учусь в колледже, так что пойди разберись).
Я не смог ничего найти об этом в ИнтернетеНадеюсь, это избавит будущих интернет-путешественников от проблем, с которыми мне пришлось столкнуться.
if let range = textView?.rangeForUserParagraphAttributeChange {
// So, the user has selected some text. Range may or may not be the user's exact selection...
// The start of range has possibly back-tracked from the start of the selection to the start of
// the nearest paragraph, and the end has possibly extended from the end of the selection to the
// end of the nearest paragraph at that point.
// Therefore, we are guarunteed to be selecting complete paragraphs.
//
// Now, for each paragraph, we want to insert a bullet.
// The trick is finding the range of each paragraph.
//
// Find the attributed string contents of the entire possibly multi-paragraph selection:
let entireRangeText = textView!.textStorage!.attributedSubstringFromRange(range)
// Find the string contents
let entireRangeString = entireRangeText.string as NSString
// Let's make a list to store all of the paragraph ranges inside range
var paragraphRanges: [NSRange] = []
// Find the range of the first paragraph (possibly the only paragraph)
var paraRange = entireRangeString.paragraphRangeForRange(NSMakeRange(0, 0))
// Para range is relative to range, so let's keep track of it's actual position.
var actualRange = NSMakeRange(paraRange.location + range.location, paraRange.length)
// Add it to the list
paragraphRanges += [actualRange]
// Now find each paragraph inside the selection
while NSMaxRange(paraRange) < entireRangeString.length {
// Find the next range relative to the range
paraRange = entireRangeString.paragraphRangeForRange(NSMakeRange(NSMaxRange(paraRange), 0))
// Find it's actual range relative to the entire text
actualRange = NSMakeRange(paraRange.location + range.location, paraRange.length)
// Add it to our list of paragraph ranges
paragraphRanges += [actualRange]
}
// This is the attributed string that we will use to replace the entireRangeText
// with the bulleted version.
let newText = NSMutableAttributedString()
// Start counting our bullets at 1...
// Todo: Respect the starting number in the preferences
var bulletNumber = 1
// Make a list object to add to each paragraph
// Todo: Respect the list type specified
let list = NSTextList(markerFormat: "{decimal}", options: 0)
// Go through each paragraph range and add bullets to the text inside it
for paragraphRange in paragraphRanges {
// Construct the bullet header:
let bulletText = "\t" + list.markerForItemNumber(bulletNumber) + "\t"
// Find the text from the paragraph
let paragraphText = textView!.textStorage!.attributedSubstringFromRange(paragraphRange)
let mutableParagraphText = NSMutableAttributedString(attributedString: paragraphText)
// A range pointer we really don't need:
var effectiveRange = NSRange()
// Construct our new string appropriately
// Get the paragraph attribute and modify it to have the text list
if let paragraphStyle = paragraphText.attribute(NSParagraphStyleAttributeName,
atIndex: 0,
longestEffectiveRange: &effectiveRange,
inRange: NSMakeRange(0, paragraphText.length))
as? NSParagraphStyle {
let newParagraphStyle = paragraphStyle.mutableCopy() as! NSMutableParagraphStyle
// Attach the list object to the paragraph style of the paragraph text
// So that auto list continuation will work properly
newParagraphStyle.textLists = [list]
// Update the text's paragraph style:
mutableParagraphText.addAttributes([NSParagraphStyleAttributeName: newParagraphStyle],
range: NSMakeRange(0, paragraphText.length))
}
// Make the bullet's attributes match the attributes of the text it's near, just
// like TextEdit does.
newText.appendAttributedString(
NSAttributedString(string: bulletText,
attributes: mutableParagraphText.attributesAtIndex(0,
longestEffectiveRange: &effectiveRange,
inRange: NSMakeRange(0, paragraphText.length))))
newText.appendAttributedString(mutableParagraphText)
// Increase to the next bullet number:
bulletNumber += 1
}
// Finally, insert our string
textView?.shouldChangeTextInRange(range, replacementString: nil)
textView?.insertText(newText, replacementRange: range)
// Tell the undo manager what happened:
textView?.didChangeText()
undoManager?.setActionName("Paragraph List") // ToDo: Localize
// Change the selection for auto-insertion to work
// IMPORTANT: Note that you have to change the selection twice to get auto list insertion to work
// (at least on my system). Must be a bug.
textView?.setSelectedRange(NSMakeRange(range.location + newText.string.characters.count - 1, 0))
textView?.setSelectedRange(NSMakeRange(range.location + newText.string.characters.count, 0))
}