Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Text Exercise: Integrate the new Athena #174

Open
wants to merge 43 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
fb98ec4
Fix incorrect filters
terlan98 Aug 7, 2023
fe26424
Enable view-only exercises
terlan98 Aug 7, 2023
b35da64
Fix: exams are not updated when switching courses
terlan98 Aug 7, 2023
312c3b3
Create AthenaService; Fetch suggestions; Display special highlights f…
terlan98 Aug 14, 2023
aea1f8d
Make overlap calculation more efficient
terlan98 Aug 14, 2023
0656af1
Merge branch 'develop' into bugfix/courseview-grouping
maximiliansoelch Aug 21, 2023
cf5f994
Merge branch 'develop' into bugfix/courseview-grouping
terlan98 Aug 24, 2023
4a28cdf
Merge branch 'develop' into bugfix/courseview-grouping
terlan98 Aug 24, 2023
8afb36a
Fix SwiftLint error
terlan98 Aug 27, 2023
3274fb0
Merge remote-tracking branch 'refs/remotes/origin/bugfix/courseview-g…
terlan98 Aug 27, 2023
b282470
Create FeedbackSuggestion subtypes; Open sheet for adding suggested f…
terlan98 Aug 29, 2023
aad55cd
Make suggested feedbacks deletable; Add robot icon to suggested feedb…
terlan98 Aug 29, 2023
5b3515e
Merge branch 'bugfix/courseview-grouping' of https://github.com/ls1in…
terlan98 Aug 29, 2023
2073efe
Move suggestion fetching logic into TextAssessmentViewModel
terlan98 Aug 30, 2023
36304f5
Fix a SwiftLint error
terlan98 Aug 30, 2023
5cabbc4
Fix a SwiftLint warning
terlan98 Aug 30, 2023
a6c7ae5
Merge branch 'develop' into feature/text/new-athena
terlan98 Sep 5, 2023
e4fe1a4
Merge branch 'develop' into feature/text/new-athena
maximiliansoelch Sep 5, 2023
f644ebb
SwiftLint warning fix
terlan98 Sep 7, 2023
c3e74d2
Fix SwiftLint syntax error
terlan98 Sep 7, 2023
a95ff47
Merge branch 'develop' into feature/text/new-athena
terlan98 Sep 7, 2023
6cfdd86
Attempt to fix the SPM issue
terlan98 Sep 10, 2023
6cf502f
Merge remote-tracking branch 'refs/remotes/origin/feature/text/new-at…
terlan98 Sep 10, 2023
ea1c5bc
Fix warning
terlan98 Sep 10, 2023
ef04a23
Merge branch 'develop' of https://github.com/ls1intum/Themis into fea…
terlan98 Sep 19, 2023
25862e7
Merge branch 'develop' into feature/text/new-athena
maximiliansoelch Sep 27, 2023
115e982
Merge branch 'develop' of https://github.com/ls1intum/Themis into fea…
terlan98 Oct 10, 2023
223e00f
Merge remote-tracking branch 'refs/remotes/origin/develop'
terlan98 Oct 26, 2023
3754686
Adapt everything fo ther new server-side changes
terlan98 Oct 26, 2023
07a0b33
Add badge to feedback cell
terlan98 Oct 26, 2023
7d0754c
Merge branch 'develop' into feature/text/new-athena
maximiliansoelch Oct 27, 2023
b1bf578
remove unrelated files
terlan98 Oct 27, 2023
7329a5c
Merge remote-tracking branch 'refs/remotes/origin/feature/text/new-at…
terlan98 Oct 27, 2023
ff88b10
Merge remote-tracking branch 'refs/remotes/origin/develop'
terlan98 Oct 30, 2023
ff4002c
Fix 2 swiftlint warnings
terlan98 Oct 30, 2023
cb0f135
Merge remote-tracking branch 'refs/remotes/origin/develop'
terlan98 Nov 5, 2023
7716c4d
Fix package version and merge issues
terlan98 Nov 5, 2023
a2c9b37
Merge branch 'develop' into feature/text/new-athena
terlan98 Nov 9, 2023
2b6d47c
Merge branch 'develop' into feature/text/new-athena
terlan98 Nov 11, 2023
04ff421
Merge branch 'develop' into feature/text/new-athena
maximiliansoelch Nov 16, 2023
a001ea0
Merge branch 'develop' into feature/text/new-athena
maximiliansoelch Nov 20, 2023
54c7a69
Update CodeEditor's ArtemisCore dependency
terlan98 Nov 20, 2023
3ee9c61
Merge branch 'develop' into feature/text/new-athena
terlan98 Dec 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions CodeEditor/Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.3
// swift-tools-version:5.9

