diff --git a/extension.js b/extension.js index 4fc8331..6e9cc79 100644 --- a/extension.js +++ b/extension.js @@ -1,23 +1,43 @@ // The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below -var vscode = require('vscode'); +const vscode = require('vscode'); +const { AndroidContentProvider, openLogcatWindow } = require('./src/logcat'); + +function getADBPort() { + var adbPort = 5037; + // there's surely got to be a better way than this... + var configs = vscode.workspace.getConfiguration('launch.configurations'); + for (var i=0,config; config=configs.get(''+i); i++) { + if (config.type!=='android') continue; + if (config.request!=='launch') continue; + if (typeof config.adbPort === 'number' && config.adbPort === (config.adbPort|0)) + adbPort = config.adbPort; + break; + } + return adbPort; +} // this method is called when your extension is activated // your extension is activated the very first time the command is executed function activate(context) { - /* Nothing is done here. The debugger is launched from src/debugMain.js */ + /* Only the logcat stuff is configured here. The debugger is launched from src/debugMain.js */ + AndroidContentProvider.register(context, vscode.workspace); + + // logcat connections require the (fake) websocket proxy to be up + // - take the ADB port from launch.json + const wsproxyserver = require('./src/wsproxy').proxy.Server(6037, getADBPort()); // The commandId parameter must match the command field in package.json var disposables = [ - /* - vscode.commands.registerCommand('extension.doCommand', config => { - return vscode.window.showInputBox({ - placeHolder: "Enter a value", - value: "a value to display" - }); + // add the view logcat handler + vscode.commands.registerCommand('android-dev-ext.view_logcat', () => { + openLogcatWindow(vscode); + }), + // watch for changes in the launch config + vscode.workspace.onDidChangeConfiguration(e => { + wsproxyserver.setADBPort(getADBPort()); }) - */ ]; var spliceparams = [context.subscriptions.length,0].concat(disposables); diff --git a/package.json b/package.json index c01fa0d..cb7f540 100644 --- a/package.json +++ b/package.json @@ -17,14 +17,21 @@ "color": "#5c2d91", "theme": "dark" }, - "activationEvents": [], + "activationEvents": [ + "onCommand:android-dev-ext.view_logcat" + ], "repository": { "type": "git", "url": "https://github.com/adelphes/android-dev-ext" }, "main": "./extension", "contributes": { - "commands": [], + "commands": [ + { + "command": "android-dev-ext.view_logcat", + "title": "Android: View Logcat" + } + ], "breakpoints": [ { "language": "java" @@ -107,6 +114,7 @@ "dependencies": { "vscode-debugprotocol": "^1.15.0", "vscode-debugadapter": "^1.15.0", + "ws":"^1.1.1", "xmldom": "^0.1.27", "xpath": "^0.0.23" }, diff --git a/src/jq-promise.js b/src/jq-promise.js index b6bedb1..a0c726f 100644 --- a/src/jq-promise.js +++ b/src/jq-promise.js @@ -28,6 +28,14 @@ var Deferred = exports.Deferred = function(p, parent) { thendef._promise = thendef._original = p; return thendef; }, + always(fn) { + var thendef = this.then(fn); + this.fail(function() { + // we cannot bind thendef to the function because we need the caller's this to resolve the thendef + thendef.resolveWith(this, Array.prototype.map.call(arguments,x=>x)); + }); + return thendef; + }, fail(fn) { var faildef = $.Deferred(null, this); var p = this._promise.catch(function(a) { diff --git a/src/logcat.js b/src/logcat.js new file mode 100644 index 0000000..5440768 --- /dev/null +++ b/src/logcat.js @@ -0,0 +1,335 @@ +'use strict' +// vscode stuff +const { EventEmitter, Uri } = require('vscode'); +// node and external modules +const os = require('os'); +const path = require('path'); +const WebSocketServer = require('ws').Server; +// our stuff +const { ADBClient } = require('./adbclient'); +const $ = require('./jq-promise'); +const { D } = require('./util'); + +/* + Class to setup and store logcat data + */ +class LogcatContent { + + constructor(provider/*: AndroidContentProvider*/, uri/*: Uri*/) { + this._provider = provider; + this._uri = uri; + this._logcatid = uri.query; + this._logs = []; + this._htmllogs = []; + this._oldhtmllogs = []; + this._prevlogs = null; + this._notifying = 0; + this._refreshRate = 200; // ms + this._state = ''; + this._adbclient = new ADBClient(uri.query); + this._initwait = new Promise((resolve, reject) => { + this._state = 'connecting'; + LogcatContent.initWebSocketServer() + .then(() => { + return this._adbclient.logcat({ + onlog: this.onLogcatContent.bind(this), + onclose: this.onLogcatDisconnect.bind(this), + }); + }).then(x => { + this._state = 'connected'; + this._initwait = null; + resolve(this.content); + }).fail(e => { + this._state = 'connect_failed'; + reject(e) + this._provider._notifyLogDisconnected(this); + }) + }); + } + get content() { + if (this._initwait) return this._initwait; + if (this._state !== 'disconnected') + return this.htmlBootstrap(true, ''); + // if we're in the disconnected state, and this.content is called, it means the user has requested + // this logcat again - check if the device has reconnected + return this._initwait = new Promise((resolve, reject) => { + // clear the logs first - if we successfully reconnect, we will be retrieving the entire logcat again + this._prevlogs = {_logs: this._logs, _htmllogs: this._htmllogs, _oldhtmllogs: this._oldhtmllogs }; + this._logs = []; this._htmllogs = []; this._oldhtmllogs = []; + this._adbclient.logcat({ + onlog: this.onLogcatContent.bind(this), + onclose: this.onLogcatDisconnect.bind(this), + }).then(x => { + // we successfully reconnected + this._state = 'connected'; + this._prevlogs = null; + this._initwait = null; + resolve(this.content); + }).fail(e => { + // reconnection failed - put the logs back and return the cached info + this._logs = this._prevlogs._logs; + this._htmllogs = this._prevlogs._htmllogs; + this._oldhtmllogs = this._prevlogs._oldhtmllogs; + this._prevlogs = null; + this._initwait = null; + var cached_content = this.htmlBootstrap(false, 'Device disconnected'); + resolve(cached_content); + }) + }); + } + sendDisconnectMsg() { + var clients = LogcatContent._wss.clients.filter(client => client._logcatid === this._logcatid); + clients.forEach(client => client.send(':disconnect')); + } + updateLogs() { + // no point in formatting the data if there are no connected clients + var clients = LogcatContent._wss.clients.filter(client => client._logcatid === this._logcatid); + if (clients.length) { + var lines = '