#include "slang-language-server-auto-format.h" #include "../core/slang-char-util.h" #include "../compiler-core/slang-lexer.h" #include "../core/slang-file-system.h" namespace Slang { String findClangFormatTool() { String processName = String("clang-format") + String(Process::getExecutableSuffix()); if (File::exists(processName)) return processName; RefPtr proc; CommandLine cmdLine; cmdLine.setExecutableLocation(ExecutableLocation(processName)); if (Process::create(cmdLine, 0, proc) == SLANG_OK) { auto inStream = proc->getStream(StdStreamType::In); if (inStream) inStream->close(); proc->kill(0); return processName; } auto fileName = Slang::SharedLibraryUtils::getSharedLibraryFileName((void*)slang_createGlobalSession); auto dirName = Slang::Path::getParentDirectory(fileName); auto localProcess = Path::combine(dirName, processName); if (File::exists(localProcess)) return localProcess; auto extensionsStr = UnownedStringSlice("extensions"); Index vsCodeLoc = dirName.indexOf(extensionsStr); if (vsCodeLoc != -1) { // If we still cannot find clang-format, try to use the clang-format bundled with VSCode's C++ extension. String vsCodeExtDir = dirName.subString(0, vsCodeLoc + extensionsStr.getLength()); struct CallbackContext { String foundPath; String parentDir; String processName; } callbackContext; callbackContext.processName = processName; callbackContext.parentDir = vsCodeExtDir; OSFileSystem::getExtSingleton()->enumeratePathContents(vsCodeExtDir.getBuffer(), [](SlangPathType /*pathType*/, const char* name, void* userData) { CallbackContext* context = (CallbackContext*)userData; if (UnownedStringSlice(name).indexOf(UnownedStringSlice("ms-vscode.cpptools-")) != -1) { String candidateFileName = Path::combine(Path::combine(context->parentDir, name, "LLVM"), "bin", context->processName); if (File::exists(candidateFileName)) context->foundPath = candidateFileName; } }, & callbackContext); if (callbackContext.foundPath.getLength()) return callbackContext.foundPath; } return String(); } void translateXmlEscape(StringBuilder& sb, UnownedStringSlice text) { if (text.getLength() == 0) return; if (text[0] == '#') { Int charVal = 0; StringUtil::parseInt(text.tail(1), charVal); if (charVal != 0) sb.appendChar((char)charVal); } else if (text == "lt") { sb.appendChar('<'); } else if (text == "gt") { sb.appendChar('>'); } else if (text == "amp") { sb.appendChar('&'); } else if (text == "apos") { sb.appendChar('\''); } else if (text == "quot") { sb.appendChar('\"'); } } String parseXmlText(UnownedStringSlice text) { StringBuilder sb; Index pos = 0; for (; pos < text.getLength();) { if (text[pos] == '&') { pos++; Index endPos = pos; while (endPos < text.getLength() && text[endPos] != ';') endPos++; auto escapedToken = text.subString(pos, endPos - pos); pos = endPos + 1; translateXmlEscape(sb, escapedToken); } else { sb.appendChar(text[pos]); pos++; } } return sb.ProduceString(); } bool shouldUseFallbackStyle(const FormatOptions& options) { if (!options.style.startsWith("file")) return false; String clangFormatFileName = ".clang-format"; bool standardFileName = true; if (options.style.startsWith("file:")) { auto fileName = options.style.getUnownedSlice().tail(5); if (fileName.startsWith("\"")) fileName = fileName.head(fileName.getLength() - 1).tail(1); clangFormatFileName = fileName; standardFileName = false; } auto path = options.fileName; do { path = Path::getParentDirectory(path); auto expectedFormatFileName = Path::combine(path, clangFormatFileName); if (File::exists(expectedFormatFileName)) { return false; } if (standardFileName) { expectedFormatFileName = Path::combine(path, "_clang_format"); if (File::exists(expectedFormatFileName)) { return false; } } } while (path.getLength()); return true; } List formatSource(UnownedStringSlice text, Index lineStart, Index lineEnd, Index cursorOffset, const FormatOptions& options) { List edits; String clangProcessName = options.clangFormatLocation; CommandLine cmdLine; cmdLine.setExecutableLocation(ExecutableLocation(clangProcessName)); cmdLine.addArg("--assume-filename"); cmdLine.addArg(options.fileName + ".cs"); if (cursorOffset != -1) { cmdLine.addArg("--cursor=" + String(cursorOffset)); } if (lineStart != -1) { cmdLine.addArg("--lines=" + String(lineStart) + ":" + String(lineEnd + 1)); } cmdLine.addArg("--output-replacements-xml"); // clang-format does not allow non-builtin style for `fallback-style`. // We detect if clang format file exists, and if not set style directly to user specified fallback style. if (shouldUseFallbackStyle(options)) { if (options.fallbackStyle.getLength()) { cmdLine.addArg("-style"); cmdLine.addArg(options.fallbackStyle); } } else if (options.style.getLength()) { cmdLine.addArg("-style"); cmdLine.addArg(options.style); } RefPtr proc; if (SLANG_FAILED(Process::create(cmdLine, 0, proc))) return edits; auto inStream = proc->getStream(StdStreamType::In); inStream->write(text.begin(), text.getLength()); char terminator = '\0'; inStream->write(&terminator, 1); inStream->flush(); inStream->close(); ExecuteResult result; ProcessUtil::readUntilTermination(proc, result); /* Example result of clang-format: */ // Adhoc parsing of clang-format's result. List lines; auto offsetStr = UnownedStringSlice("offset="); auto lengthStr = UnownedStringSlice("length="); auto endStr = UnownedStringSlice(""); auto replacementStr = UnownedStringSlice("'); if (pos == -1) continue; line = line.tail(pos + 1); Index endPos = line.indexOf(endStr); if (endPos == -1) continue; line = line.head(endPos); edt.text = parseXmlText(line); // For on-type formatting, don't make any changes beyond the current cursor position. if (cursorOffset != -1 && edt.offset >= cursorOffset) break; // Never allow clang-format to put the semicolon after `}` in its own line. if (edt.offset < text.getLength() && edt.length == 0 && text[edt.offset] == ';' && edt.offset >0 && text[edt.offset - 1] == '}') continue; // If need to preserve line break, turn all edits with a line break into a space. if (options.behavior == FormatBehavior::PreserveLineBreak) { auto originalText = text.subString(edt.offset, edt.length); bool originalHasLineBreak = originalText.indexOf('\n') != -1; bool newHasLineBreak = edt.text.indexOf('\n') != -1; if (originalHasLineBreak == newHasLineBreak) { } else if (!originalHasLineBreak && newHasLineBreak) { if (edt.offset < text.getLength() && edt.offset >= 0 && text[edt.offset] == '}') continue; edt.text = " "; } else { continue; } } edits.add(edt); } return edits; } } // namespace Slang