import PackageDescription

Expand All @@ -7,18 +7,19 @@ let package = Package(
name: "CodeEditor",

platforms: [
.macOS(.v10_15), .iOS(.v13)
.iOS(.v17)
],

products: [
.library(name: "CodeEditor", targets: [ "CodeEditor" ])
],

dependencies: [
.package(url: "https://github.com/raspu/Highlightr", from: "2.1.2")
.package(url: "https://github.com/raspu/Highlightr", from: "2.1.2"),
.package(url: "https://github.com/ls1intum/artemis-ios-core-modules", from: "7.0.0"),
],

targets: [
.target(name: "CodeEditor", dependencies: [ "Highlightr" ])
.target(name: "CodeEditor", dependencies: [ "Highlightr", .product(name: "SharedModels", package: "artemis-ios-core-modules")])
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// FeedbackSuggestion.swift
//
//
// Created by Tarlan Ismayilsoy on 29.08.23.
//

import Foundation
import SharedModels

public protocol FeedbackSuggestion: Equatable, Decodable {
var id: Int { get }
var exerciseId: Int { get }
var submissionId: Int { get }
var title: String { get }
var description: String { get }
var credits: Double { get }
var gradingInstruction: GradingInstruction? { get }

var associatedAssessmentFeedbackId: UUID? { get set } // not decoded
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
//
// FeedbackSuggestion.swift
// ProgrammingFeedbackSuggestion.swift
// Themis
//
// Created by Andreas Cselovszky on 25.01.23.
//

import Foundation
import SharedModels

public struct FeedbackSuggestion: Decodable, Equatable {
public let id = UUID()
public let exerciseId: Int
public struct ProgrammingFeedbackSuggestion: FeedbackSuggestion, Decodable {

public var id: Int

public var exerciseId: Int

public var submissionId: Int

public var title: String

public var description: String

public var credits: Double

public var gradingInstruction: GradingInstruction?

public var associatedAssessmentFeedbackId: UUID?

// TODO: rename/remove the fields below once programming suggestions are integrated into Athena
public let participationId: Int
public let srcFile: String
public let fromLine: Int
public let toLine: Int
public let text: String
public let credits: Double

enum DecodingKeys: String, CodingKey {
case exercise_id
Expand All @@ -27,14 +42,18 @@ public struct FeedbackSuggestion: Decodable, Equatable {
case credits
}

// TODO: correct the decoding logic below once programming suggestions are integrated into Athena
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: DecodingKeys.self)
id = Int.random(in: 1...999999)
exerciseId = try values.decode(Int.self, forKey: .exercise_id)
submissionId = -1
title = "Suggestion"
participationId = try values.decode(Int.self, forKey: .participation_id)
srcFile = try values.decode(String.self, forKey: .src_file)
fromLine = try values.decode(Int.self, forKey: .from_line)
toLine = try values.decode(Int.self, forKey: .to_line)
text = try values.decode(String.self, forKey: .text)
description = try values.decode(String.self, forKey: .text)
credits = try values.decode(Double.self, forKey: .credits)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//
// TextFeedbackSuggestion.swift
//
//
// Created by Tarlan Ismayilsoy on 29.08.23.
//

import Foundation
import SharedModels

public struct TextFeedbackSuggestion: FeedbackSuggestion {
public let id: Int

public let exerciseId: Int

public let submissionId: Int

public let title: String

public let description: String

public let credits: Double

public let gradingInstruction: GradingInstruction?

public var associatedAssessmentFeedbackId: UUID?

public let indexStart: Int?

public let indexEnd: Int?

public static func == (lhs: TextFeedbackSuggestion, rhs: TextFeedbackSuggestion) -> Bool {
lhs.id == rhs.id
&& lhs.exerciseId == rhs.exerciseId
&& lhs.submissionId == rhs.submissionId
&& lhs.title == rhs.title
&& lhs.description == rhs.description
&& lhs.credits == rhs.credits
}

public var textBlockContent: String?

public var isReferenced: Bool {
indexStart != nil && indexEnd != nil
}

public var textBlock: TextBlock? {
guard isReferenced else {
return nil
}
return TextBlock(submissionId: submissionId, text: textBlockContent, startIndex: indexStart, endIndex: indexEnd)
}

public var feedback: Feedback {
// TODO: grading instruction support

Check warning on line 55 in CodeEditor/Sources/CodeEditor/Models/Suggestion/TextFeedbackSuggestion.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Todo Violation: TODOs should be resolved (grading instruction support). (todo)
return Feedback(text: Feedback.feedbackSuggestionAcceptedIdentifier + title,
detailText: description,
reference: textBlock?.id,
credits: credits,
type: isReferenced ? .MANUAL : .MANUAL_UNREFERENCED,
positive: credits > 0)
}

public mutating func setTextBlockContent(from submission: TextSubmission) {
guard let indexStart,
let indexEnd,
let text = submission.text else {
return
}
let nsRange = (indexStart ..< indexEnd).toNSRange()
if let indexRange = Range(nsRange, in: text) {
textBlockContent = String(text[indexRange])
}
}
}

public extension Feedback {
static let feedbackSuggestionIdentifier = "FeedbackSuggestion:"
static let feedbackSuggestionAcceptedIdentifier = "FeedbackSuggestion:accepted:"
static let feedbackSuggestionAdaptedIdentifier = "FeedbackSuggestion:adapted:"

var isSuggested: Bool {
text?.hasPrefix(Self.feedbackSuggestionIdentifier) ?? false
}

var isSuggestedAndAccepted: Bool {
text?.hasPrefix(Self.feedbackSuggestionAcceptedIdentifier) ?? false
}

var isSuggestedAndAdapted: Bool {
text?.hasPrefix(Self.feedbackSuggestionAdaptedIdentifier) ?? false
}
}
25 changes: 25 additions & 0 deletions CodeEditor/Sources/CodeEditor/Models/TextBlockRef.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// TextBlockRef.swift
// Themis
//
// Created by Tarlan Ismayilsoy on 14.08.23.
//

import Foundation
import SharedModels

public struct TextBlockRef: Codable {
public var block: TextBlock
public var feedback: Feedback

public init(block: TextBlock, feedback: Feedback) {
self.block = block
self.feedback = feedback
}

private enum CodingKeys: String, CodingKey {
case block, feedback
}

public var associatedAssessmentFeedbackId: UUID? // not decoded
}
28 changes: 20 additions & 8 deletions CodeEditor/Sources/CodeEditor/RoundedCornerLayoutManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
let paraStyle = NSMutableParagraphStyle()
var diffLines = [Int]()
var isNewFile = false
var feedbackSuggestions = [FeedbackSuggestion]()
var feedbackSuggestions = [any FeedbackSuggestion]()

override init() {
super.init()
Expand All @@ -24,7 +24,7 @@
self.paraStyle.alignment = .right
}

required init?(coder: NSCoder) {

Check warning on line 27 in CodeEditor/Sources/CodeEditor/RoundedCornerLayoutManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Unavailable Function Violation: Unimplemented functions should be marked as unavailable. (unavailable_function)
fatalError("This has not been implemented (no body cares anyhow)")
}

Expand Down Expand Up @@ -61,13 +61,21 @@
lineRect.origin.y += containerOrigin.y

lineRect = lineRect.integral.insetBy(dx: 0.4, dy: 0.4)
/// The roundedReect is responsible for the round Corners
let path = UIBezierPath(roundedRect: lineRect, cornerRadius: height * 0.2)
path.fill()
if underlineVal == .thick {
lineRect.origin.y += height
lineRect.size.height = 1

let path = UIBezierPath(roundedRect: lineRect, cornerRadius: height * 0.2)
path.lineWidth = height * 0.1
path.stroke()
} else {
let path = UIBezierPath(roundedRect: lineRect, cornerRadius: height * 0.2)
path.fill()
}
}

private func numViewWidth() -> CGFloat {
if !showsLineNumbers { return 0.0 }

Check warning on line 78 in CodeEditor/Sources/CodeEditor/RoundedCornerLayoutManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Conditional Returns on Newline Violation: Conditional statements should always return on the next line (conditional_returns_on_newline)
let maxNum = 4.0

let fontAttributes = [NSAttributedString.Key.font: lineNumberFont]
Expand Down Expand Up @@ -95,7 +103,7 @@
return paraNumber
} else {
let code = textStorage?.string ?? ""
let ns = NSString(string: code)

Check warning on line 106 in CodeEditor/Sources/CodeEditor/RoundedCornerLayoutManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Identifier Name Violation: Variable name should be between 3 and 40 characters long: 'ns' (identifier_name)
var paraNumber = lastParaNumber
ns.enumerateSubstrings(in: NSRange(location: lastParaLocation, length: charRange.location - lastParaLocation),
options: [.byParagraphs, .substringNotRequired]) { _, _, enclosingRange, stop in
Expand All @@ -110,7 +118,7 @@
}
}

override func processEditing(for textStorage: NSTextStorage,

Check warning on line 121 in CodeEditor/Sources/CodeEditor/RoundedCornerLayoutManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Multiline Parameters Violation: Functions and methods parameters should be either on the same line, or one per line. (multiline_parameters)
edited editMask: NSTextStorage.EditActions,
range newCharRange: NSRange, changeInLength delta: Int,
invalidatedRange invalidatedCharRange: NSRange) {
Expand All @@ -124,13 +132,13 @@

override func drawBackground(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
super.drawBackground(forGlyphRange: glyphsToShow, at: origin)
let atts: [NSAttributedString.Key: Any] = [.font: lineNumberFont,

Check warning on line 135 in CodeEditor/Sources/CodeEditor/RoundedCornerLayoutManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Multiline Literal Brackets Violation: Multiline literals should have their surrounding brackets in a new line. (multiline_literal_brackets)
.foregroundColor: lineNumberTextColor,
.paragraphStyle: paraStyle]

Check warning on line 137 in CodeEditor/Sources/CodeEditor/RoundedCornerLayoutManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Multiline Literal Brackets Violation: Multiline literals should have their surrounding brackets in a new line. (multiline_literal_brackets)
var gutterRect = CGRect.zero
var paraNumber = 0
let ctx = UIGraphicsGetCurrentContext()
guard let ctx else { return }

Check warning on line 141 in CodeEditor/Sources/CodeEditor/RoundedCornerLayoutManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Conditional Returns on Newline Violation: Conditional statements should always return on the next line (conditional_returns_on_newline)
UIGraphicsPushContext(ctx)

enumerateLineFragments(forGlyphRange: glyphsToShow) { rect, _, _, glyphRange, _ in
Expand All @@ -148,12 +156,12 @@
}
}
self.drawDiffLines(paraNumber, rect, origin)
self.drawFeedbackSuggestions(paraNumber, rect, origin)
self.drawProgrammingFeedbackSuggestions(paraNumber, rect, origin)
}
UIGraphicsPopContext()

if NSMaxRange(glyphsToShow) > numberOfGlyphs {
let ln = String(format: "%ld", paraNumber + 2)

Check warning on line 164 in CodeEditor/Sources/CodeEditor/RoundedCornerLayoutManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Identifier Name Violation: Variable name should be between 3 and 40 characters long: 'ln' (identifier_name)
let size = (ln as NSString).size(withAttributes: atts)

gutterRect = gutterRect.offsetBy(dx: 0, dy: gutterRect.height)
Expand All @@ -178,7 +186,7 @@

private func drawDiffLines(_ paraNumber: Int, _ rect: CGRect, _ origin: CGPoint) {
let ctx = UIGraphicsGetCurrentContext()
guard let ctx else { return }

Check warning on line 189 in CodeEditor/Sources/CodeEditor/RoundedCornerLayoutManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Conditional Returns on Newline Violation: Conditional statements should always return on the next line (conditional_returns_on_newline)
UIGraphicsPushContext(ctx)
ctx.setFillColor(CGColor(red: 0, green: 0.9, blue: 0.1, alpha: 0.2))
ctx.setStrokeColor(CGColor(red: 0, green: 0.9, blue: 0.1, alpha: 0.2))
Expand All @@ -190,13 +198,17 @@
UIGraphicsPopContext()
}

private func drawFeedbackSuggestions(_ paraNumber: Int, _ rect: CGRect, _ origin: CGPoint) {
private func drawProgrammingFeedbackSuggestions(_ paraNumber: Int, _ rect: CGRect, _ origin: CGPoint) {
let ctx = UIGraphicsGetCurrentContext()
guard let ctx else { return }
guard let ctx else {
return
}
UIGraphicsPushContext(ctx)
ctx.setFillColor(CGColor(red: 0, green: 0.2, blue: 0.8, alpha: 0.8))
ctx.setStrokeColor(CGColor(red: 0, green: 0.2, blue: 0.8, alpha: 0.8))
if self.feedbackSuggestions.contains(where: { paraNumber + 1 >= $0.fromLine && paraNumber + 1 <= $0.toLine }) {

let programmingSuggestions = feedbackSuggestions.compactMap({ $0 as? ProgrammingFeedbackSuggestion })
if programmingSuggestions.contains(where: { paraNumber + 1 >= $0.fromLine && paraNumber + 1 <= $0.toLine }) {
let path = CGPath(
rect: CGRect(
x: rect.origin.x,
Expand Down
28 changes: 14 additions & 14 deletions CodeEditor/Sources/CodeEditor/UXCodeTextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

var highlightedRanges: [HighlightedRange] = []
var dragSelection: Range<Int>?
var feedbackSuggestions: [FeedbackSuggestion] = []
var feedbackSuggestions: [ProgrammingFeedbackSuggestion] = []
var showsLineNumbers: Bool

private var firstPoint: CGPoint?
Expand Down Expand Up @@ -409,7 +409,8 @@
layoutManager.enumerateLineFragments(forGlyphRange: layoutManager.glyphRange(for: textContainer)) { rect, _, _, _, _ in
let offset = self.calculateWrapOffsetOf(lineNumber)
if let feedback = self.feedbackSuggestions.first(where: { $0.fromLine == lineNumber - offset }) {
if let lightbulb = self.buildLightbulbButton(rect: rect, feedbackId: feedback.id.uuidString) {
// TODO: get rid of the string interpolation once programming exercise suggestions are integrated into Athena
if let lightbulb = self.buildLightbulbButton(rect: rect, feedbackId: "\(feedback.id)") {
self.lightBulbs.append(lightbulb)
self.addSubview(lightbulb)
}
Expand Down Expand Up @@ -446,12 +447,7 @@
guard text.hasRange(hRange.range) else {
continue
}
self.textStorage.addAttributes(
[
.underlineStyle: NSUnderlineStyle.single.rawValue,
.underlineColor: hRange.color,
.link: hRange.id.uuidString // equals feedback id
], range: hRange.range)
addUnderlineAttribute(for: hRange)
}
}
}
Expand All @@ -462,16 +458,20 @@
guard text.hasRange(hRange.range) else {
continue
}
self.textStorage.addAttributes(
[
.underlineStyle: NSUnderlineStyle.single.rawValue,
.underlineColor: hRange.color,
.link: hRange.id.uuidString // equals feedback id
], range: hRange.range)
addUnderlineAttribute(for: hRange)
}
}
}

private func addUnderlineAttribute(for highlightedRange: HighlightedRange) {
self.textStorage.addAttributes(
[
.underlineStyle: highlightedRange.isSuggested ? NSUnderlineStyle.thick.rawValue : NSUnderlineStyle.single.rawValue,
.underlineColor: highlightedRange.color,
.link: highlightedRange.id.uuidString // equals feedback id
], range: highlightedRange.range)
}


private func getGlyphIndex(textView: UXCodeTextView, point: CGPoint) -> Int {
let point = CGPoint(x: point.x, y: point.y - (self.font?.pointSize ?? 0.0) / 2.0)
Expand Down Expand Up @@ -615,7 +615,7 @@

extension UXTextView {
/// Tries to find a range enclosing either the given position, it's left neighbors, or it's right neighbors
func rangeEnclosingApproximatePosition(_ position: UITextPosition, with granularity: UITextGranularity, inDirection direction: UITextDirection) -> UITextRange? {

Check failure on line 618 in CodeEditor/Sources/CodeEditor/UXCodeTextView.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Line Length Violation: Line should be 150 characters or less: currently 165 characters (line_length)

// We check the position itself, followed by the left and right neighbors
for offset in [0, -1, -2, -3, 1, 2, 3] {
Expand Down
4 changes: 2 additions & 2 deletions CodeEditor/Sources/CodeEditor/Utils/EditorBindings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public struct EditorBindings {
public var diffLines: [Int]
public var isNewFile: Bool
public var showsLineNumbers: Bool
public var feedbackSuggestions: [FeedbackSuggestion]
public var feedbackSuggestions: [ProgrammingFeedbackSuggestion]
public var selectedFeedbackSuggestionId: Binding<String>

public init(source: Binding<String>,
Expand All @@ -46,7 +46,7 @@ public struct EditorBindings {
diffLines: [Int] = [],
isNewFile: Bool = false,
showsLineNumbers: Bool = true,
feedbackSuggestions: [FeedbackSuggestion],
feedbackSuggestions: [ProgrammingFeedbackSuggestion],
selectedFeedbackSuggestionId: Binding<String>
) {
self.source = source
Expand Down
Loading