• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

thoni56 / c-xrefactory / 1766

02 Apr 2026 02:45PM UTC coverage: 82.708% (+0.008%) from 82.7%
1766

push

travis-ci

thoni56
[fix][normalize] Always remove trailing '.' when normalizing

15674 of 18951 relevant lines covered (82.71%)

15510638.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.31
src/server.c
1
#include "server.h"
2

3
#include <string.h>
4

5
#include "commons.h"
6
#include "complete.h"
7
#include "cxfile.h"
8
#include "cxref.h"
9
#include "editor.h"
10
#include "editorbuffer.h"
11
#include "editorbuffertable.h"
12
#include "filedescriptor.h"
13
#include "fileio.h"
14
#include "filetable.h"
15
#include "globals.h"
16
#include "head.h"
17
#include "log.h"
18
#include "misc.h"
19
#include "navigation.h"
20
#include "options.h"
21
#include "parsers.h"
22
#include "parsing.h"
23
#include "ppc.h"
24
#include "progress.h"
25
#include "projectstructure.h"
26
#include "referenceableitemtable.h"
27
#include "referencerefresh.h"
28
#include "session.h"
29
#include "startup.h"
30
#include "yylex.h"
31

32

33
const char *operationNamesTable[] = {
34
    ALL_OPERATION_ENUMS(GENERATE_ENUM_STRING)
35
};
36

37

38
static bool needsReferenceDatabase(ServerOperation operation) {
1,189✔
39
    return operation==OP_BROWSE_PUSH
40
        ||  operation==OP_BROWSE_PUSH_ONLY
1,048✔
41
        ||  operation==OP_BROWSE_PUSH_AND_CALL_MACRO
1,039✔
42
        ||  operation==OP_INTERNAL_PARSE_TO_GOTO_PARAM_NAME
1,035✔
43
        ||  operation==OP_INTERNAL_PARSE_TO_GET_PARAM_COORDINATES
1,002✔
44
        ||  operation==OP_GET_AVAILABLE_REFACTORINGS
906✔
45
        ||  operation==OP_BROWSE_PUSH_NAME
877✔
46
        ||  operation==OP_INTERNAL_PUSH_FOR_USAGE_CHECK
872✔
47
        ||  operation==OP_UNUSED_GLOBAL
842✔
48
        ||  operation==OP_UNUSED_LOCAL
839✔
49
        ||  operation==OP_INTERNAL_LIST
834✔
50
        ||  operation==OP_INTERNAL_PUSH_FOR_RENAME
834✔
51
        ||  operation==OP_INTERNAL_PUSH_FOR_ARGUMENT_MANIPULATION
789✔
52
        ||  operation==OP_INTERNAL_SAFETY_CHECK
744✔
53
        ||  operation==OP_INTERNAL_GET_FUNCTION_BOUNDS
705✔
54
        ||  operation==OP_REFACTORY
2,237✔
55
        ;
56
}
57

58
static bool requiresProcessingInputFile(ServerOperation operation) {
515✔
59
    return operation==OP_COMPLETION
60
           || operation==OP_INTERNAL_PARSE_TO_EXTRACT
489✔
61
           || operation==OP_SEARCH
477✔
62
           || operation==OP_INTERNAL_PARSE_TO_SET_MOVE_TARGET
475✔
63
           || operation==OP_INTERNAL_GET_FUNCTION_BOUNDS
468✔
64
           || operation==OP_GET_ENV_VALUE
461✔
65
           || needsReferenceDatabase(operation)
1,004✔
66
        ;
67
}
68

69

70
static int scheduleFileUsingTheMacro(void) {
1✔
71
    SessionStackEntry *tmpc;
72

73
    assert(completionStringInMacroBody);
1✔
74
    tmpc = NULL;
1✔
75
    ReferenceableItem references = makeReferenceableItem(completionStringInMacroBody, TypeMacro, StorageExtern,
76
                                                         GlobalScope, VisibilityGlobal, NO_FILE_NUMBER);
77

1✔
78
    BrowsingMenu menu = makeBrowsingMenu(references, 1, true, 0, UsageUsed, UsageNone, NO_POSITION);
1✔
79
    if (sessionData.browsingStack.top==NULL) {
×
80
        pushEmptySession(&sessionData.browsingStack);
×
81
        tmpc = sessionData.browsingStack.top;
82
    }
83

1✔
84
    assert(sessionData.browsingStack.top);
1✔
85
    BrowsingMenu *oldMenu = sessionData.browsingStack.top->hkSelectedSym;
1✔
86
    sessionData.browsingStack.top->hkSelectedSym = &menu;
1✔
87
    fileToParseForMacroExpansion = NO_FILE_NUMBER;
1✔
88
    scanForMacroUsage(completionStringInMacroBody);
1✔
89
    sessionData.browsingStack.top->hkSelectedSym = oldMenu;
×
90
    if (tmpc!=NULL) {
91
        deleteEntryFromSessionStack(tmpc);
1✔
92
    }
1✔
93
    log_debug(":scheduling file '%s'", getFileItemWithFileNumber(fileToParseForMacroExpansion)->name);
94
    return fileToParseForMacroExpansion;
95
}
96

