CodeForgeController constructor

CodeForgeController({
  1. LspConfig? lspConfig,
})

Implementation

CodeForgeController({this.lspConfig}) {
  _listeners.add(() {
    if (isBufferActive) {
      final col = bufferCursorColumn;
      final lineText = bufferLineText ?? '';
      if (col > 0 && col <= lineText.length) {
        _insertedChar = lineText.substring(col - 1, col);
      }
    } else {
      final base = _prevSelection.baseOffset.clamp(0, text.length);
      final extent = selection.baseOffset.clamp(0, text.length);
      if (extent - base == 1) {
        _insertedChar = text.substring(base, extent);
      } else if (extent > 0) {
        final cursor = selection.baseOffset.clamp(0, text.length);
        if (cursor > 0) {
          _insertedChar = text.substring(cursor - 1, cursor);
        }
      }
    }
    _isTyping = _insertedChar.isNotEmpty && _isAlpha(_insertedChar);
  });

  if (lspConfig != null) {
    (() async {
      try {
        if (lspConfig is LspSocketConfig) {
          await (lspConfig! as LspSocketConfig).connect();
        }
        if (!lspConfig!.isIntialized) {
          await lspConfig!.initialize();
        }
        await Future.delayed(const Duration(milliseconds: 300));
        await lspConfig!.openDocument(openedFile!);
        _lspReady = true;
        await _fetchSemanticTokensFull();
      } catch (e) {
        debugPrint('Error initializing LSP: $e');
      } finally {
        _listeners.add(_highlightListener);
      }
    })();

    _lspResponsesSubscription = lspConfig!.responses.listen((data) async {
      try {
        if (data['method'] == 'workspace/applyEdit') {
          final Map<String, dynamic>? params = data['params'];
          if (params != null && params.isNotEmpty) {
            if (params.containsKey('edit')) {
              await applyWorkspaceEdit(params);
            }
          }
        }

        if (data['method'] == 'textDocument/publishDiagnostics') {
          final List<dynamic> rawDiagnostics =
              data['params']?['diagnostics'] ?? [];
          if (rawDiagnostics.isNotEmpty) {
            final List<LspErrors> errors = [];
            for (final item in rawDiagnostics) {
              if (item is! Map<String, dynamic>) continue;
              int severity = item['severity'] ?? 0;
              if (severity == 1 && lspConfig!.disableError) {
                severity = 0;
              }
              if (severity == 2 && lspConfig!.disableWarning) {
                severity = 0;
              }
              if (severity > 0) {
                errors.add(
                  LspErrors(
                    severity: severity,
                    range: item['range'],
                    message: item['message'] ?? '',
                  ),
                );
              }
            }
            if (!_isDisposed) diagnostics.value = errors;

            _codeActionTimer?.cancel();
            _codeActionTimer = Timer(
              const Duration(milliseconds: 250),
              () async {
                if (errors.isEmpty) {
                  if (!_isDisposed) codeActions.value = null;
                  return;
                }
                int minStartLine = errors
                    .map((d) => d.range['start']?['line'] as int? ?? 0)
                    .reduce((a, b) => a < b ? a : b);
                int minStartChar = errors
                    .map((d) => d.range['start']?['character'] as int? ?? 0)
                    .reduce((a, b) => a < b ? a : b);
                int maxEndLine = errors
                    .map((d) => d.range['end']?['line'] as int? ?? 0)
                    .reduce((a, b) => a > b ? a : b);
                int maxEndChar = errors
                    .map((d) => d.range['end']?['character'] as int? ?? 0)
                    .reduce((a, b) => a > b ? a : b);

                try {
                  final actions = await lspConfig!.getCodeActions(
                    filePath: openedFile!,
                    startLine: minStartLine,
                    startCharacter: minStartChar,
                    endLine: maxEndLine,
                    endCharacter: maxEndChar,
                    diagnostics: rawDiagnostics.cast<Map<String, dynamic>>(),
                  );
                  if (!_isDisposed) codeActions.value = actions;
                } catch (e) {
                  debugPrint('Error fetching code actions: $e');
                }
              },
            );
          } else {
            if (!_isDisposed) diagnostics.value = [];
          }
        }
      } catch (e, st) {
        debugPrint('Error handling LSP response: $e\n$st');
      }
    });
  } else {
    _listeners.add(() {
      final cursorPosition = selection.extentOffset;
      final prefix = getCurrentWordPrefix(text, cursorPosition);
      if (_isTyping && selection.extentOffset > 0) {
        final regExp = RegExp(r'\b\w+\b');
        final List<String> words = regExp
            .allMatches(text)
            .map((m) => m.group(0)!)
            .toList();

        String currentWord = '';
        if (text.isNotEmpty) {
          final match = RegExp(r'\w+$').firstMatch(text);
          if (match != null) {
            currentWord = match.group(0)!;
          }
        }

        _suggestions.clear();

        for (final i in words) {
          if (!_suggestions.contains(i) && i != currentWord) {
            _suggestions.add(i);
          }
        }
        if (prefix.isNotEmpty) {
          _suggestions = _suggestions
              .where((s) => s.startsWith(prefix))
              .toList();
        }
        _sortSuggestions(prefix);
        final triggerChar = text[cursorPosition - 1];
        if (!_isAlpha(triggerChar)) {
          if (!_isDisposed) suggestions.value = null;
          return;
        }
        if (!_isDisposed) suggestions.value = _suggestions;
      } else {
        if (!_isDisposed) suggestions.value = null;
      }
    });
  }
}