Skip to content

Commit

Permalink
Toggle comment for current line via CMD + / (#241)
Browse files Browse the repository at this point in the history
This change implements the functionality of <kbd>⌘</kbd> <kbd>/</kbd>
for single line comments. It allows you to toggle between commented and
uncommented for the line the cursor is currently on when you press
<kbd>⌘</kbd> <kbd>/</kbd>.

To do so, I implemented a `keyDown` event recognizer, which listens for
when the relevant keys are pressed. If <kbd>⌘</kbd> <kbd>/</kbd> is
pressed, it calls a method called `commandSlashCalled()`, which decides
which toggle is supposed to happen depending on if the line is already
commented or not. It also addresses the situation of special cases of
languages like HTML, which need a comment at the beginning and end of
the line (essentially a `rangeComment`) to comment a single line.

### Related Issues

- #38

This PR accomplishes part of #38. I talked with some of the project
leads (@FastestMolasses) and they said it makes sense to break #38 into
a couple different issues (I.e. single-line vs highlighted chunks, etc).
Single-line comment toggling is completed as of this PR but commenting
highlighted code still needs to be completed.

### Checklist

- [x] I read and understood the [contributing
guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md)
as well as the [code of
conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md)
- [x] The issues this PR addresses are related to each other
- [x] My changes generate no new warnings
- [x] My code builds and runs on my machine
- [x] My changes are all related to the related issue above
- [x] I documented my code

### Screenshots

<img width="626" alt="Screenshot 2024-04-19 at 5 44 24 PM"
src="https://github.com/CodeEditApp/CodeEditSourceEditor/assets/143217945/1200a5c1-d8ea-48a4-9704-70db7aa23fc7">

---------

Co-authored-by: Abe <[email protected]>
  • Loading branch information
Sophiahooley and FastestMolasses authored Apr 25, 2024
1 parent 0c741c2 commit dae56c8
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,18 @@ extension TextViewController {
}
}
.store(in: &cancellables)

NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
guard self.view.window?.firstResponder == self.textView else { return event }
let charactersIgnoringModifiers = event.charactersIgnoringModifiers
let commandKey = NSEvent.ModifierFlags.command.rawValue
let modifierFlags = event.modifierFlags.intersection(.deviceIndependentFlagsMask).rawValue
if modifierFlags == commandKey && event.charactersIgnoringModifiers == "/" {
self.commandSlashCalled()
return nil
} else {
return event
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//
// TextViewController+Shortcuts.swift
// CodeEditSourceEditor
//
// Created by Sophia Hooley on 4/21/24.
//

import CodeEditTextView
import AppKit

extension TextViewController {
/// Method called when CMD + / key sequence recognized, comments cursor's current line of code
public func commandSlashCalled() {
guard let cursorPosition = cursorPositions.first else {
print("There is no cursor \(#function)")
return
}
// Many languages require a character sequence at the beginning of the line to comment the line.
// (ex. python #, C++ //)
// If such a sequence exists, we will insert that sequence at the beginning of the line
if !language.lineCommentString.isEmpty {
toggleCharsAtBeginningOfLine(chars: language.lineCommentString, lineNumber: cursorPosition.line)
}
// In other cases, languages require a character sequence at beginning and end of a line, aka a range comment
// (Ex. HTML <!--line here -->)
// We treat the line as a one-line range to comment it out using rangeCommentStrings on both sides of the line
else {
let (openComment, closeComment) = language.rangeCommentStrings
toggleCharsAtEndOfLine(chars: closeComment, lineNumber: cursorPosition.line)
toggleCharsAtBeginningOfLine(chars: openComment, lineNumber: cursorPosition.line)
}
}

/// Toggles comment string at the beginning of a specified line (lineNumber is 1-indexed)
private func toggleCharsAtBeginningOfLine(chars: String, lineNumber: Int) {
guard let lineInfo = textView.layoutManager.textLineForIndex(lineNumber - 1) else {
print("There are no characters/lineInfo \(#function)")
return
}
guard let lineString = textView.textStorage.substring(from: lineInfo.range) else {
print("There are no characters/lineString \(#function)")
return
}
let firstNonWhiteSpaceCharIndex = lineString.firstIndex(where: {!$0.isWhitespace}) ?? lineString.startIndex
let numWhitespaceChars = lineString.distance(from: lineString.startIndex, to: firstNonWhiteSpaceCharIndex)
let firstCharsInLine = lineString.suffix(from: firstNonWhiteSpaceCharIndex).prefix(chars.count)
// toggle comment off
if firstCharsInLine == chars {
textView.replaceCharacters(in: NSRange(
location: lineInfo.range.location + numWhitespaceChars,
length: chars.count
), with: "")
}
// toggle comment on
else {
textView.replaceCharacters(in: NSRange(
location: lineInfo.range.location + numWhitespaceChars,
length: 0
), with: chars)
}
}

/// Toggles a specific string of characters at the end of a specified line. (lineNumber is 1-indexed)
private func toggleCharsAtEndOfLine(chars: String, lineNumber: Int) {
guard let lineInfo = textView.layoutManager.textLineForIndex(lineNumber - 1) else {
print("There are no characters/lineInfo \(#function)")
return
}
guard let lineString = textView.textStorage.substring(from: lineInfo.range) else {
print("There are no characters/lineString \(#function)")
return
}
let lineLastCharIndex = lineInfo.range.location + lineInfo.range.length - 1
let closeCommentLength = chars.count
let closeCommentRange = NSRange(
location: lineLastCharIndex - closeCommentLength,
length: closeCommentLength
)
let lastCharsInLine = textView.textStorage.substring(from: closeCommentRange)
// toggle comment off
if lastCharsInLine == chars {
textView.replaceCharacters(in: NSRange(
location: lineLastCharIndex - closeCommentLength,
length: closeCommentLength
), with: "")
}
// toggle comment on
else {
textView.replaceCharacters(in: NSRange(location: lineLastCharIndex, length: 0), with: chars)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import TextFormation
///
/// A view controller class for managing a source editor. Uses ``CodeEditTextView/TextView`` for input and rendering,
/// tree-sitter for syntax highlighting, and TextFormation for live editing completions.
///
public class TextViewController: NSViewController {
// swiftlint:disable:next line_length
public static let cursorPositionUpdatedNotification: Notification.Name = .init("TextViewController.cursorPositionNotification")
Expand Down

0 comments on commit dae56c8

Please sign in to comment.