97
/* Prepare a single input file for this request, maybe?
98
 *
99
 * TODO This function does something but the logic is seriously broken so it is
100
 * impossible to improve further until that mystery is sorted.
502✔
101
 */
502✔
102
static bool prepareInputFileForRequest(void) {
103
    fileProcessingStartTime = time(NULL);
104

105
    // Server mode: get a single scheduled file for this request
502✔
106
    // TODO: why is it picking the first scheduled in fileNumber order?
502✔
107
    int fileNumber = 0;
108
    if (getNextScheduledFile(&fileNumber) == NULL) { /* No more input files... */
21✔
109
        // No file scheduled - likely the operation doesn't need one, or error
21✔
110
        requestFileNumber = NO_FILE_NUMBER;
111
        return false;
112
    }
113

114
    assert(getFileItemWithFileNumber(fileNumber)->isScheduled);
115

116
    /* Ensure only this file is processed during this request */
3,516✔
117
    /* TODO: this is clearing all fileItems with a fileNumber above the one found
3,035✔
118
     * above. That just doesn't make sense... */
3,035✔
119
    for (int i=getNextExistingFileNumber(fileNumber+1); i != -1; i = getNextExistingFileNumber(i+1)) {
1✔
120
        FileItem *fileItem = getFileItemWithFileNumber(i);
121
        if (fileItem->isScheduled) {
3,035✔
122
            log_trace("Found unexpected scheduled file, '%s', unscheduling it.", fileItem->name);
123
        }
124
        fileItem->isScheduled = false;
481✔
125
    }
481✔
126

127
    requestFileNumber = fileNumber;
128
    return true;
269✔
129
}
269✔
130

269✔
131
static void closeInputFile(void) {
132
    if (currentFile.characterBuffer.file != stdin) {
269✔
133
        closeCharacterBuffer(&currentFile.characterBuffer);
134
    }
269✔
135
}
269✔
136

×
137
static void parseInputFile(void) {
138
    if (options.fileTrace)
139
        fprintf(stderr, "parseInputFile: '%s\n", currentFile.fileName);
269✔
140

141
    /* Bridge: Sync parsingConfig for all operations using this entry point */
269✔
142
    setupParsingConfig(requestFileNumber);
265✔
143

265✔
144
    if (options.serverOperation != OP_SEARCH && options.serverOperation != OP_BROWSE_PUSH_NAME) {
265✔
145
        log_debug("parse start");
146
        callParser(parsingConfig.fileNumber, parsingConfig.language);
4✔
147
        log_debug("parse end");
148
    } else
269✔
149
        log_debug("Not parsing input because of server operation TAG_SEARCH or PUSH_NAME");
269✔
150

151
    closeInputFile();
612✔
152
}
612✔
153

612✔
154
void initServer(ArgumentsVector args) {
503✔
155
    clearAvailableRefactorings();
503✔
156
    processOptions(args, PROCESS_FILE_ARGUMENTS_YES); /* no include or define options */
503✔
157
    processFileArguments();
158
    initCompletions(&collectedCompletions, 0, NO_POSITION);
268✔
159
}
268✔
160

