234234 # doc-content li {
235235 margin-bottom : .4rem ;
236236 }
237+
238+ # code-content pre {
239+ margin : 0 ;
240+ background : transparent;
241+ }
242+
243+ # code-content [data-stim-id ] {
244+ border-left : 2px solid transparent;
245+ padding-left : .5rem ;
246+ transition : border-color .15s ;
247+ }
248+
249+ # code-content [data-stim-id ]: hover {
250+ border-left-color : var (--border );
251+ }
237252 </ style >
238253</ head >
239254
296311 let _selectedStimId = null ;
297312 let _docResponse = '' ;
298313 let _diagramResponse = '' ;
314+ let _codeResponse = '' ;
315+
299316 function selectStim ( el , id ) {
300317 document . querySelectorAll ( '.stim-clickable' ) . forEach ( c => c . classList . remove ( 'selected' ) ) ;
301318 el . classList . add ( 'selected' ) ;
434451 return {
435452 type : jsPsychHtmlButtonResponse ,
436453 stimulus : `
437- <div style="padding-top:10rem;">
454+ <div style="padding-top:3rem;">
455+ <p style="font-size:1rem; color:var(--text-muted); margin-bottom:1rem;">Using the following diagram, answer the prompt below.</p>
456+
438457 <div style="position:relative; width:100%; margin-bottom:1.5rem;">
439458 <img
440459 id="diag-img"
530549 }
531550
532551
552+ function codeTrial ( { codeSrc, question, data = { } } ) {
553+ let responseText = '' ;
554+
555+ return {
556+ type : jsPsychHtmlButtonResponse ,
557+ stimulus : `
558+ <div style="padding-top:3rem;">
559+ <p style="font-size:1rem; color:var(--text-muted); margin-bottom:1rem;">
560+ Read the following source code, then answer the question below.
561+ </p>
562+ <div id="code-content" style="
563+ background:var(--surface);
564+ border:1px solid var(--border);
565+ border-radius:var(--radius);
566+ box-shadow:var(--shadow);
567+ padding:1.5rem 2rem;
568+ margin-bottom:1.5rem;
569+ font-size:.9rem;
570+ text-align:left;
571+ "></div>
572+ <p class="trial-question">${ question } </p>
573+ <textarea
574+ id="code-response"
575+ rows="5"
576+ style="
577+ width:100%; padding:.75rem 1rem; font-size:.95rem;
578+ font-family:inherit; border:1px solid var(--border);
579+ border-radius:var(--radius); resize:vertical; margin-bottom:1rem;
580+ "
581+ placeholder="Type your response here…"
582+ ></textarea>
583+ </div>
584+ ` ,
585+ choices : [ 'Submit' ] ,
586+ data : { trial_type_custom : 'code' , ...data } ,
587+ on_load ( ) {
588+ fetch ( codeSrc )
589+ . then ( r => r . text ( ) )
590+ . then ( src => {
591+ const blocks = parseJavaBlocks ( src ) ;
592+ const container = document . getElementById ( 'code-content' ) ;
593+ container . innerHTML = blocks . map ( b => `
594+ <div data-stim-id="${ b . id } " style="margin-bottom:.25rem;">
595+ <pre style="margin:0;"><code class="language-java">${ escapeHtml ( b . code ) } </code></pre>
596+ </div>
597+ ` ) . join ( '' ) ;
598+ container . querySelectorAll ( 'code' ) . forEach ( el => hljs . highlightElement ( el ) ) ;
599+ } ) ;
600+ const ta = document . getElementById ( 'code-response' ) ;
601+ ta . addEventListener ( 'input' , ( ) => { _codeResponse = ta . value ; } ) ;
602+ } ,
603+ on_finish ( trialData ) {
604+ trialData . free_response = _codeResponse ;
605+ snapshotStimRects ( jsPsych ) ;
606+ } ,
607+ } ;
608+ }
609+
610+ function escapeHtml ( str ) {
611+ return str
612+ . replace ( / & / g, '&' )
613+ . replace ( / < / g, '<' )
614+ . replace ( / > / g, '>' ) ;
615+ }
616+
617+ function parseJavaBlocks ( src ) {
618+ const lines = src . split ( '\n' ) ;
619+ const blocks = [ ] ;
620+ let i = 0 ;
621+
622+ // ── Block 1: everything up to the first method/constructor ──────────
623+ // Collect imports, class declaration, and field declarations
624+ const headerLines = [ ] ;
625+ while ( i < lines . length ) {
626+ const trimmed = lines [ i ] . trim ( ) ;
627+ // Stop when we hit a Javadoc comment or a method-looking line
628+ if ( trimmed . startsWith ( '/**' ) || isMethodOrConstructorStart ( lines , i ) ) break ;
629+ headerLines . push ( lines [ i ] ) ;
630+ i ++ ;
631+ }
632+ if ( headerLines . some ( l => l . trim ( ) ) ) {
633+ blocks . push ( { id : 'code-header' , code : headerLines . join ( '\n' ) . trim ( ) } ) ;
634+ }
635+
636+ // ── Remaining blocks: Javadoc + method pairs ─────────────────────────
637+ while ( i < lines . length ) {
638+ const trimmed = lines [ i ] . trim ( ) ;
639+
640+ // Javadoc comment block
641+ if ( trimmed . startsWith ( '/**' ) ) {
642+ const commentLines = [ ] ;
643+ while ( i < lines . length ) {
644+ commentLines . push ( lines [ i ] ) ;
645+ if ( lines [ i ] . trim ( ) . endsWith ( '*/' ) ) { i ++ ; break ; }
646+ i ++ ;
647+ }
648+ // Peek ahead to get method name for the ID
649+ const methodName = peekMethodName ( lines , i ) ;
650+ blocks . push ( {
651+ id : `code-comment-${ methodName } ` ,
652+ code : commentLines . join ( '\n' ) . trim ( ) ,
653+ } ) ;
654+ continue ;
655+ }
656+
657+ // Method or constructor body
658+ if ( isMethodOrConstructorStart ( lines , i ) ) {
659+ const methodName = extractMethodName ( lines [ i ] ) ;
660+ const methodLines = [ ] ;
661+ let braceDepth = 0 ;
662+ let started = false ;
663+ while ( i < lines . length ) {
664+ const line = lines [ i ] ;
665+ methodLines . push ( line ) ;
666+ for ( const ch of line ) {
667+ if ( ch === '{' ) { braceDepth ++ ; started = true ; }
668+ if ( ch === '}' ) braceDepth -- ;
669+ }
670+ i ++ ;
671+ if ( started && braceDepth === 0 ) break ;
672+ }
673+ blocks . push ( {
674+ id : `code-method-${ methodName } ` ,
675+ code : methodLines . join ( '\n' ) . trim ( ) ,
676+ } ) ;
677+ continue ;
678+ }
679+
680+ // Skip blank lines between blocks
681+ i ++ ;
682+ }
683+
684+ return blocks ;
685+ }
686+
687+ function isMethodOrConstructorStart ( lines , i ) {
688+ const line = lines [ i ] . trim ( ) ;
689+ if ( ! line ) return false ;
690+ // Annotations like @Override don't count on their own
691+ if ( line . startsWith ( '@' ) ) return false ;
692+ // Must contain ( and either end with { or have { somewhere
693+ // Look ahead a few lines for the opening brace
694+ const chunk = lines . slice ( i , i + 4 ) . join ( ' ' ) ;
695+ return / [ \w < > \[ \] ] + \s + \w + \s * \( / . test ( chunk ) && chunk . includes ( '{' ) ;
696+ }
697+
698+ function extractMethodName ( line ) {
699+ // Match: [modifiers] [returnType] methodName(
700+ const match = line . match ( / (?: p u b l i c | p r i v a t e | p r o t e c t e d | s t a t i c | f i n a l | o v e r r i d e | \s ) * [ \w < > \[ \] ] + \s + ( \w + ) \s * \( / ) ;
701+ if ( match ) return match [ 1 ] ;
702+ // Constructor fallback
703+ const ctorMatch = line . match ( / (?: p u b l i c | p r i v a t e | p r o t e c t e d ) ? \s + ( \w + ) \s * \( / ) ;
704+ if ( ctorMatch ) return ctorMatch [ 1 ] ;
705+ return 'unknown' ;
706+ }
707+
708+ function peekMethodName ( lines , i ) {
709+ for ( let j = i ; j < Math . min ( i + 5 , lines . length ) ; j ++ ) {
710+ const name = extractMethodName ( lines [ j ] ) ;
711+ if ( name !== 'unknown' ) return name ;
712+ }
713+ return 'unknown' ;
714+ }
715+
716+
533717 // ═══════════════════════════════════════════════════════════════
534718 // TIMELINE ← Edit everything below here
535719 // ═══════════════════════════════════════════════════════════════
548732 stimulus : `
549733 <div class="instructions-body stim-card">
550734 <h2>Welcome</h2>
551- <p>Thank you for participating in this study. You will be shown a series of stimuli and asked to make judgments about them.</p>
552- <p>Your responses will be recorded and downloaded at the end of the session. The study takes approximately <strong>X minutes</strong> .</p>
735+ <p>Thank you for participating in this study. You will be shown a series of software engineering related stimuli and asked questions about them.</p>
736+ <p>Your responses will be recorded and downloaded at the end of the session. Your eyes will be tracked during this study .</p>
553737 <p>Click <strong>Begin</strong> when you are ready.</p>
554738 </div>
555739 ` ,
556740 choices : [ 'Begin' ] ,
557741 } ) ;
558742
559743 // ── 2. Trials ──────────────────────────────────────────────────
560- // Example A: single text stimulus with button response
561- timeline . push ( singleButtonTrial ( {
562- stim : {
563- id : 'trial1-text' ,
564- type : 'text' ,
565- content : 'This is an example text stimulus. Replace with your actual content.' ,
566- label : 'Passage' ,
567- } ,
568- question : 'How difficult was this text to understand?' ,
569- buttons : [ 'Very easy' , 'Easy' , 'Difficult' , 'Very difficult' ] ,
570- data : { condition : 'text-example' } ,
571- } ) ) ;
572744
573- // DIAGRAM (Working)
745+ // DIAGRAM
574746 timeline . push ( diagramTrial ( {
575747 src : './tasks/diagram/class-example-library-domain.png' ,
576748 aois : DIAGRAM_AOIS ,
@@ -585,6 +757,15 @@ <h2>Welcome</h2>
585757 data : { condition : 'doc-chromium-profile' } ,
586758 } ) ) ;
587759
760+
761+ // CODE SUMMARY
762+ timeline . push ( codeTrial ( {
763+ codeSrc : './tasks/code/CircularFifoQueue.java' ,
764+ question : 'In your own words, describe what the <code>add()</code> method does when the queue is already at full capacity. Be specific about what happens to existing elements and how the internal state changes.' ,
765+ data : { condition : 'code-circular-fifo' } ,
766+ } ) ) ;
767+
768+
588769 // Example C: click-to-select between two images
589770 timeline . push ( clickSelectTrial ( {
590771 stims : [
0 commit comments