diff options
| author | Yong He <yonghe@outlook.com> | 2022-06-27 15:36:00 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-06-27 15:36:00 -0700 |
| commit | b7638b8fffe78ade657f361cadc08dffc8c10acf (patch) | |
| tree | e27a141cfc6a9cc77356b8cba27b41c495d4ee27 /source/slang/slang-language-server-auto-format.cpp | |
| parent | 62d16a23b0ecd72dc624abd7e10b373c40adaa90 (diff) | |
Language server fixes and improvements (#2304)
* Language server: Inlay hints.
* Signature help for base exprs that is not a declref.
* Fix checking of jvp operator.
* Fix.
* Add clang-format based auto formatting.
* Fix clang error.
* Fix clang-format discovery logic.
* Fine tune auto formatting and completion experience.
* Update macos workflow.
* Fixes to configurations.
* Fix parser recovery to trigger completion for index exprs.
* Typo fix.
Co-authored-by: Yong He <yhe@nvidia.com>
Diffstat (limited to 'source/slang/slang-language-server-auto-format.cpp')
| -rw-r--r-- | source/slang/slang-language-server-auto-format.cpp | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/source/slang/slang-language-server-auto-format.cpp b/source/slang/slang-language-server-auto-format.cpp new file mode 100644 index 000000000..8d31c44c6 --- /dev/null +++ b/source/slang/slang-language-server-auto-format.cpp @@ -0,0 +1,213 @@ +#include "slang-language-server-auto-format.h" +#include "../core/slang-char-util.h" +#include "../compiler-core/slang-lexer.h" + +namespace Slang +{ + +String findClangFormatTool() +{ + String processName = String("clang-format") + String(Process::getExecutableSuffix()); + if (File::exists(processName)) + return processName; + RefPtr<Process> 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; + + Index vsCodeLoc = dirName.indexOf(".vscode"); + 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 + 7); + vsCodeExtDir = Path::combine(vsCodeExtDir, "extensions"); + 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(); +} + +List<Edit> formatSource(UnownedStringSlice text, Index lineStart, Index lineEnd, Index cursorOffset, const FormatOptions& options) +{ + List<Edit> edits; + + String clangProcessName = options.clangFormatLocation; + CommandLine cmdLine; + cmdLine.setExecutableLocation(ExecutableLocation(clangProcessName)); + cmdLine.addArg("--assume-filename=source.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"); + if (options.style.getLength()) + { + cmdLine.addArg("-style"); + cmdLine.addArg(options.style); + } + RefPtr<Process> 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: + + <?xml version='1.0'?> + <replacements xml:space='preserve' incomplete_format='false'> + <replacement offset='26' length='2'> </replacement> + </replacements> + */ + + // Adhoc parsing of clang-format's result. + List<UnownedStringSlice> lines; + auto offsetStr = UnownedStringSlice("offset="); + auto lengthStr = UnownedStringSlice("length="); + auto endStr = UnownedStringSlice("</replacement>"); + auto replacementStr = UnownedStringSlice("<replacement "); + StringUtil::calcLines(result.standardOutput.getUnownedSlice(), lines); + for (auto line : lines) + { + line = line.trim(); + if (!line.startsWith(replacementStr)) + continue; + line = line.tail(replacementStr.getLength()); + Index pos = line.indexOf(offsetStr); + if (pos == -1) + continue; + pos += offsetStr.getLength(); + if (pos < line.getLength() && line[pos] == '\'') + pos++; + Edit edt; + edt.offset = StringUtil::parseIntAndAdvancePos(line, pos); + pos = line.indexOf(lengthStr); + if (pos == -1) + continue; + pos += lengthStr.getLength(); + if (pos < line.getLength() && line[pos] == '\'') + pos++; + edt.length = StringUtil::parseIntAndAdvancePos(line, pos); + line = line.tail(pos); + pos = line.indexOf('>'); + 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; + + edits.add(edt); + } + return edits; +} + + +} // namespace Slang |