161
static void singlePass(ArgumentsVector args, ArgumentsVector nargs) {
268✔
162
    bool inputOpened = false;
163

268✔
164
    inputOpened = initializeFileProcessing(args, nargs);
268✔
165

166
    loadFileNumbersFromStore();
268✔
167
    parsingConfig.fileNumber = currentFile.characterBuffer.fileNumber;
168

268✔
169
    if (inputOpened) {
268✔
170
        /* If the file has preloaded content, remove old references before parsing */
24✔
171
        EditorBuffer *buffer = getOpenedAndLoadedEditorBuffer(inputFileName);
172
        if (buffer != NULL && buffer->preLoadedFromFile != NULL) {
24✔
173
            log_debug("file has preloaded content, removing old references for file %d",
174
                      parsingConfig.fileNumber);
175
            removeReferenceableItemsForFile(parsingConfig.fileNumber);
268✔
176
        }
177

178
        parseInputFile();
179

180
        /* Mark file as freshly parsed so navigation doesn't think it's stale.
181
         * But NOT for refactoring operations (RENAME, ARGUMENT_MANIPULATION, SAFETY_CHECK)
182
         * because those trigger xref updates that use lastParsedMtime to decide
268✔
183
         * whether to re-index - and xref reads from disk, not the preload buffer.
24✔
184
         */
24✔
185
        if (buffer != NULL && buffer->preLoadedFromFile != NULL
23✔
186
            && options.serverOperation != OP_INTERNAL_PUSH_FOR_RENAME
23✔
187
            && options.serverOperation != OP_INTERNAL_PUSH_FOR_ARGUMENT_MANIPULATION
23✔
188
            && options.serverOperation != OP_INTERNAL_SAFETY_CHECK) {
189
            FileItem *fileItem = getFileItemWithFileNumber(parsingConfig.fileNumber);
190
            fileItem->lastParsedMtime = buffer->modificationTime;
268✔
191
        }
192
    }
6✔
193
    if (options.cursorOffset == 0) {
5✔
194
        // special case, push the file as include reference
5✔
195
        if (needsReferenceDatabase(options.serverOperation)) {
5✔
196
            Position position = makePosition(parsingConfig.fileNumber, 1, 0);
197
            parsingConfig.positionOfSelectedReference = position;
198
            addFileAsIncludeReference(parsingConfig.fileNumber);
268✔
199
        }
200
    }
201
    if (completionPositionFound && !completionStringServed) {
202
        // Cursor is on an identifier inside a macro body definition, which hasn't been
1✔
203
        // processed as a symbol yet. Find and parse a file where the macro is invoked
1✔
204
        // so the macro expansion will resolve the identifier as an actual symbol.
1✔
205
        int fileWithMacroExpansion = scheduleFileUsingTheMacro();
1✔
206
        if (fileWithMacroExpansion!=NO_FILE_NUMBER) {
1✔
207
            inputFileName = getFileItemWithFileNumber(fileWithMacroExpansion)->name;
1✔
208
            inputOpened = initializeFileProcessing(args, nargs);
209
            if (inputOpened) {
210
                parseInputFile();
211
            }
268✔
212
        }
213
    }
268✔
214
}
268✔
215

216
static void processFile(ArgumentsVector baseArgs, ArgumentsVector requestArgs) {
268✔
217
    FileItem *fileItem = getFileItemWithFileNumber(requestFileNumber);
501✔
218

268✔
219
    assert(fileItem->isScheduled);
268✔
220
    maxPasses = 1;
268✔
221
    for (currentPass=1; currentPass<=maxPasses; currentPass++) {
222
        inputFileName = fileItem->name;
223
        assert(inputFileName!=NULL);
268✔
224
        singlePass(baseArgs, requestArgs);
268✔
225
        if (options.serverOperation==OP_INTERNAL_PARSE_TO_EXTRACT || (completionStringServed && !needsReferenceDatabase(options.serverOperation)))
226
            break;
227
    }
228
    fileItem->isScheduled = false;
229
}
230

231
#define MAX_INCLUDE_WALK_FILES 256
232
#define MAX_CUS_TO_REPARSE 128
233

599✔
234
/* Walk the reverse-include graph from a stale header up to compilation units,
235
 * using TypeCppInclude references in the reference table (populated by prior
599✔
236
 * parsing or loaded from disk db). Collect CU file numbers into the provided
599✔
237
 * array, deduplicating against entries already present. */
238
static int collectIncludersOfStaleHeader(int headerFileNumber,
599✔
239
                                         int cuFileNumbers[], int cuCount, int maxCUs) {
240
    FileItem *headerItem = getFileItemWithFileNumber(headerFileNumber);
241
    log_debug("Looking for CUs that include stale header '%s'", headerItem->name);
242

243
    ensureReferencesAreLoadedFor(LINK_NAME_INCLUDE_REFS);
244

599✔
245
    /* Walk reverse-include graph transitively: starting from the stale header,
599✔
246
     * find all files that include it, then files that include those, etc.
247
     * Collect any CUs encountered along the way. */
3,341✔
248
    int filesToWalk[MAX_INCLUDE_WALK_FILES];
2,742✔
249
    int walkCount = 1;
250
    filesToWalk[0] = headerFileNumber;
251

252
    for (int i = 0; i < walkCount; i++) {
253
        ReferenceableItem searchItem = makeReferenceableItem(
2,742✔
254
            LINK_NAME_INCLUDE_REFS, TypeCppInclude, StorageExtern,
453✔
255
            GlobalScope, VisibilityGlobal, filesToWalk[i]);
256

7,766✔
257
        ReferenceableItem *found;
5,477✔
258
        if (!isMemberInReferenceableItemTable(&searchItem, NULL, &found))
259
            continue;
260

5,477✔
261
        for (Reference *r = found->references; r != NULL; r = r->next) {
43,098✔
262
            int includerFileNum = r->position.file;
40,955✔
263

3,334✔
264
            /* Already in our walk list? */
3,334✔
265
            bool alreadySeen = false;
266
            for (int j = 0; j < walkCount; j++) {
267
                if (filesToWalk[j] == includerFileNum) {
5,477✔
268
                    alreadySeen = true;
3,334✔
269
                    break;
270
                }
2,143✔
271
            }
2,143✔
272
            if (alreadySeen)
273
                continue;
2,143✔
274

2,143✔
275
            if (walkCount < MAX_INCLUDE_WALK_FILES)
276
                filesToWalk[walkCount++] = includerFileNum;
495✔
277

598✔
278
            FileItem *includer = getFileItemWithFileNumber(includerFileNum);
515✔
279
            if (isCompilationUnit(includer->name)) {
412✔
280
                /* Deduplicate against CUs already collected (from other stale headers) */
412✔
281
                bool alreadyCollected = false;
282
                for (int j = 0; j < cuCount; j++) {
283
                    if (cuFileNumbers[j] == includerFileNum) {
495✔
284
                        alreadyCollected = true;
83✔
285
                        break;
83✔
286
                    }
287
                }
288
                if (!alreadyCollected && cuCount < maxCUs) {
289
                    cuFileNumbers[cuCount++] = includerFileNum;
290
                    log_debug("CU '%s' (transitively) includes stale header '%s'",
291
                              includer->name, headerItem->name);
292
                }
599✔
293
            }
×
294
        }
295
    }
599✔
296

297
    if (cuCount == 0)
599✔
298
        log_debug("No CUs found that include '%s'", headerItem->name);
299

300
    removeReferenceableItemsForFile(headerFileNumber);
301

302
    return cuCount;
21✔
303
}
304

