CodeEditApp / CodeEditSourceEditor

A code editor view written in Swift powered by tree-sitter.
https://codeeditapp.github.io/CodeEditSourceEditor/documentation/codeeditsourceeditor
MIT License
510 stars 78 forks source link

New Feature: Compute the cursor position #134

Closed Eliulm closed 1 year ago

Eliulm commented 1 year ago

In essence, the line is calculated by dividing the y-position of the text segment with the cursor by the line height:

var line = Int(textSegmentFrame.maxY / textSegmentFrame.height)

However, this counts the preceding line wraps as lines too. As a result, I have to count the preceding line wraps with:

textLayoutManager.enumerateTextLayoutFragments(from: textLayoutManager.documentRange.location,
                                                           options: [.ensuresLayout, .ensuresExtraLineFragment]
            ) { textLayoutFragment in

                guard let cursorTextLineFragment = textLayoutManager.textLineFragment(at: insertionPointLocation)
                else { return false }

                /// Check whether the textLayoutFragment has line wraps
                if textLayoutFragment.textLineFragments.count > 1 {
                    for lineFragment in textLayoutFragment.textLineFragments {
                        lineWrapsCount += 1
                        /// Do not count lineFragments after the lineFragment where the cursor is placed
                        if lineFragment == cursorTextLineFragment { break }
                    }

                    /// The first lineFragment will be counted as an actual line
                        lineWrapsCount -= 1
                }

                if textLayoutFragment.textLineFragments.contains(cursorTextLineFragment) {
                    return false
                }
                return true
            }

Unfortunately, this does scale with the line count of the file. So we might want to change that if we can come up with a better alternative in the future. As a first implementation, I think it works.

thecoolwinter commented 1 year ago

This will break when #131 is fixed. Instead of enumerating the entire string, could we use insertionPointLocation to get the line and column directly? We already compute the visible location, so we'd get the line like: insertionPointLocation.nsRange(...).location - visibleRange.location and then compute the column by walking backwards from the insertionPointLocation until we hit a \n. A similar approach could be used in setCursorPosition.

Eliulm commented 1 year ago

This will break when #131 is fixed. Instead of enumerating the entire string, could we use insertionPointLocation to get the line and column directly? We already compute the visible location, so we'd get the line like: insertionPointLocation.nsRange(...).location - visibleRange.location and then compute the column by walking backwards from the insertionPointLocation until we hit a \n. A similar approach could be used in setCursorPosition.

Ok, so I guess that I have to wait until the new render method is implemented. I will definitely think about how I can get the updateCursor function to work in that case.

Should I discard this PR?

thecoolwinter commented 1 year ago

No, don't discard it it can stay open

thecoolwinter commented 1 year ago

Now that the viewport issue is fixed, it looks like there’s a better way to get the number of lines at the beginning of the visible area.

https://github.com/krzyzanowskim/STTextView/blob/7c378dca8e15902f7762d13bc1e33134a7729ed8/Sources/STTextView/STLineNumberRulerView.swift#L146

Let me know if you’re still willing to work on this, and thank you for being so patient while we got everything else finished.

Eliulm commented 1 year ago

Not a problem, I was occupied with exams anyways so I did not do too much there. I will work on this in the coming days.

Eliulm commented 1 year ago

@thecoolwinter So I just reimplemented updateCursorPosition() and it works much better now. However, I am having issues when the last empty line is edited since it is not immediately converted into a new textLineFragment. As a result, the line and column values become out of sync, until the cursor is moved one back:

https://user-images.githubusercontent.com/82230675/219975291-962d4f49-acda-41d6-aeb5-1d9e77ae1cd6.mov

I will see what I can do about it, but I am a bit clueless right now.

thecoolwinter commented 1 year ago

Oh that's a weird problem. Good catch though. I'll do some investigating too. Also, great work the changes look good!

Eliulm commented 1 year ago

@thecoolwinter I have found a workaround for the last line. I had to add a bit more code to get it to work and swiftlint is giving me function_boy_length errors now. Should I ignore it or move the cursor-specific code to a separate new folder?

austincondiff commented 1 year ago

What is the status of this? It has been open for over a month now and I want to make sure we aren't blocked.

Eliulm commented 1 year ago

It is working as expected. I just need to know, whether I should allocate all the cursor logic to a separate file/folder since I am getting SwiftLint function_boy_length errors now.

thecoolwinter commented 1 year ago

So sorry I missed the comments on this. You’ll have to move it to a new file. You can make a Controller folder and move STTextViewController.swift into there, and make a new STTextViewController+Cursor.swift file for the cursor additions.

Eliulm commented 1 year ago

No Problem, I will do it shortly.

Eliulm commented 1 year ago

@thecoolwinter Alright, I moved it over.

thecoolwinter commented 1 year ago

Just waiting for @lukepistrol to review b/c I can't merge the branch until all requested changes are resolved.