redirect console.log messages to an output channel - visual-studio-code

how can I configure my extension to write all conselo.log}info|debug messages to an outputchannel ?
this seems to be the default for LSP Extensions See this issue where it was broken and then fixed, however I have not been able to find how set this configuration for a regular extension.
Clearly it is possible to create and write directly to a custom Output Channel but that would require me to create a custom logging class that just replicates something that has been done before.

This is a workaround rather than exact solution
I ended up creating a logger that wraps an outputchannel and writes to it
import * as vscode from 'vscode';
import * as util from 'util' // has no default export
import { OutputChannel } from "vscode";
export class Logger {
private outputChannel:OutputChannel ;
constructor(extensionOuputName: string, private useConsole = true) {
if (!useConsole) {
this.outputChannel = vscode.window.createOutputChannel(extensionOuputName);
this.outputChannel.show();
}
}
public append(o: any) {
const prefix = `[${new Date().toLocaleString()}]`;
if (o.constructor === Array) {
o.forEach(item => this.append(item));
}
else {
if (this.useConsole) {
console.log(prefix, o);
}
else {
this.outputChannel.append(prefix + ' ');
const isObject = (typeof o === 'object' && o !== null);
//be carefull stringify can not convert circular dependencies
//this.outputChannel.appendLine(isObject ? JSON.stringify(o) : o);
this.outputChannel.appendLine(isObject ? util.inspect(o) : o);
}
}
}
}
Then in your extension just create a logger and use it
export class YourClass {
//false to use the outputchannel - true to use regular console
private log: Logger = new Logger('Nav-Code', false);
this.log.append(`Checking folder: ${this.projectFolder}`);
This creates and shows an outputchannel with the name 'Nav-Code'

Related

VSCode: Create a document in memory with URI for automated testing?

Background
I created an extension that interacts with documents. In order to test the extension I need to create documents, that the extension can work with. The extension has to access the document via uri.
Currently I'm using vscode.workspace.openTextDocument({content: _content, language: _language}); for document creation. The problem is, it does not have a valid URI.
Question
How can I create a virtual document in memory, that has a valid URI?
As there was no native solution to this, I created my and I'd like to share it here:
A TextDocumentContentProvider for files in memory. Example usage shown below
memoryfile.ts
import * as vscode from 'vscode';
const _SCHEME = "inmemoryfile";
/**
* Registration function for In-Memory files.
* You need to call this once, if you want to make use of
* `MemoryFile`s.
**/
export function register_memoryFileProvider ({ subscriptions }: vscode.ExtensionContext)
{
const myProvider = new (class implements vscode.TextDocumentContentProvider
{
provideTextDocumentContent(uri: vscode.Uri): string
{
let memDoc = MemoryFile.getDocument (uri);
if (memDoc == null)
return "";
return memDoc.read ();
}
})();
subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(
_SCHEME, myProvider));
}
/**
* Management class for in-memory files.
**/
class MemoryFileManagement
{
private static _documents: {[key: string]: MemoryFile} = {};
private static _lastDocId: number = 0;
public static getDocument(uri: vscode.Uri) : MemoryFile | null
{
return MemoryFileManagement._documents[uri.path];
}
private static _getNextDocId(): string{
MemoryFileManagement._lastDocId++;
return "_" + MemoryFileManagement._lastDocId + "_";
}
public static createDocument(extension = "")
{
let path = MemoryFileManagement._getNextDocId ();
if (extension != "")
path += "." + extension;
let self = new MemoryFile(path);
MemoryFileManagement._documents[path] = self;
return self;
}
}
/**
* A file in memory
**/
export class MemoryFile
{
/******************
** Static Area **
******************/
public static getDocument(uri: vscode.Uri) : MemoryFile | null {
return MemoryFileManagement.getDocument (uri);
}
public static createDocument(extension = "") {
return MemoryFileManagement.createDocument (extension);
}
/******************
** Object Area **
******************/
public content: string = "";
public uri: vscode.Uri;
constructor (path: string)
{
this.uri = vscode.Uri.from ({scheme: _SCHEME, path: path})
}
public write(strContent: string){
this.content += strContent;
}
public read(): string {
return this.content;
}
public getUri(): vscode.Uri {
return this.uri;
}
}
Example usage
Register the provider
You need to register the provider somewhere in the beginning of your test code (I do it in index.ts before Mocha is instantiated):
register_memoryFileProvider (extensionContext);
(How do I get the extension context?)
Create a document
Creating and using a file works as follows:
// create the in-memory document
let memfile = MemoryFile.createDocument ("ts");
memfile.write ("my content");
// create a vscode.TextDocument from the in-memory document.
let doc = await vscode.workspace.openTextDocument (memfile.getUri ());
Notes
Be aware, that LSP commands might not work with with approach, because they might be registered to a certain specific schema.
As rioV8 said, you can also use an existing document and change its content. Here the code:
export class TmpFile
{
private static _lastDocId: number = 0;
private static _getNextDocId(): string{
this._lastDocId++;
return "tmpfile_" + this._lastDocId;
}
public static async createDocument(strContent: string, extension:string = "")
: Promise<vscode.TextDocument | null>
{
let folder = "/tmp"
let filename = this._getNextDocId ();
let ext = (extension != "" ? "." + extension : "");
const newFile = vscode.Uri.parse('untitled:' + path.join(folder, filename + ext));
{
const edit = new vscode.WorkspaceEdit();
edit.insert(newFile, new vscode.Position(0, 0), strContent);
let success = await vscode.workspace.applyEdit(edit);
if (!success)
return null;
}
let document = await vscode.workspace.openTextDocument(newFile);
return document;
}
}
Pro's
It's a file (schema), so all LSP commands will work
The path (used above) does not even need to exist.
Con's
The file is really opened in the editor. You need to close it later
The file is a changed file in the editor, so it will ask you to save the changes upon closing.
Files cannot be closed in vscode. You can only run:
vscode.window.showTextDocument(doc.uri, {preview: true, preserveFocus: false})
.then(() => {
return vscode.commands.executeCommand('workbench.action.closeActiveEditor');
});
```<br>
which is a rather nasty workaround.

why flutter static final variable changed

I am define a static final variable in flutter using configuration package like this:
class GlobalConfig {
static final GlobalConfiguration config = GlobalConfiguration();
}
to my understand, the config should initial once when load the class, then keep the same all the time until close the app. But now the config has different value in app running. It make me sometimes read config success, sometimes failed. This is my full code of GlobalConfig class:
import 'package:global_configuration/global_configuration.dart';
enum ConfigType { DEV, PRO }
class GlobalConfig {
GlobalConfig(this.baseUrl, this.shareUrl, this.staticResourceUrl);
static final GlobalConfiguration config = GlobalConfiguration();
String baseUrl = config.get("baseUrl");
String shareUrl = config.get("shareUrl");
String staticResourceUrl = config.get("staticUrl");
static getBaseUrl() {
String configBaseUrl = config.get("baseUrl");
return configBaseUrl;
}
static getShareUrl() {
return config.get("shareUrl");
}
static getStaticResourceUrl() {
return config.get("staticUrl");
}
static getConfig(String key) {
return config.get(key);
}
static init(ConfigType configType) {
switch (configType) {
case ConfigType.DEV:
break;
case ConfigType.PRO:
break;
}
}
}
is there any way to make the config keep the same and do not change? or what should I do to make read the config keep stable? I want the config read all success or all failed. this is the success reading when debbuging:
this is the read faild debbuing:
the breakpoint was entered after the app configuration loaded complete. BTW, my project is open source, all the source code from here. The basic logic is when playing music, I send a http request to save the songs info to my own server. the function is: ReddwarfMusic.savePlayingMusic, this function read a global config of my own server url. The RM radio page add favirate save reading config success, but auto save from FM radio read config failed. I tried to make the config final static but not fixed it. So I thinking for days and do not know how to make it work.
TRIED:
now I tweak the code like this:
import 'package:global_configuration/global_configuration.dart';
enum ConfigType { DEV, PRO }
class GlobalConfig {
static Map config = Map<String, String>();
GlobalConfig() {
}
static getBaseUrl() {
String configBaseUrl = config["baseUrl"];
return configBaseUrl;
}
static getShareUrl() {
return config["shareUrl"];
}
static getStaticResourceUrl() {
return config["staticUrl"];
}
static getConfig(String key) {
return config[key];
}
static init(ConfigType configType) {
var globalConfig = GlobalConfiguration();
if (globalConfig.appConfig.isNotEmpty) {
config = Map.unmodifiable(globalConfig.appConfig);
}
switch (configType) {
case ConfigType.DEV:
break;
case ConfigType.PRO:
break;
}
}
}
but the config seems initial every time. First I set the config success, but when I use, the config changed to null.
this is the initial code:
var globalConfig = GlobalConfiguration();
if (globalConfig.appConfig.isNotEmpty) {
config = Map.unmodifiable(globalConfig.appConfig);
}
I am sure the initial config success.
I finnaly find one type of invoke was send from background service pragma entry point, so the configuration should initial in background service too. This is the entry look like:
import 'package:flutter/material.dart';
import 'package:music_player/music_player.dart';
import 'package:quiet/app.dart';
import 'package:quiet/component.dart';
import 'package:quiet/repository/netease.dart';
import 'package:wheel/wheel.dart';
void main() {
CommonUtils.initialApp(ConfigType.PRO).whenComplete(() => {loadApp(ConfigType.PRO)});
}
/// The entry of dart background service
/// NOTE: this method will be invoked by native (Android/iOS)
#pragma('vm:entry-point') // avoid Tree Shaking
void playerBackgroundService() {
CommonUtils.initialApp(ConfigType.PRO).whenComplete(() => {
loadRepository()
});
}
void loadRepository() {
WidgetsFlutterBinding.ensureInitialized();
GlobalConfig.init(ConfigType.PRO);
neteaseRepository = NeteaseRepository();
runBackgroundService(
imageLoadInterceptor: BackgroundInterceptors.loadImageInterceptor,
playUriInterceptor: BackgroundInterceptors.playUriInterceptor,
playQueueInterceptor: QuietPlayQueueInterceptor(),
);
}
I don't know why the background service could not read the configuration.

VSCode API Check if the current line is a comment

Is it possible to query if the current line is a comment using the VSCode API ?
This solution is not optimal, but it works.
Taken from: https://github.com/aaron-bond/better-comments/pull/302/commits/47717e7ddcf110cb7cd2a7902ccc98ab146f97a5
You can generate a configuration file that lists all the language specific keywords/symbols from the currently installed extensions.
Then use this file to get the comment configuration and thus the comment delimiters (for line comment and comment blocks).
I implemented a class for doing this, borrowing from the above commit:
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
interface CommentConfig {
lineComment?: string;
blockComment?: [string, string];
}
export class CommentConfigHandler {
private readonly languageToConfigPath = new Map<string, string>();
private readonly commentConfig = new Map<string, CommentConfig | undefined>();
public constructor() {
this.updateLanguagesDefinitions();
}
/**
* Generate a map of language configuration file by language defined by extensions
* External extensions can override default configurations os VSCode
*/
public updateLanguagesDefinitions() {
this.commentConfig.clear();
for (const extension of vscode.extensions.all) {
const packageJSON = extension.packageJSON as any;
if (packageJSON.contributes && packageJSON.contributes.languages) {
for (const language of packageJSON.contributes.languages) {
if (language.configuration) {
const configPath = path.join(extension.extensionPath, language.configuration);
this.languageToConfigPath.set(language.id, configPath);
}
}
}
}
}
/**
* Return the comment config for `languageCode`
* #param languageCode The short code of the current language
*/
public getCommentConfig(languageCode: string): CommentConfig | undefined {
if (this.commentConfig.has(languageCode)) {
return this.commentConfig.get(languageCode);
}
if (!this.languageToConfigPath.has(languageCode)) {
return undefined;
}
const file = this.languageToConfigPath.get(languageCode) as string;
const content = fs.readFileSync(file, { encoding: 'utf8' });
try {
// Using normal JSON because json5 behaved buggy.
// Might need JSON5 in the future to parse language jsons with comments.
const config = JSON.parse(content);
this.commentConfig.set(languageCode, config.comments);
return config.comments;
} catch (error) {
this.commentConfig.set(languageCode, undefined);
return undefined;
}
}
}
To detect if a line is a comment you could get the comment configuration using the class above, escape the delimiters, and use them in a regex.
Something like this:
activeEditor = vscode.window.activeTextEditor;
const commentConfigHandler = new CommentConfig();
const commentCfg = commentConfigHandler.getCommentConfig(activeEditor.document.languageId);
function escapeRegex(string: string) {
return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}
const commentLineDelimiter = commentCfg.lineComment;
const regex = new RegExp(`\s*${escapeRegex(commentLineDelimiter )}.*`, "ig");
const isComment = regex.test(lineText)
Note that the above example only tests for single line comments and will need to be extended for block comments with a more extensive regex that includes commentCfg.blockComment[0] and commentCfg.blockComment[1].

Log to console from Javascript

Is it possible to log to the java console?
I've tried to supply an object with a method on it to use for logging but nothing shows up in debug console.
Am I missing something?
window.setMember("mylog", new Console());
execute("mylog.log('11111111'))
public class Console {
public void log(Object ... objects) {
Logger...get..then..log(obj);
}
}
Is there a better way to log to the Java console ?
This is not working.
Unfortunately, my code relies on a number of libraries and I am unable to push the solution to the UI4J repo.
It basically overrides the normal console.log and works similarly. It intentionally avoids to JSON.stringify(object) since circular dependencies can cause serious issues.
The code:
import netscape.javascript.JSObject;
import momomo.com.Opensource.sources.Functional.lambdas.version.interfaces.Lambda;
....
try {
this.page = navigate( IO.toString(file) );
putConsole();
}
finally {
IO.remove(file);
}
private void putConsole() {
// Note that we call log using the entire arguments ( array )
execute("console.log = function() {" +
put(CONSOLE) + ".log(arguments);" +
"};");
}
}
private static final Console CONSOLE = new Console();
public static final class Console {
public void log(JSObject js) {
StringBuilder sb = new StringBuilder();
iterate(js, (o) -> {
sb.append(o).append(" ");
});
$Log.info(Console.class, sb.toString());
}
}
public static void iterate(JSObject js, Lambda.V1<Object> lambda) {
iterate(js, lambda.R1());
}
public static void iterate(JSObject js, Lambda.R1<Boolean, Object> lambda) {
if ( js != null ) {
Object member;
int i = 0;
while (true) {
member = js.getSlot(i);
if ( "undefined".equals(member) || Is.False( lambda.call(member) ) ) {
return;
}
i++;
}
}
}
Outdated but useful Lambda.java reference:
https://github.com/momomo/Opensource/blob/master/src/momomo/com/Opensource/sources/Functional/lambdas/version/interfaces/Lambda.java
Note that put(CONSOLE) call above, basically calls execute("window").setMember("key", new Console()), so there is no magic there, although I have some other logic to achive the same result.

phpunit abstract class constant

I'm trying to find a way to test a abstract class constant that must exist and match/not match a value. Example:
// to be extended by ExternalSDKClild
abstract class ExternalSDK {
const VERSION = '3.1.1.';
}
class foo extends AController {
public function init() {
if ( ExternalSDK::VERSION !== '3.1.1' ) {
throw new Exception('Wrong ExternalSDK version!');
}
$this->setExternalSDKChild(new ExternalSDKChild());
}
}
Limitations... The framework we use doesn't allow dependency injection in the init() method. (Suggestion to refactor the init() method could be the way to go...)
The unit tests and code coverage I have run, cover all but the Exception. I can't figure out a way to make the ExternalSDK::Version to be different from what it is.
All thoughts welcome
First, refactor the call to new into a separate method.
Second, add a method to acquire the version instead of accessing the constant directly. Class constants in PHP are compiled into the file when parsed and cannot be changed.* Since they are accessed statically, there's no way to override it without swapping in a different class declaration with the same name. The only way to do that using standard PHP is to run the test in a separate process which is very expensive.
class ExternalSDK {
const VERSION = '3.1.1';
public function getVersion() {
return static::VERSION;
}
}
class foo extends AController {
public function init() {
$sdk = $this->createSDK();
if ( $sdk->getVersion() !== '3.1.1' ) {
throw new Exception('Wrong ExternalSDK version!');
}
$this->setExternalSDKChild($sdk);
}
public function createSDK() {
return new ExternalSDKChild();
}
}
And now for the unit test.
class NewerSDK extends ExternalSDK {
const VERSION = '3.1.2';
}
/**
* #expectedException Exception
*/
function testInitFailsWhenVersionIsDifferent() {
$sdk = new NewerSDK();
$foo = $this->getMock('foo', array('createSDK'));
$foo->expects($this->once())
->method('createSDK')
->will($this->returnValue($sdk));
$foo->init();
}
*Runkit provides runkit_constant_redefine() which may work here. You'll need to catch the exception manually instead of using #expectedException so you can reset the constant back to the correct value. Or you can do it in tearDown().
function testInitFailsWhenVersionIsDifferent() {
try {
runkit_constant_redefine('ExternalSDK::VERSION', '3.1.0');
$foo = new foo();
$foo->init();
$failed = true;
}
catch (Exception $e) {
$failed = false;
}
runkit_constant_redefine('ExternalSDK::VERSION', '3.1.1');
if ($failed) {
self::fail('Failed to detect incorrect SDK version.');
}
}