21✔
305
static bool fileNeedsParsing(FileItem *fileItem);
21✔
306

×
307
static void setupProgress(int cuCount, int skippedCapped) {
308
    static char progressFormat[128];
309
    int totalFound = cuCount + skippedCapped;
21✔
310
    if (skippedCapped > 0)
311
        snprintf(progressFormat, sizeof(progressFormat),
21✔
312
                 "%d unparsed sibling compilation units, parsing %d... %%d remaining", totalFound, cuCount);
21✔
313
    else
314
        snprintf(progressFormat, sizeof(progressFormat), "Parsing %d sibling compilation units... %%d remaining",
315
                 cuCount);
316
    initProgress(progressFormat);
317
}
318

319
/* Entry refresh Pass 3: Find CUs that share headers with the request file
320
 * but haven't been parsed yet. On cold start without preloads, only the
321
 * request file gets parsed (by processFile). This pass ensures sibling CUs
360✔
322
 * are also parsed so their symbol references are available for navigation.
323
 *
360✔
324
 * Uses brute-force forward-include lookup: iterates all headers in the
360✔
325
 * file table and checks if the request file is among their includers. */
360✔
326
static void parseUnparsedSiblingCUs(int requestFileNumber, ArgumentsVector baseArgs) {
327
    int cuFileNumbers[MAX_CUS_TO_REPARSE];
360✔
328
    int cuCount = 0;
329
    int skippedAlreadyParsed = 0;
330
    int skippedCapped = 0;
360✔
331

332
    log_info("Pass 3: request file '%s' (fileNumber=%d)",
3,826✔
333
             getFileItemWithFileNumber(requestFileNumber)->name, requestFileNumber);
3,466✔
334

3,466✔
335
    ensureReferencesAreLoadedFor(LINK_NAME_INCLUDE_REFS);
3,055✔
336

337
    for (int i = getNextExistingFileNumber(0); i != -1; i = getNextExistingFileNumber(i + 1)) {
2,884✔
338
        FileItem *fi = getFileItemWithFileNumber(i);
339
        if (isCompilationUnit(fi->name))
340
            continue;  /* Skip CUs, only look at headers */
341

342
        ReferenceableItem searchItem = makeReferenceableItem(
2,884✔
343
            LINK_NAME_INCLUDE_REFS, TypeCppInclude, StorageExtern,
361✔
344
            GlobalScope, VisibilityGlobal, i);
345

346
        ReferenceableItem *found;
2,523✔
347
        if (!isMemberInReferenceableItemTable(&searchItem, NULL, &found))
7,231✔
348
            continue;
5,119✔
349

411✔
350
        /* Is this header included by the request file? */
411✔
351
        bool requestIncludesThis = false;
352
        for (Reference *r = found->references; r != NULL; r = r->next) {
353
            if (r->position.file == requestFileNumber) {
354
                requestIncludesThis = true;
2,523✔
355
                break;
2,112✔
356
            }
357
        }
358

1,145✔
359
        if (!requestIncludesThis)
734✔
360
            continue;
734✔
361

413✔
362
        /* Collect sibling CUs that include this header and need parsing */
363
        for (Reference *r = found->references; r != NULL; r = r->next) {
321✔
364
            int siblingFileNum = r->position.file;
321✔
365
            if (siblingFileNum == requestFileNumber)
167✔
366
                continue;
154✔
367

119✔
368
            FileItem *siblingItem = getFileItemWithFileNumber(siblingFileNum);
119✔
369
            if (!isCompilationUnit(siblingItem->name))
370
                continue;
371
            if (!fileNeedsParsing(siblingItem)) {
372
                skippedAlreadyParsed++;
35✔
373
                continue;
36✔
374
            }
14✔
375

13✔
376
            /* Deduplicate */
13✔
377
            bool alreadyCollected = false;
378
            for (int j = 0; j < cuCount; j++) {
379
                if (cuFileNumbers[j] == siblingFileNum) {
35✔
380
                    alreadyCollected = true;
22✔
381
                    break;
22✔
382
                }
22✔
383
            }
384
            if (!alreadyCollected) {
385
                if (cuCount < MAX_CUS_TO_REPARSE) {
×
386
                    cuFileNumbers[cuCount++] = siblingFileNum;
387
                    log_debug("Sibling CU '%s' shares header with request file",
388
                              siblingItem->name);
389
                } else {
390
                    skippedCapped++;
391
                }
360✔
392
            }
393
        }
394
    }
360✔
395

21✔
396
    log_info("Pass 3: %d to parse, %d skipped (already parsed), %d skipped (cap %d)",
21✔
397
             cuCount, skippedAlreadyParsed, skippedCapped, MAX_CUS_TO_REPARSE);
21✔
398

399
    if (cuCount > 0) {
21✔
400
        log_info("Entry refresh pass 3: parsing %d sibling CU(s)", cuCount);
21✔
401
        if (options.xref2) {
43✔
402
            setupProgress(cuCount, skippedCapped);
22✔
403
        }
22✔
404
        int savedCursorOffset = options.cursorOffset;
22✔
405
        options.cursorOffset = NO_CURSOR_OFFSET;
22✔
406
        for (int i = 0; i < cuCount; i++) {
22✔
407
            reparseStaleFile(cuFileNumbers[i], baseArgs);
408
            FileItem *fi = getFileItemWithFileNumber(cuFileNumbers[i]);
21✔
409
            fi->lastParsedMtime = editorFileModificationTime(fi->name);
410
            if (options.xref2)
360✔
411
                writeProgressInformation(cuCount - i - 1);
412
        }
393✔
413
        options.cursorOffset = savedCursorOffset;
393✔
414
    }
1,744✔
415
}
2,433✔
416

