first comit
This commit is contained in:
113
venv/lib/python3.10/site-packages/debugpy/launcher/output.py
Normal file
113
venv/lib/python3.10/site-packages/debugpy/launcher/output.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import threading
|
||||
|
||||
from debugpy import launcher
|
||||
from debugpy.common import log
|
||||
|
||||
|
||||
class CaptureOutput(object):
|
||||
"""Captures output from the specified file descriptor, and tees it into another
|
||||
file descriptor while generating DAP "output" events for it.
|
||||
"""
|
||||
|
||||
instances = {}
|
||||
"""Keys are output categories, values are CaptureOutput instances."""
|
||||
|
||||
def __init__(self, whose, category, fd, stream):
|
||||
assert category not in self.instances
|
||||
self.instances[category] = self
|
||||
log.info("Capturing {0} of {1}.", category, whose)
|
||||
|
||||
self.category = category
|
||||
self._whose = whose
|
||||
self._fd = fd
|
||||
self._decoder = codecs.getincrementaldecoder("utf-8")(errors="surrogateescape")
|
||||
|
||||
if stream is None:
|
||||
# Can happen if running under pythonw.exe.
|
||||
self._stream = None
|
||||
else:
|
||||
self._stream = stream.buffer
|
||||
encoding = stream.encoding
|
||||
if encoding is None or encoding == "cp65001":
|
||||
encoding = "utf-8"
|
||||
try:
|
||||
self._encode = codecs.getencoder(encoding)
|
||||
except Exception:
|
||||
log.swallow_exception(
|
||||
"Unsupported {0} encoding {1!r}; falling back to UTF-8.",
|
||||
category,
|
||||
encoding,
|
||||
level="warning",
|
||||
)
|
||||
self._encode = codecs.getencoder("utf-8")
|
||||
else:
|
||||
log.info("Using encoding {0!r} for {1}", encoding, category)
|
||||
|
||||
self._worker_thread = threading.Thread(target=self._worker, name=category)
|
||||
self._worker_thread.start()
|
||||
|
||||
def __del__(self):
|
||||
fd = self._fd
|
||||
if fd is not None:
|
||||
try:
|
||||
os.close(fd)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _worker(self):
|
||||
while self._fd is not None:
|
||||
try:
|
||||
s = os.read(self._fd, 0x1000)
|
||||
except Exception:
|
||||
break
|
||||
if not len(s):
|
||||
break
|
||||
self._process_chunk(s)
|
||||
|
||||
# Flush any remaining data in the incremental decoder.
|
||||
self._process_chunk(b"", final=True)
|
||||
|
||||
def _process_chunk(self, s, final=False):
|
||||
s = self._decoder.decode(s, final=final)
|
||||
if len(s) == 0:
|
||||
return
|
||||
|
||||
try:
|
||||
launcher.channel.send_event(
|
||||
"output", {"category": self.category, "output": s.replace("\r\n", "\n")}
|
||||
)
|
||||
except Exception:
|
||||
pass # channel to adapter is already closed
|
||||
|
||||
if self._stream is None:
|
||||
return
|
||||
|
||||
try:
|
||||
s, _ = self._encode(s, "surrogateescape")
|
||||
size = len(s)
|
||||
i = 0
|
||||
while i < size:
|
||||
written = self._stream.write(s[i:])
|
||||
self._stream.flush()
|
||||
if written == 0:
|
||||
# This means that the output stream was closed from the other end.
|
||||
# Do the same to the debuggee, so that it knows as well.
|
||||
os.close(self._fd)
|
||||
self._fd = None
|
||||
break
|
||||
i += written
|
||||
except Exception:
|
||||
log.swallow_exception("Error printing {0!r} to {1}", s, self.category)
|
||||
|
||||
|
||||
def wait_for_remaining_output():
|
||||
"""Waits for all remaining output to be captured and propagated."""
|
||||
for category, instance in CaptureOutput.instances.items():
|
||||
log.info("Waiting for remaining {0} of {1}.", category, instance._whose)
|
||||
instance._worker_thread.join()
|
||||
Reference in New Issue
Block a user