1,082✔
417
static int countStalePreloadedFiles(void) {
559✔
418
    int count = 0;
393✔
419
    for (int i = 0; i != -1; i = getNextExistingEditorBufferIndex(i + 1))
420
        for (EditorBufferList *l = getEditorBufferListElementAt(i); l != NULL; l = l->next)
421
            if (fileNumberIsStale(l->buffer->fileNumber))
393✔
422
                count++;
393✔
423
    return count;
393✔
424
}
285✔
425

426
static void reparseStalePreloadedFiles(ArgumentsVector baseArgs) {
108✔
427
    int staleCount = countStalePreloadedFiles();
428
    if (staleCount == 0)
429
        return;
430

431
    log_info("Refreshing %d stale preloaded file(s)", staleCount);
432

825✔
433
    /* Pass 1: Reparse stale CUs directly. This also refreshes their
1,433✔
434
     * TypeCppInclude references, which Pass 2 depends on.
716✔
435
     * No progress reporting here — Pass 1 is fast (only directly
716✔
436
     * preloaded CUs, typically 1-2 files). */
716✔
437
    for (int i = 0; i != -1; i = getNextExistingEditorBufferIndex(i + 1)) {
98✔
438
        for (EditorBufferList *l = getEditorBufferListElementAt(i); l != NULL; l = l->next) {
98✔
439
            int fileNumber = l->buffer->fileNumber;
98✔
440
            FileItem *fileItem = getFileItemWithFileNumber(fileNumber);
98✔
441
            if (fileNumberIsStale(fileNumber) && isCompilationUnit(fileItem->name)) {
98✔
442
                log_debug("Reparsing stale CU '%s'", fileItem->name);
98✔
443
                reparseStaleFile(fileNumber, baseArgs);
444
                EditorBuffer *buffer = getOpenedAndLoadedEditorBuffer(fileItem->name);
445
                if (buffer != NULL)
446
                    fileItem->lastParsedMtime = buffer->modificationTime;
447
                fileItem->needsBrowsingStackRefresh = true;
448
            }
449
        }
450
    }
451

452
    /* Pass 2: For stale headers, find CUs that include them and reparse.
453
     * Must come after Pass 1: Pass 1 reparses stale CUs, refreshing their
454
     * TypeCppInclude references. Pass 2 queries those references to find
455
     * which CUs include the stale header (transitively).
108✔
456
     *
457
     * First collect all CUs across all stale headers (deduplicated),
875✔
458
     * then reparse with per-CU progress reporting. */
1,550✔
459
    int cuFileNumbers[MAX_CUS_TO_REPARSE];
783✔
460
    int cuCount = 0;
783✔
461

783✔
462
    for (int i = 0; i != -1; i = getNextExistingEditorBufferIndex(i + 1)) {
599✔
463
        for (EditorBufferList *l = getEditorBufferListElementAt(i); l != NULL; l = l->next) {
464
            int fileNumber = l->buffer->fileNumber;
599✔
465
            FileItem *fileItem = getFileItemWithFileNumber(fileNumber);
599✔
466
            if (fileNumberIsStale(fileNumber) && !isCompilationUnit(fileItem->name)) {
599✔
467
                cuCount = collectIncludersOfStaleHeader(fileNumber, cuFileNumbers, cuCount,
599✔
468
                                                       MAX_CUS_TO_REPARSE);
469
                EditorBuffer *buffer = getOpenedAndLoadedEditorBuffer(fileItem->name);
470
                if (buffer != NULL)
471
                    fileItem->lastParsedMtime = buffer->modificationTime;
472
                fileItem->needsBrowsingStackRefresh = true;
108✔
473
            }
52✔
474
        }
52✔
475
    }
476

52✔
477
    if (cuCount > 0) {
478
        log_info("Reparsing %d CU(s) for stale header includers", cuCount);
52✔
479
        if (options.xref2) {
480
            static char progressFormat[128];
135✔
481
            snprintf(progressFormat, sizeof(progressFormat),
83✔
482
                     "Updating %d header includers... %%d remaining", cuCount);
83✔
483
            initProgress(progressFormat);
83✔
484
        }
83✔
485
        for (int i = 0; i < cuCount; i++) {
486
            reparseStaleFile(cuFileNumbers[i], baseArgs);
487
            getFileItemWithFileNumber(cuFileNumbers[i])->needsBrowsingStackRefresh = true;
488
            if (options.xref2)
489
                writeProgressInformation(cuCount - i - 1);
178✔
490
        }
178✔
491
    }
178✔
492
}
493

494
static bool fileNeedsParsing(FileItem *fileItem) {
14✔
495
    return fileItem->lastParsedMtime == 0
496
        || editorFileModificationTime(fileItem->name) != fileItem->lastParsedMtime;
497
}
498

14✔
499
static void parseDiscoveredCompilationUnits(ArgumentsVector baseArgs) {
56✔
500
    /* Parse all discovered CUs to populate in-memory references.
42✔
501
     * Skip the request file — it will be handled by the dispatch below
42✔
502
     * (processFile for input-processing operations, or just unscheduled). */
12✔
503
    int cuCount = 0;
10✔
504
    for (int i = getNextExistingFileNumber(0); i != -1; i = getNextExistingFileNumber(i + 1)) {
505
        FileItem *fileItem = getFileItemWithFileNumber(i);
506
        if (fileItem->isScheduled && isCompilationUnit(fileItem->name) && i != requestFileNumber
14✔
507
            && fileNeedsParsing(fileItem))
83✔
508
            cuCount++;
69✔
509
    }
69✔
510

12✔
511
    int parsed = 0;
10✔
512
    for (int i = getNextExistingFileNumber(0); i != -1; i = getNextExistingFileNumber(i + 1)) {
10✔
513
        FileItem *fileItem = getFileItemWithFileNumber(i);
10✔
514
        if (fileItem->isScheduled && isCompilationUnit(fileItem->name) && i != requestFileNumber) {
10✔
515
            if (fileNeedsParsing(fileItem)) {
516
                reparseStaleFile(i, baseArgs);
12✔
517
                parsed++;
518
                if (options.xref2)
519
                    writeRelativeProgress((100 * parsed) / cuCount);
14✔
520
            }
14✔
521
            fileItem->isScheduled = false;
522
        }
1✔
523
    }
1✔
524
    log_info("Startup: parsed %d compilation units", parsed);
1✔
525
}
1✔
526

527
static bool waitForUserConfirmation(char *message) {
528
    ppcAskConfirmation(message);
529
    closeOutputFile();
530
    ppcSynchronize();
1✔
531

532
    /* Read the response directly from stdin without using readOptionsFromPipe,
2✔
533
     * which would overwrite the static optMemory buffer and clobber requestArgs. */
2✔
534
    char line[MAX_OPTION_LEN];
2✔
535
    bool confirmed = false;
2✔
536

537
    while (fgets(line, sizeof(line), stdin) != NULL) {
2✔
538
        int len = strlen(line);
1✔
539
        if (len > 0 && line[len - 1] == '\n')
1✔
540
            line[len - 1] = '\0';
1✔
541

1✔
542
        if (strcmp(line, END_OF_OPTIONS_STRING) == 0)
×
543
            break;
544
        if (strcmp(line, "-continue") == 0)
545
            confirmed = true;
546
        if (strcmp(line, "-cancel") == 0)
1✔
547
            confirmed = false;
1✔
548
    }
×
549

×
550
    /* Consume the trailing empty line (protocol separator) */
551
    int c = getc(stdin);
552
    if (c == EOF) {
1✔
553
        log_error("Broken pipe in waitForUserConfirmation");
1✔
554
        exit(EXIT_FAILURE);
555
    }
556

502✔
557
    openOutputFile(options.outputFileName);
558
    return confirmed;
559
}
560

502✔
561
void callServer(ArgumentsVector baseArgs, ArgumentsVector requestArgs) {
562
    static bool projectContextInitialized = false;
502✔
563
    static bool scanDone = false;
564

565
    ENTER();
566

567
    loadAllOpenedEditorBuffers();
568

502✔
569
    /* Close preloaded buffers that the client no longer sends — the user
570
     * has saved and/or closed the file in the editor. Without this, the
571
     * server would keep parsing from the stale preloaded content instead
572
     * of reading the current disk file. */
502✔
573
    closeEditorBuffersNoLongerPreloaded();
574

575
    /* Reparse any stale preloaded files before dispatching the operation,
576
     * so all operations see fresh in-memory references. */
393✔
577
    if (projectContextInitialized) {
393✔
578
        /* Clear cursorOffset so the lexer doesn't set positionOfSelectedReference
393✔
579
         * during the reparse — that would trigger on-line action handling (browsing stack
393✔
580
         * assertions) before the operation has set things up. */
581
        int savedCursorOffset = options.cursorOffset;
582
        options.cursorOffset = NO_CURSOR_OFFSET;
502✔
583
        reparseStalePreloadedFiles(baseArgs);
584
        options.cursorOffset = savedCursorOffset;
585
    }
502✔
586

109✔
587
    bool hasInputFile = prepareInputFileForRequest();
96✔
588

×
589
    /* ONE-TIME: project identity + disk db load */
590
    if (!projectContextInitialized && hasInputFile) {
96✔
591
        if (options.serverOperation == OP_ACTIVE_PROJECT) {
592
            if (!initializeProjectContext(getFileItemWithFileNumber(requestFileNumber)->name, baseArgs, requestArgs))
96✔
593
                goto done;
594

14✔
595
            loadSnapshotFromStore();
14✔
596

14✔
597
            if (options.detectedProjectRoot == NULL || options.detectedProjectRoot[0] == '\0') {
14✔
598
                /* Legacy path: no detected project root, fall back to old flow */
599
                if (options.inputFiles == NULL)
600
                    addToStringListOption(&options.inputFiles, ".");
96✔
601
                processFileArguments();
13✔
602
                parseDiscoveredCompilationUnits(baseArgs);
×
603
            }
×
604

605
            projectContextInitialized = true;
606
        } else if (!requiresProcessingInputFile(options.serverOperation)) {
607
            errorMessage(ERR_ST, "Project not initialized - client must send getprojectname first");
608
            goto done;
609
        }
610
        /* requiresProcessingInputFile operations proceed to processFile below,
611
         * which handles its own context via initializeFileProcessing (legacy path). */
612
    }
613

502✔
614
    /* CONFIG-CHANGE-AWARE SCAN (auto-detect path only).
489✔
615
     * First request: !scanDone triggers the scan.
489✔
616
     * Config change: re-reads .c-xrefrc (updates options.includeDirs and
361✔
617
     * savedOptions), then re-runs scan with new include dirs. */
361✔
618
    if (projectContextInitialized
×
619
        && options.detectedProjectRoot != NULL
361✔
620
        && options.detectedProjectRoot[0] != '\0') {
82✔
621
        bool configChanged = isProjectConfigChanged();
82✔
622
        if (configChanged)
623
            reloadProjectConfig(baseArgs, requestArgs);
208✔
624
        if (!scanDone || configChanged) {
126✔
625
            StringList *discoveredCUs = scanProjectForFilesAndIncludes(
65✔
626
                options.detectedProjectRoot, options.includeDirs);
2✔
627
            /* Also scan extra source directories from the project config */
2✔
628
            for (StringList *dir = getProjectConfig()->sourceDirs; dir != NULL; dir = dir->next) {
629
                if (isDirectory(dir->string)
2✔
630
                    && strcmp(dir->string, options.detectedProjectRoot) != 0) {
1✔
631
                    StringList *extraCUs = scanProjectForFilesAndIncludes(
1✔
632
                        dir->string, options.includeDirs);
×
633
                    /* Prepend to discoveredCUs */
1✔
634
                    if (extraCUs != NULL) {
1✔
635
                        StringList *tail = extraCUs;
636
                        while (tail->next != NULL)
637
                            tail = tail->next;
638
                        tail->next = discoveredCUs;
82✔
639
                        discoveredCUs = extraCUs;
82✔
640
                    }
82✔
641
                }
642
            }
643
            markMissingFilesAsDeleted(discoveredCUs);
644
            freeStringList(discoveredCUs);
645
            scanDone = true;
646
        }
502✔
647
    }
468✔
648

360✔
649
    /* Entry refresh pass 3: parse sibling CUs that share headers with
650
     * the request file but haven't been parsed yet (e.g. cold start). */
651
    if (projectContextInitialized && hasInputFile
652
        && options.detectedProjectRoot != NULL && options.detectedProjectRoot[0] != '\0') {
502✔
653
        parseUnparsedSiblingCUs(requestFileNumber, baseArgs);
2✔
654
    }
59✔
655

57✔
656
    /* Search completeness: if unparsed CUs exist, ask user before searching */
57✔
657
    if (projectContextInitialized && options.serverOperation == OP_SEARCH) {
2✔
658
        int totalCUs = 0, unparsedCUs = 0;
2✔
659
        for (int i = getNextExistingFileNumber(0); i != -1; i = getNextExistingFileNumber(i + 1)) {
1✔
660
            FileItem *fi = getFileItemWithFileNumber(i);
661
            if (isCompilationUnit(fi->name)) {
662
                totalCUs++;
2✔
663
                if (fi->lastParsedMtime == 0)
664
                    unparsedCUs++;
1✔
665
            }
666
        }
1✔
667
        if (unparsedCUs > 0) {
1✔
668
            char msg[TMP_STRING_SIZE];
669
            sprintf(msg, "%d of %d compilation units not yet parsed. Parse all before searching?",
1✔
670
                    unparsedCUs, totalCUs);
671
            if (waitForUserConfirmation(msg)) {
1✔
672
                int parsed = 0;
3✔
673
                char progressFormat[128];
2✔
674
                snprintf(progressFormat, sizeof(progressFormat),
2✔
675
                         "Parsing %d compilation units for search... %%d remaining", unparsedCUs);
1✔
676
                initProgress(progressFormat);
1✔
677
                for (int i = getNextExistingFileNumber(0); i != -1; i = getNextExistingFileNumber(i + 1)) {
1✔
678
                    FileItem *fi = getFileItemWithFileNumber(i);
679
                    if (isCompilationUnit(fi->name) && fi->lastParsedMtime == 0) {
1✔
680
                        reparseStaleFile(i, baseArgs);
×
681
                        parsed++;
682
                        writeProgressInformation(unparsedCUs - parsed);
683
                        /* Save snapshot periodically so progress survives Ctrl-g/crash */
1✔
684
                        if (parsed % 100 == 0)
685
                            saveReferences();
686
                    }
687
                }
688
                log_info("Search: parsed %d CUs", parsed);
502✔
689
            }
223✔
690
        }
691
    }
502✔
692

268✔
693
    if (needsReferenceDatabase(options.serverOperation))
268✔
694
        pushEmptySession(&sessionData.browsingStack);
268✔
695

696
    if (requiresProcessingInputFile(options.serverOperation)) {
×
697
        if (hasInputFile) {
698
            processFile(baseArgs, requestArgs);
699
            projectContextInitialized = true;
234✔
700
        } else {
213✔
701
            errorMessage(ERR_ST, "No input file");
213✔
702
        }
703
    } else {
704
        if (hasInputFile) {
21✔
705
            getFileItemWithFileNumber(requestFileNumber)->isScheduled = false;
502✔
706
            inputFileName = NULL;
502✔
707
        }
708
    }
109✔
709
done:
710
    LEAVE();
711
}
109✔
712

109✔
713
void server(ArgumentsVector args) {
714
    ArgumentsVector pipedOptions;
109✔
715

716
    ENTER();
487✔
717
    cxResizingBlocked = true;
487✔
718

719
    deepCopyOptionsFromTo(&options, &savedOptions);
487✔
720
    for(;;) {
721
        currentPass = ANY_PASS;
487✔
722
        deepCopyOptionsFromTo(&savedOptions, &options);
723

724
        pipedOptions = readOptionsFromPipe();
487✔
725
        // TODO -o option on command line should catch also file not found
487✔
726
        openOutputFile(options.outputFileName);
727
        //&dumpArguments(nargc, nargv);
487✔
728

487✔
729
        progressOffset = 0;
487✔
730
        progressFactor = 1;
×
731

732
        log_trace("Server: Getting request");
378✔
733
        clearPreloadedThisRequestFlags();
378✔
734
        initServer(pipedOptions);
×
735
        if (outputFile==stdout && options.outputFileName!=NULL) {
736
            openOutputFile(options.outputFileName);
378✔
737
        }
738
        callServer(args, pipedOptions);
739
        if (options.serverOperation == OP_ABOUT) {
378✔
740
            aboutMessage();
378✔
741
        } else {
378✔
742
            answerEditorAction();
378✔
743
        }
378✔
744

745
        closeAllEditorBuffersIfClosable();
746
        closeOutputFile();
747
        if (options.xref2)
748
            ppcSynchronize();
749
        log_trace("Server: Request answered");
750
    }
751
    LEAVE();
752
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc